{"id":17723552,"url":"https://github.com/doriantaylor/rb-forget-passwords","last_synced_at":"2025-03-31T13:54:54.450Z","repository":{"id":62559496,"uuid":"188147098","full_name":"doriantaylor/rb-forget-passwords","owner":"doriantaylor","description":"Standalone module for extremely lazy Web authentication","archived":false,"fork":false,"pushed_at":"2023-07-27T12:11:38.000Z","size":258,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-10-13T08:57:08.930Z","etag":null,"topics":["authentication","web"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doriantaylor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-05-23T02:29:00.000Z","updated_at":"2022-12-14T23:01:03.000Z","dependencies_parsed_at":"2024-10-25T20:04:21.127Z","dependency_job_id":"2fee5fcd-5e4e-47e5-a95b-0a6fd6ab998f","html_url":"https://github.com/doriantaylor/rb-forget-passwords","commit_stats":{"total_commits":44,"total_committers":2,"mean_commits":22.0,"dds":"0.20454545454545459","last_synced_commit":"0dc6c52ff22b89f67c87252b1cc543629be360ba"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-forget-passwords","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-forget-passwords/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-forget-passwords/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Frb-forget-passwords/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doriantaylor","download_url":"https://codeload.github.com/doriantaylor/rb-forget-passwords/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246477292,"owners_count":20783976,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","web"],"created_at":"2024-10-25T15:43:21.202Z","updated_at":"2025-03-31T13:54:54.429Z","avatar_url":"https://github.com/doriantaylor.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Forget Passwords: Log in the Lazy Way\n\n**Forget Passwords** is a stand-alone Web authentication module that\nreplicates the \"forgot-my-password\" user flow, which will, on request,\ne-mail a special link to an address on a predefined list, in lieu of\npassword-based authentication. This module makes use of [a\nlesser-known feature of the FastCGI\nprotocol](https://fastcgi-archives.github.io/FastCGI_Specification.html#S6.3)\nto do its work, and plugs into a receiving end such as [Apache's\n`mod_authnz_fcgi`](https://httpd.apache.org/docs/2.4/mod/mod_authnz_fcgi.html).\n\n## Rationale \u0026 Goal\n\nI have various Web properties littered around the internet in various\nstages of development. Sometimes I want to show these properties to\npeople, but only _certain_ people—an example being both existing and\nprospective clients.\n\nProviding meaningful protection to a website almost always entails\nsome kind of authentication mechanism, and unless you go exotic, this\nalmost always means some kind of password. A single, shared password\nis often inadequate protection because it can be leaked. This means\neverybody to whom I would want to access one of these Web properties\nwould need their own password.\n\nIn this particular case, I am looking to support a relatively small\nnumber of people, the total extent of whom is not necessarily known up\nfront. Under a password-based authentication regime, not only would I\nbe burdening clients and prospects with yet another set of\nauthentication credentials to manage, but I would also be burdening\n_myself_ with the chore of fielding requests for new accounts* as\nstragglers trickle in, as well as reset or retrieve lost passwords.\n\n\u003e \\* I suppose I could set up UI for _them_ to create their own\n\u003e accounts and retrieve their lost passwords, but that would actually\n\u003e be more work than the solution I propose here, and the net effect\n\u003e would be to _further_ burden my users.\n\nThe solution to this problem stems from observing that the long tail\nof Web authentication is serviced by the archetypal **forgot my\npassword** flow _itself_, so why force people to go through the extra\nstep of _creating_, and then _remembering_, a password?\n\nThe goal, then, is to create an authentication module that replicates\nthe forgot-my-password flow, provides about the same security as\n`Basic` authentication over SSL, has a generic-enough user interface\nto be merged seamlessly into any existing system, and otherwise\ninteracts minimally with any downstream access control mechanism or\nWeb application, including static content. An additional requirement\nis that a mapping scheme (e.g. website domain to e-mail domain) can be\nset up to provision identities (accounts) automatically.\n\n## How It Works\n\nThis module mainly operates as a FastCGI application in the\n`AUTHORIZER` role, intended to plug into Apache's `mod_authnz_fcgi` or\nany workalike, and configured in the server just as one would any\nother authentication module. In addition to the authentication module,\nthere a couple of dynamic pages (namely login, logout) that need to be\nsurfaced as well. (Their locations are configurable.)\n\nWhen unauthenticated users hit the protected area, they are met with a\nform entreating them to enter their e-mail address. When they submit\nthe form, they are mailed a link with a random token attached to it\nthat provides the authentication. When the user visits the link, the\nauthentication handler trades the token attached to the URL (marking\nit as used in the process) for a cookie. Responses to subsequent\nrequests then match the cookie to the user's e-mail address, and use\nthat to populate the `REMOTE_USER` field, which can then be picked up\nby any downstream authorization handler or Web application.\n\nUsers of this system must be pre-authorized. The `forgetpw`\ncommand-line tool that ships with this package has a verb for doing\nthis. Since the primary use case for this module is client extranets,\nand it is customary that everybody at Widgets, Inc., will have a\n`@widgets.biz` address, entire e-mail domains can be mapped to Web\ndomains. In other words, you can say \"grant access to anybody at\nwidgets dot biz to `widgets-inc.extranet.my.company`\", and then not\nhave to think subsequently about whether this or that person at the\ncompany has access.\n\n\u003e Also, if necessary, specific addresses can be blocked.\n\nThis module uses an SQL database as its primary storage mechanism. It\nhas been tested with SQLite and PostgreSQL, though in principle it\nshould work with anything for which there is a\n[Sequel](https://sequel.jeremyevans.net/) driver. Use of SQLite is\ndiscouraged in production, due to its well-known inability to handle\nconcurrent transactions. There is a secondary storage in the module\nitself for the little over a dozen user interface templates.  The\nlocations of these (and thus their contents) can be overridden in a\nconfiguration file, along with a number of other parameters, a few of\nwhich (e.g., data source name, e-mail sender) are necessary for the\nmodule to operate.\n\n## Usage\n\nTo start using Forget Passwords, we'll assume you have done the\nnecessary setup on the server (below), as well as all the necessary\nsetup for an address to send e-mail from. After that, we'll need a\ndatabase (this example uses PostgreSQL; you can of course skip this\nstep for SQLite):\n\n    $ createdb forgetpw\n\nNow we initialize the configuration file and create the tables:\n\n    $ forgetpw -c ~/.forgetpw.yml init -d postgres:///forgetpw \\\n    -f noreply@my.company\n\n\u003e Note: the `init` command uses the `-c` flag as the location to\n\u003e _write_ a _new_ configuration file, while all other commands use the\n\u003e flag as the source to _read_ from an existing one. The program\n\u003e otherwise looks for `forgetpw.yml` in the current directory.\n\nNow we privilege some e-mail addresses:\n\n    $ forgetpw -c ~/.forgetpw.yml privilege \\\n    -d widgets-inc.extranet.my.company widgets.biz some@other.person\n\nNow, assuming we have configured the server, we start the daemon:\n\n    $ forgetpw -c ~/.forgetpw.yml fcgi\n    Running authenticator daemon on fcgi://localhost:10101/\n\n\u003e You can use `-z` to detach the process. Listener IP and port are of\n\u003e course also configurable.\n\n## Server Configuration\n\nCurrently the only known receptacle for this module is\n`mod_authnz_fcgi`, which ships with Apache, though the interface is\nstandard (to the extent that FastCGI is a standard), and so in\nprinciple it is usable in other systems. What follows is the\nconfiguration for Apache 2.4.x or newer.\n\nFirst, we need to declare the authenticator (here it can be called\nanything but we are appropriately calling it `ForgetPasswords`) and\nwhere it's listening:\n\n```apache\nAuthnzFcgiDefineProvider authn ForgetPasswords fcgi://localhost:10101/\n```\n\n\u003e On Debian systems and their derivatives, this is in a separate file,\n\u003e `mods-available/authnz_fcgi.conf`. Note that you will also have to\n\u003e `a2enmod authnz_fcgi` or none of this configuration will work.\n\nThen, in the virtual host (or main server configuration in lieu\nthereof), we can use any standard configuration mechanism we want to\ndelineate the protected area. We invoke the module with the\n`AuthnzFcgiCheckAuthnProvider` directive, and then tune it with\n`Require`. `mod_authnz_fcgi` has a number of idiosyncrasies, one of\nwhich is that it always must return a user, so we have to give it a\nthrowaway user like `nobody`, and then subsequently deny that user. (I\nwould consider this a design flaw in `mod_authnz_fcgi`.) The\nexpression `%{reqenv:FCGI_USER}` (where the slug `FCGI_USER` is\nconfigurable on our side) is how the identity gets transmitted\nupstream from Forget Passwords to the server.\n\n```apache\n\u003cLocation /protected\u003e\n  # unfortunately mod_authnz_fcgi won't let you have a blank default user\n  AuthnzFcgiCheckAuthnProvider ForgetPasswords Authoritative On RequireBasicAuth Off UserExpr \"%{reqenv:FCGI_USER}\" DefaultUser nobody\n  \u003cRequireAll\u003e\n    Require valid-user\n    # that's fine, we just outlaw 'nobody'\n    Require not user nobody\n  \u003c/RequireAll\u003e\n\u003c/Location\u003e\n```\n\nAnother idiosyncrasy of `mod_authnz_fcgi` is that while it uses the\n`200` response code to indicate a success, the _actual_ response back\nto the client necessarily has to come from the downstram content\nhandler. As such, any other information from a _successful_\nauthentication response needs to be smuggled out through environment\nvariables. Since Forget Passwords performs a redirect to remove the\nauthentication token from the URL upon successful authentication, the\nfollowing `mod_rewrite` configuration needs to be in place to turn the\nenvironment variable back into an actual redirect:\n\n```apache\nRewriteCond %{QUERY_STRING} .+\nRewriteCond %{ENV:FCGI_REDIRECT} .+\nRewriteRule .* %{ENV:FCGI_REDIRECT} [R=307,L,QSD]\n```\n\n\u003e Note that `mod_rewrite` syntax is different from `ap_expr` syntax,\n\u003e and the prefix `ENV` is used in the expression instead of `reqenv`\n\u003e above. We also use `QSD` to remove the query string from the\n\u003e _currently-requested_ URI, and redirect with `307` to preserve the\n\u003e request method.\n\u003e\n\u003e Note as well that URL rewriting typically happens _before_\n\u003e authorization, so the environment variable will not yet be set\n\u003e unless you trick the `RewriteRule` to run in a later phase (e.g. by\n\u003e putting it in a `\u003cDirectory\u003e` block or `.htaccess`).\n\nWe also need to account for _unsuccessful_ responses from the\nauthentication module, since certain headers (notably `Content-Type`)\nare either getting overwritten by an unfortunate interaction with the\ndefault error handler, or are otherwise not being transmitted (which\nwould be another bug in `mod_authnz_fcgi`).\n\n```apache\nHeader always set Content-Type \"expr=%{resp:Variable-FCGI_CONTENT_TYPE}\" \"expr=%{resp:Variable-FCGI_CONTENT_TYPE} != ''\"\nHeader always unset Variable-FCGI_CONTENT_TYPE\n```\n\nFinally, the module provides two dynamic resources that need to be\nmapped to content handlers; here we use `mod_proxy_fcgi` (remember to\nenable it):\n\n```apache\nProxyPass /email-link fcgi://localhost:10101/email-link\nProxyPass /logout     fcgi://localhost:10101/logout\n```\n\n\u003e An earlier design had these operations controlled exclusively by\n\u003e `POST` parameters on _any_ resource, and therefore these\n\u003e purpose-made resources were ostensibly not necessary. However, it\n\u003e turns out that `mod_authnz_fcgi` does not convey request body\n\u003e content to the downstream FastCGI script, causing the latter to\n\u003e crash with a protocol error. While the handling is less than\n\u003e delicate, this is actually a reasonable expectation, as request\n\u003e bodies are only read once off the wire and will thus be already\n\u003e consumed (whether or not they contain the fields to which Forget\n\u003e Passwords is sensitive) when the content handler is invoked. (The\n\u003e way Apache handles the request body, it _can_ be duplicated and\n\u003e reinserted into the input stream, but that is a whole project unto\n\u003e itself.\n\n### Caveats\n\nI have noticed that a `RewriteRule` (in a `.htaccess`) with the\npassthrough (`PT`) flag will short-circuit the redirect that happens\nwhen a user follows the link off an e-mail. Same ostensibly goes for\noverriding `DirectoryIndex` in a `.htaccess`. The observable effect is\nthat the server returns 401 (and doesn't redirect/remove the query\nstring) even though the cookie is set and the knock token is consumed.\nIf you refresh the page, then it will say (correctly, from its point\nof view) that the link is expired. If you manually chop off the query\nstring, it will correctly display the logged-in state.\n\n\u003e One thing I didn't check is if it still returned a `Location:`\n\u003e header, which the browser will ignore if the response code is\n\u003e anything other than most (but not all) of the 300s and 201.\n\nThis is likely because these configuration directives are causing\nsubrequests and/or internal redirects, which triggers the handler, but\ndoesn't convey its response to the client. This might be an inherent\nlimitation of using FastCGI in `AUTHORIZER` mode, because there is no\nway to tell it that it is being triggered from a subrequest (unless\nthere is?). More research is needed to probe potential interactions\nwith other handlers.\n\n## Templates\n\nForget Passwords has a number of UI states that are embedded in the\ngem. These take the form of template files. The functionality of these\ntemplates is currently at the absolute bare minimum required to do the\njob. The templates are XHTML, with a basic placeholder substitution\nfunctionality, which can take place either in processing instructions\n(`\u003c?var $WHATEVER?\u003e`), or attribute values (`\u003celem\nattr=\"$WHATEVER\"/\u003e`).\n\n\u003e I did this deliberately for a few reasons, the first being that the\n\u003e substitutions occur in a way such that the input _and_ the output\n\u003e always validates, i.e., there is no way to produce broken markup.\n\u003e The second is that this system neither needs nor merits a more\n\u003e sophisticated templating system. Each state is directly addressable;\n\u003e it gets its own template file. Anything that needs to be addressed\n\u003e in any individual state, save for a small number of substitutions in\n\u003e text nodes or attribute values, can be done by supplanting its file\n\u003e with a different one. Any styling or page composition needed to knit\n\u003e these states into their surroundings can be handled through an\n\u003e exterior mechanism, which I will endeavour to write up separately. I\n\u003e may consider different or additional template mechanisms\n\u003e (e.g. markdown, or any of the zillion non-standard template engines)\n\u003e at some point in the future.\n\u003e\n\u003e Note as well that the templates are not currently internationalized,\n\u003e but I am open to making them so if there is sufficient demand.\n\nThe configuration parameter `transform` under `templates` will cause\nan `xml-stylesheet` processing instruction to be inserted into all\noutgoing templates with the location of an XSLT stylesheet, enabling\narbitrary manipulations (and also the main reason why these templates\nare XHTML and not regular HTML).\n\n\u003e **NOTE 2022-04-22** this `forgetpw extract` business is still\n\u003e under construction.\n\nThe default templates for all states are embedded in the gem\ndistribution, and can be overridden individually or en masse in the\nconfiguration file by specifying the location of a supplanting file.\nThe command-line verb `forgetpw extract $DESTINATION` will extract\nthe full set of templates from the gem, and deposit copies of them\nwherever you tell it to.\n\nIn addition to these templates that get piped out from arbitrary\nlocations, there are a couple resources, namely two logout states\n(`/logged-out` for current device; `/logged-out-all` for all devices),\nwhich can be completely static. Boilerplate for these states is\nincluded in the distribution and can be retrieved by running\n`forgetpw extract --static`. The URLs of these resources can\nnaturally be overridden in the configuration file.\n\n\u003e Out of an abundance of prudence I should also remark that to\n\u003e eliminate file extensions in static resources (at least in Apache),\n\u003e enable `mod_negotiation` and add `MultiViews` to any `Options`\n\u003e directive in scope.\n\nWhat follows is the list of states, when they show up, and roughly\nwhat they say. Most of them are specific error conditions:\n\n### `default_401` (currently handled by `basic-401.xhtml`)\n\nThis page is the one everybody sees when they are not logged in,\nunless a more specific page is more appropriate. It explains that the\narea is protected, and the way to get access (assuming that you're on\nthe list) is to enter your e-mail address. It then provides said\nform. Note that the `action=` of the form **must** point to the\nlocation of the `email-link` resource, and there must also be a hidden\nform field by the name of `forward` that contains the current URL.\n\n### `default_404` (currently handled by `basic-404.xhtml`)\n\nThis resource should actually never be seen, as it currently only\narises when outside content-handling traffic is directed to locations\nother than the two specified by Forget Passwords.\n\n### `knock_bad` (currently handled by `basic-409.xhtml`)\n\nThis is shown when the knock-knock token attached to the URL is\nmalformed. It is an undifferentiated `409 Conflict` message, which\nalso includes a form like the one found in the default `401`.\n\n### `knock_not_found` (currently handled by `basic-409.xhtml`)\n\nThis is shown when the token is _not_ malformed, but also not present\nin the database. (This is treated as a `403 Forbidden`, but the error\nmessage is not meaningfully different from `409`, so it gets the same\nmessage by default.)\n\n### `knock_expired` (currently handled by `nonce-expired.xhtml`)\n\nHere, the token attached to the link sent out in the e-mail has\nexpired, i.e., the user has not claimed it in time (by default, 10\nminutes). Again we notify themm, and show them the form to generate a\nnew one.\n\n### `cookie_bad` (currently handled by `basic-409.xhtml`)\n\nThis recapitulates the `knock_bad` scenario, but with a cookie.\n\n### `cookie_not_found` (currently handled by `basic-409.xhtml`)\n\nThe cookie equivalent of `knock_not_found`.\n\n### `cookie_expired` (currently handled by `cookie-expired.xhtml`)\n\nThis message is shown when the user has a cookie which has been\ninvalidated either by a logout or has been expired on the server\nside. The user is given an opportunity to log back in.\n\n### `no_user` (currently handled by `not-on-list.xhtml`)\n\nThis message is returned when the cookie is valid but the user is not,\ne.g. their access was revoked since they hit the site last. They are\ngiven an opportunity to log back in.\n\n### `forward_bad` (currently handled by `uri-409.xhtml`)\n\nThis message is shown as the result of the user submitting their\ne-mail when the forwarding address (URL), which should have been\nincluded in the submitted form, is malformed (e.g. does not match the\ndomain). This is nominally a client error but it should never be\nreached by normal operation. The only way a user would get here is a\nmisconfiguration on our part, or an attempt at abuse. We tell them to\ngo back and try again.\n\n### `email` (currently handled by `email.xhtml`)\n\nThis is the actual e-mail that gets sent to the user. Note that the\n`\u003ctitle\u003e` gets turned into the subject, and the entire thing is also\nstripped to plain text.\n\n### `email_bad` (currently handled by `email-409.xhtml`)\n\nThis status is returned after a user submits an e-mail address that is\nsyntactically bad.\n\n### `email_not_listed` (currently handled by `not-on-list.xhtml`)\n\nThis happens when the e-mail address is not on the permit list. Users\nare given an opportunity to try a different one.\n\n### `email_failed` (currently handled by `basic-500.xhtml`)\n\nThis happens when the e-mailing process _itself_ fails, e.g. when the\nscript can't connectd to the specified SMTP server.\n\n### `email_sent` (currently handled by `email-sent.xhtml`)\n\nThis is the confirmation page people see when Forget Passwords has\naccepted their e-mmail address and sent the link-containing e-mail.\n\n### `post_only` (currently handled by `post-405.xhtml`)\n\nThis error only occurs when somebody tries to access one of the two\ntargets (by default `/email-link` and `/logout`) by a request method\nother than `POST`, which should never happen outside of normal operation.\n\n## All Configuration Options\n\n### `host`\n\nThe host to listen on; defaults (as expected) to `localhost`.\n\n### `port`\n\nThe TCP port, default `10101`.\n\n### `state`\n\nThis is the configuration group involving the persistent state,\ni.e. the database.\n\n* `dsn` is the DSN (data source name), i.e., the connection string\n  that gets passed into Sequel.\n* `user` is the user name, which can be rolled into the DSN or\n  separated out.\n* Same goes for the `password`.\n* `options` are additional options that get passed directly to the\n  Sequel constructor.\n* `expiry` deals with the expiration times of the different kinds of\n  token, which are represented as ISO 8601 durations:\n  * `query` handles the expiry for the token in the link's query\n    string, defaulting to 10 minutes (`PT10M`)\n  * `cookie` handles the expiry for the cookie, defaulting to two weeks\n    (which gets refreshed by accessing the site; `P2W`)\n\n### `keys`\n\nThese are overrides for different keys in query strings and HTML\nforms.\n\n* `query` is the key for the URL query string component that contains\n  the nonce token; it defaults to `knock`.\n* `cookie` is the key for the cookie, which defaults to `forgetpw`.\n* `email` is the form key where the user's e-mail address is expected,\n  defaulting to `email`.\n* `logout` is the form key which would be set to something true-ish\n  (`true`, `yes`, `on`, `1`) for whether to log out all tokens or just\n  the current one, defaulting to `logout`.\n\n### `vars`\n\nThese are overrides for the names of the environment variables that\nare handed back to `mod_authnz_fcgi`, in case anything collides with\nan existing setup and needs to be called something else.\n\n* `user`is what gets retrieved and turned into `REMOTE_USER`,\n  defaulting to `FCGI_USER`.\n* `redirect` is what gets retrieved and turned into a `Location:`\n  header, defaulting to `FCGI_REDIRECT`.\n* `type` is what gets retrieved and turned into a `Content-Type:`\n  header, defaulting to `FCGI_CONTENT_TYPE`.\n\n### `targets`\n\nThese are (relative, but not necessarily) URLs to pages that perform\nspecific functions within the system, and have a stable location.\n\n* `login` is the target that accepts the `POST` request from the `401`\n  page and others, that sends the e-mail and issues a confirmation. It\n  defaults to `/email-link`. This resource is powered by Forget\n  Passwords and is used internally to configure the location of that\n  resource.\n* `logout` is the target that accepts the `POST` request to log\n  out. It (rather predictably) defaults to `/logout`. This location is\n  also handled by Forget Passwords.\n* `logout_one` is a _static_ (or other arbitrary) target (i.e., _not_\n  handled by Forget Passwords) that confirms the user has logged out\n  their current session. It defaults to `/logged-out`.\n* `logout_all` is another static target that confirms the user has\n  logged out of all devices.\n\n### `templates`\n\nThis is configuration for the various templates.\n\n* `path` is the template root, that defaults to `content/` under the\n  gem root.\n* `transform` is the URL of an XSLT stylesheet. Omitted if omitted.\n* `mapping` is a key-value structure of templates (listed above) to\n  file names, relative to `path`.\n\n### `email`\n\nThis is configuration for the e-mail sender.\n\n* `from` is the sender's address; it has no default.\n* `method` is how the sender will send mail, defaults to `sendmail`.\n* `options` is a key-value structure of additional options, e.g. for\n  when the `method` is `smtp`. It is fed directly into\n  `Mail::Message#delivery_method`.\n\n## Minimal Configuration\n\nThis is the absolute bare minimum configuration you will need supply\ndirectly. All other values have defaults:\n\n```yaml\nstate:\n  dsn: whatever://database\ntemplates:\n  # this is actually optional, but there is no default value.\n  transform: /transform.xsl\nemail:\n  from: robot@my.company\n  # additional SMTP configuration would go here, if applicable.\n```\n\n## Alternate Authentication Methods\n\nIt is possible to take the token in the cookie and feed it in as\neither a `Basic` authentication password or `Bearer` token. In the\ncase of `Basic`, the username is ignored. This enables `curl` or API\naccess, or other automated things like feed readers. There is\ncurrently no UI for this, but an \"app password\" management screen is\npotentially on the horizon.\n\n## Future Directions\n\nThis project began on something of a lark, with the intent to make a\nquick-and-easy passwordless authentication mechanism with zero UI, or\nrather, _I_ was the UI, manually e-mailing magic links to people. What\nI found when I put this scheme into production was that people balked\nbecause the experience was actually *too* seamless: a prospective\nclient insisted on believing a confidential proposal was just out on\nthe open internet for anybody to see, even though this was not the\ncase. As a result, I shelved this code for three years because I\ndidn't have time to do what was necessary to ameliorate it.\n\nWhat I had here was an _optics_ problem: the user needs to _see_ that\nthe content is protected, and logging in has to be a positive action;\nsomething that they _do_. This meant going from _zero_ UI, to rather\nquite a bit of it. As such, I anticipate what was once a one-off\nendeavour is now a significant Project™ that will have to be\nmaintained and expanded upon.\n\nWhat follows are some remarks around where things might go.\n\n### How about a test suite?\n\nMy philosophy around automated tests is that they are useful for\nensuring the behaviour of a piece of code without having to look\ndirectly at it. In my experience, getting little products like these\nto a functioning state is *system*-heavy, which has a crapload of\noverhead setting up a test regime, and furthermore the various\nconstituent parts either very obviously work or very obviously do\nnot. In other words, eyeballing it is a perfectly satisfactory quality\nassurance regime in the early stages of development (at least until it\ngets out of hand, which in this case it didn't). Now that it works (as\nof 2022-04-22), the focus can shift to keeping it that way.\n\n### How about expanding out the templates?\n\nLocalizing the templates is definitely a possibility, as well as\nmaking domain-specific overrides so a single Forget Passwords daemon\ncould handle multiple domains with tailor-fit responses for each. I am\nless sanguine about going hog-wild with the templates but I could see\nsome kind of future plug-in interface so people could use their\nfavourite flavour of templating engine.\n\n### Reconcile with OAuth\n\nLet's face it: this thing is 98% of what [OAuth](https://oauth.net/)\ndoes: it trades one token for another over a more-or-less secure side\nchannel. It could be made a heck of a lot simpler by just…wrapping\nOAuth.\n\nIndeed, bearer tokens would make for an _excellent_ cleavage plane for\n_segmented_ authentication: Method X to bearer token, then bearer\ntoken to `REMOTE_USER`. This means we could have multiple\nauthentication mechanisms (Forget Passwords, OAuth, X.509, Kerberos,\nboring old password, whatever) operating in the same space at once.\n\n### The really interesting thing is `mod_authnz_fcgi`\n\nAt least in principle. The actual module itself is a bit of a dog\n(although not un-groomable), but the fact that the FastCGI people had\nthe presence of mind to design modes for things other than content\n(there is a `FILTER` role as well) is actually quite interesting.\n\nThe vast majority of Web development happens exclusively inside what\ncan be termed a _content handler_. This is where all server-side\nplatforms and frameworks operate. In reality, Web servers (like Apache\nand `nginx`) have a number of phases, most of them happening _before_\nthe content handler, that can be addressed directly—provided you write\nyour module in C. What `mod_authnz_fcgi` does is tap the\n_authentication_ phase of Apache's request-handling loop and open it\nup to cheap scripts written in any language that speak FastCGI. This\nmeans that stand-alone modules like Forget Passwords can be used in\nconjunction with *any* downstream Web application framework or\ndevelopment strategy. Some additional observations:\n\n* **It doesn't have to be FastCGI**: There is really no reason in\n  principle why, with some creative reading of the HTTP protocol, that\n  this functionality couldn't be handled 100% by a stand-alone Web\n  service that the main workhorse server proxies to.\n* **This could be done for any phase**: Using said creative reading of\n  the HTTP protocol, this puts _any_ phase in the request-handling\n  process, for either Apache _or_ `nginx`, on the table, assuming the\n  appropriate module (in C) is written for each.\n\nSo, yeah, _big_ opportunity there to take modularity in Web\ndevelopment to the next level.\n\n## Installation\n\nYou know how to do this:\n\n    $ gem install forget-passwords\n\nOr, [download it off rubygems.org](https://rubygems.org/gems/forget-passwords).\n\n## Contributing\n\nBug reports and pull requests are welcome at\n[the GitHub repository](https://github.com/doriantaylor/rb-forget-passwords).\n\n## Copyright \u0026 License\n\n©2019-2022 [Dorian Taylor](https://doriantaylor.com/)\n\nThis software is provided under\nthe [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Frb-forget-passwords","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoriantaylor%2Frb-forget-passwords","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Frb-forget-passwords/lists"}