forget-passwords 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,603 @@
1
+ # Forget Passwords: Log in the Lazy Way
2
+
3
+ **Forget Passwords** is a stand-alone Web authentication module that
4
+ replicates the "forgot-my-password" user flow, which will, on request,
5
+ e-mail a special link to an address on a predefined list, in lieu of
6
+ password-based authentication. This module makes use of [a
7
+ lesser-known feature of the FastCGI
8
+ protocol](https://fastcgi-archives.github.io/FastCGI_Specification.html#S6.3)
9
+ to do its work, and plugs into a receiving end such as [Apache's
10
+ `mod_authnz_fcgi`](https://httpd.apache.org/docs/2.4/mod/mod_authnz_fcgi.html).
11
+
12
+ ## Rationale & Goal
13
+
14
+ I have various Web properties littered around the internet in various
15
+ stages of development. Sometimes I want to show these properties to
16
+ people, but only _certain_ people—an example being both existing and
17
+ prospective clients.
18
+
19
+ Providing meaningful protection to a website almost always entails
20
+ some kind of authentication mechanism, and unless you go exotic, this
21
+ almost always means some kind of password. A single, shared password
22
+ is often inadequate protection because it can be leaked. This means
23
+ everybody to whom I would want to access one of these Web properties
24
+ would need their own password.
25
+
26
+ In this particular case, I am looking to support a relatively small
27
+ number of people, the total extent of whom is not necessarily known up
28
+ front. Under a password-based authentication regime, not only would I
29
+ be burdening clients and prospects with yet another set of
30
+ authentication credentials to manage, but I would also be burdening
31
+ _myself_ with the chore of fielding requests for new accounts* as
32
+ stragglers trickle in, as well as reset or retrieve lost passwords.
33
+
34
+ > \* I suppose I could set up UI for _them_ to create their own
35
+ > accounts and retrieve their lost passwords, but that would actually
36
+ > be more work than the solution I propose here, and the net effect
37
+ > would be to _further_ burden my users.
38
+
39
+ The solution to this problem stems from observing that the long tail
40
+ of Web authentication is serviced by the archetypal **forgot my
41
+ password** flow _itself_, so why force people to go through the extra
42
+ step of _creating_, and then _remembering_, a password?
43
+
44
+ The goal, then, is to create an authentication module that replicates
45
+ the forgot-my-password flow, provides about the same security as
46
+ `Basic` authentication over SSL, has a generic-enough user interface
47
+ to be merged seamlessly into any existing system, and otherwise
48
+ interacts minimally with any downstream access control mechanism or
49
+ Web application, including static content. An additional requirement
50
+ is that a mapping scheme (e.g. website domain to e-mail domain) can be
51
+ set up to provision identities (accounts) automatically.
52
+
53
+ ## How It Works
54
+
55
+ This module mainly operates as a FastCGI application in the
56
+ `AUTHORIZER` role, intended to plug into Apache's `mod_authnz_fcgi` or
57
+ any workalike, and configured in the server just as one would any
58
+ other authentication module. In addition to the authentication module,
59
+ there a couple of dynamic pages (namely login, logout) that need to be
60
+ surfaced as well. (Their locations are configurable.)
61
+
62
+ When unauthenticated users hit the protected area, they are met with a
63
+ form entreating them to enter their e-mail address. When they submit
64
+ the form, they are mailed a link with a random token attached to it
65
+ that provides the authentication. When the user visits the link, the
66
+ authentication handler trades the token attached to the URL (marking
67
+ it as used in the process) for a cookie. Responses to subsequent
68
+ requests then match the cookie to the user's e-mail address, and use
69
+ that to populate the `REMOTE_USER` field, which can then be picked up
70
+ by any downstream authorization handler or Web application.
71
+
72
+ Users of this system must be pre-authorized. The `forgetpw`
73
+ command-line tool that ships with this package has a verb for doing
74
+ this. Since the primary use case for this module is client extranets,
75
+ and it is customary that everybody at Widgets, Inc., will have a
76
+ `@widgets.biz` address, entire e-mail domains can be mapped to Web
77
+ domains. In other words, you can say "grant access to anybody at
78
+ widgets dot biz to `widgets-inc.extranet.my.company`", and then not
79
+ have to think subsequently about whether this or that person at the
80
+ company has access.
81
+
82
+ > Also, if necessary, specific addresses can be blocked.
83
+
84
+ This module uses an SQL database as its primary storage mechanism. It
85
+ has been tested with SQLite and PostgreSQL, though in principle it
86
+ should work with anything for which there is a
87
+ [Sequel](https://sequel.jeremyevans.net/) driver. Use of SQLite is
88
+ discouraged in production, due to its well-known inability to handle
89
+ concurrent transactions. There is a secondary storage in the module
90
+ itself for the little over a dozen user interface templates. The
91
+ locations of these (and thus their contents) can be overridden in a
92
+ configuration file, along with a number of other parameters, a few of
93
+ which (e.g., data source name, e-mail sender) are necessary for the
94
+ module to operate.
95
+
96
+ ## Usage
97
+
98
+ To start using ForgetPasswords, we'll assume you have done the necessary
99
+ setup on the server (below), as well as all the necessary setup for an
100
+ address to send e-mail from. After that, we'll need a database (this
101
+ example uses PostgreSQL; you can of course skip this step for SQLite):
102
+
103
+ $ createdb forgetpw
104
+
105
+ Now we initialize the configuration file and create the tables:
106
+
107
+ $ forgetpw -c ~/.forgetpw.yml init -d postgres:///forgetpw \
108
+ -f noreply@my.company
109
+
110
+ > Note: the `init` command uses the `-c` flag as the location to
111
+ > _write_ a _new_ configuration file, while all other commands use the
112
+ > flag as the source to _read_ from an existing one. The program
113
+ > otherwise looks for `forgetpw.yml` in the current directory.
114
+
115
+ Now we privilege some e-mail addresses:
116
+
117
+ $ forgetpw -c ~/.forgetpw.yml privilege \
118
+ -d widgets-inc.extranet.my.company widgets.biz some@other.person
119
+
120
+ Now, assuming we have configured the server, we start the daemon:
121
+
122
+ $ forgetpw -c ~/.forgetpw.yml fcgi
123
+ Running authenticator daemon on fcgi://localhost:10101/
124
+
125
+ > You can use `-z` to detach the process. Listener IP and port are of
126
+ > course also configurable.
127
+
128
+ ## Server Configuration
129
+
130
+ Currently the only known receptacle for this module is
131
+ `mod_authnz_fcgi`, which ships with Apache, though the interface is
132
+ standard (to the extent that FastCGI is a standard), and so in
133
+ principle it is usable in other systems. What follows is the
134
+ configuration for Apache 2.4.x or newer.
135
+
136
+ First, we need to declare the authenticator (here it can be called
137
+ anything but we are appropriately calling it `ForgetPasswords`) and where
138
+ it's listening:
139
+
140
+ ```apache
141
+ AuthnzFcgiDefineProvider authn ForgetPasswords fcgi://localhost:10101/
142
+ ```
143
+
144
+ > On Debian systems and their derivatives, this is in a separate file,
145
+ > `mods-available/authnz_fcgi.conf`. Note that you will also have to
146
+ > `a2enmod authnz_fcgi` or none of this configuration will work.
147
+
148
+ Then, in the virtual host (or main server configuration in lieu
149
+ thereof), we can use any standard configuration mechanism we want to
150
+ delineate the protected area. We invoke the module with the
151
+ `AuthnzFcgiCheckAuthnProvider` directive, and then tune it with
152
+ `Require`. `mod_authnz_fcgi` has a number of idiosyncrasies, one of
153
+ which is that it always must return a user, so we have to give it a
154
+ throwaway user like `nobody`, and then subsequently deny that user. (I
155
+ would consider this a design flaw in `mod_authnz_fcgi`.) The
156
+ expression `%{reqenv:FCGI_USER}` (where the slug `FCGI_USER` is
157
+ configurable on our side) is how the identity gets transmitted
158
+ upstream from ForgetPasswords to the server.
159
+
160
+ ```apache
161
+ <Location /protected>
162
+ # unfortunately mod_authnz_fcgi won't let you have a blank default user
163
+ AuthnzFcgiCheckAuthnProvider ForgetPasswords Authoritative On RequireBasicAuth Off UserExpr "%{reqenv:FCGI_USER}" DefaultUser nobody
164
+ <RequireAll>
165
+ Require valid-user
166
+ # that's fine, we just outlaw 'nobody'
167
+ Require not user nobody
168
+ </RequireAll>
169
+ </Location>
170
+ ```
171
+
172
+ Another idiosyncrasy of `mod_authnz_fcgi` is that while it uses the
173
+ `200` response code to indicate a success, the _actual_ response back
174
+ to the client necessarily has to come from the downstram content
175
+ handler. As such, any other information from a _successful_
176
+ authentication response needs to be smuggled out through environment
177
+ variables. Since ForgetPasswords performs a redirect to remove the
178
+ authentication token from the URL upon successful authentication, the
179
+ following `mod_rewrite` configuration needs to be in place to turn the
180
+ environment variable back into an actual redirect:
181
+
182
+ ```apache
183
+ RewriteCond %{QUERY_STRING} .+
184
+ RewriteCond %{ENV:FCGI_REDIRECT} .+
185
+ RewriteRule .* %{ENV:FCGI_REDIRECT} [R=307,L,QSD]
186
+ ```
187
+
188
+ > Note that `mod_rewrite` syntax is different from `ap_expr` syntax,
189
+ > and the prefix `ENV` is used in the expression instead of `reqenv`
190
+ > above. We also use `QSD` to remove the query string from the
191
+ > _currently-requested_ URI, and redirect with `307` to preserve the
192
+ > request method.
193
+ >
194
+ > Note as well that URL rewriting typically happens _before_
195
+ > authorization, so the environment variable will not yet be set
196
+ > unless you trick the `RewriteRule` to run in a later phase (e.g. by
197
+ > putting it in a `<Directory>` block or `.htaccess`).
198
+
199
+ We also need to account for _unsuccessful_ responses from the
200
+ authentication module, since certain headers (notably `Content-Type`)
201
+ are either getting overwritten by an unfortunate interaction with the
202
+ default error handler, or are otherwise not being transmitted (which
203
+ would be another bug in `mod_authnz_fcgi`).
204
+
205
+ ```apache
206
+ Header always set Content-Type "expr=%{resp:Variable-FCGI_CONTENT_TYPE}" "expr=%{resp:Variable-FCGI_CONTENT_TYPE} != ''"
207
+ Header always unset Variable-FCGI_CONTENT_TYPE
208
+ ```
209
+
210
+ Finally, the module provides two dynamic resources that need to be
211
+ mapped to content handlers; here we use `mod_proxy_fcgi` (remember to
212
+ enable it):
213
+
214
+ ```apache
215
+ ProxyPass /email-link fcgi://localhost:10101/email-link
216
+ ProxyPass /logout fcgi://localhost:10101/logout
217
+ ```
218
+
219
+ > An earlier design had these operations controlled exclusively by
220
+ > `POST` parameters on _any_ resource, and therefore these
221
+ > purpose-made resources were ostensibly not necessary. However, it
222
+ > turns out that `mod_authnz_fcgi` does not convey request body
223
+ > content to the downstream FastCGI script, causing the latter to
224
+ > crash with a protocol error. While the handling is less than
225
+ > delicate, this is actually a reasonable expectation, as request
226
+ > bodies are only read once off the wire and will thus be already
227
+ > consumed (whether or not they contain the fields to which ForgetPasswords
228
+ > is sensitive) when the content handler is invoked. (The way Apache
229
+ > handles the request body, it _can_ be duplicated and reinserted into
230
+ > the input stream, but that is a whole project unto itself.
231
+
232
+ ## Templates
233
+
234
+ ForgetPasswords has a number of UI states that are embedded in the gem. These
235
+ take the form of template files. The functionality of these templates
236
+ is currently at the absolute bare minimum required to do the job. The
237
+ templates are XHTML, with a basic placeholder substitution
238
+ functionality, which can take place either in processing instructions
239
+ (`<?var $WHATEVER?>`), or attribute values (`<elem
240
+ attr="$WHATEVER"/>`).
241
+
242
+ > I did this deliberately for a few reasons, the first being that the
243
+ > substitutions occur in a way such that the input _and_ the output
244
+ > always validates, i.e., there is no way to produce broken markup.
245
+ > The second is that this system neither needs nor merits a more
246
+ > sophisticated templating system. Each state is directly addressable;
247
+ > it gets its own template file. Anything that needs to be addressed
248
+ > in any individual state, save for a small number of substitutions in
249
+ > text nodes or attribute values, can be done by supplanting its file
250
+ > with a different one. Any styling or page composition needed to knit
251
+ > these states into their surroundings can be handled through an
252
+ > exterior mechanism, which I will endeavour to write up separately. I
253
+ > may consider different or additional template mechanisms
254
+ > (e.g. markdown, or any of the zillion non-standard template engines)
255
+ > at some point in the future.
256
+ >
257
+ > Note as well that the templates are not currently internationalized,
258
+ > but I am open to making them so if there is sufficient demand.
259
+
260
+ The configuration parameter `transform` under `templates` will cause
261
+ an `xml-stylesheet` processing instruction to be inserted into all
262
+ outgoing templates with the location of an XSLT stylesheet, enabling
263
+ arbitrary manipulations (and also the main reason why these templates
264
+ are XHTML and not regular HTML).
265
+
266
+ > **NOTE 2022-04-22** this `forgetpw extract` business is still
267
+ > under construction.
268
+
269
+ The default templates for all states are embedded in the gem
270
+ distribution, and can be overridden individually or en masse in the
271
+ configuration file by specifying the location of a supplanting file.
272
+ The command-line verb `forgetpw extract $DESTINATION` will extract
273
+ the full set of templates from the gem, and deposit copies of them
274
+ wherever you tell it to.
275
+
276
+ In addition to these templates that get piped out from arbitrary
277
+ locations, there are a couple resources, namely two logout states
278
+ (`/logged-out` for current device; `/logged-out-all` for all devices),
279
+ which can be completely static. Boilerplate for these states is
280
+ included in the distribution and can be retrieved by running
281
+ `forgetpw extract --static`. The URLs of these resources can
282
+ naturally be overridden in the configuration file.
283
+
284
+ > Out of an abundance of prudence I should also remark that to
285
+ > eliminate file extensions in static resources (at least in Apache),
286
+ > enable `mod_negotiation` and add `MultiViews` to any `Options`
287
+ > directive in scope.
288
+
289
+ What follows is the list of states, when they show up, and roughly
290
+ what they say. Most of them are specific error conditions:
291
+
292
+ ### `default_401` (currently handled by `basic-401.xhtml`)
293
+
294
+ This page is the one everybody sees when they are not logged in,
295
+ unless a more specific page is more appropriate. It explains that the
296
+ area is protected, and the way to get access (assuming that you're on
297
+ the list) is to enter your e-mail address. It then provides said
298
+ form. Note that the `action=` of the form **must** point to the
299
+ location of the `email-link` resource, and there must also be a hidden
300
+ form field by the name of `forward` that contains the current URL.
301
+
302
+ ### `default_404` (currently handled by `basic-404.xhtml`)
303
+
304
+ This resource should actually never be seen, as it currently only
305
+ arises when outside content-handling traffic is directed to locations
306
+ other than the two specified by ForgetPasswords.
307
+
308
+ ### `knock_bad` (currently handled by `basic-409.xhtml`)
309
+
310
+ This is shown when the knock-knock token attached to the URL is
311
+ malformed. It is an undifferentiated `409 Conflict` message, which
312
+ also includes a form like the one found in the default `401`.
313
+
314
+ ### `knock_not_found` (currently handled by `basic-409.xhtml`)
315
+
316
+ This is shown when the token is _not_ malformed, but also not present
317
+ in the database. (This is treated as a `403 Forbidden`, but the error
318
+ message is not meaningfully different from `409`, so it gets the same
319
+ message by default.)
320
+
321
+ ### `knock_expired` (currently handled by `nonce-expired.xhtml`)
322
+
323
+ Here, the token attached to the link sent out in the e-mail has
324
+ expired, i.e., the user has not claimed it in time (by default, 10
325
+ minutes). Again we notify themm, and show them the form to generate a
326
+ new one.
327
+
328
+ ### `cookie_bad` (currently handled by `basic-409.xhtml`)
329
+
330
+ This recapitulates the `knock_bad` scenario, but with a cookie.
331
+
332
+ ### `cookie_not_found` (currently handled by `basic-409.xhtml`)
333
+
334
+ The cookie equivalent of `knock_not_found`.
335
+
336
+ ### `cookie_expired` (currently handled by `cookie-expired.xhtml`)
337
+
338
+ This message is shown when the user has a cookie which has been
339
+ invalidated either by a logout or has been expired on the server
340
+ side. The user is given an opportunity to log back in.
341
+
342
+ ### `no_user` (currently handled by `not-on-list.xhtml`)
343
+
344
+ This message is returned when the cookie is valid but the user is not,
345
+ e.g. their access was revoked since they hit the site last. They are
346
+ given an opportunity to log back in.
347
+
348
+ ### `forward_bad` (currently handled by `uri-409.xhtml`)
349
+
350
+ This message is shown as the result of the user submitting their
351
+ e-mail when the forwarding address (URL), which should have been
352
+ included in the submitted form, is malformed (e.g. does not match the
353
+ domain). This is nominally a client error but it should never be
354
+ reached by normal operation. The only way a user would get here is a
355
+ misconfiguration on our part, or an attempt at abuse. We tell them to
356
+ go back and try again.
357
+
358
+ ### `email` (currently handled by `email.xhtml`)
359
+
360
+ This is the actual e-mail that gets sent to the user. Note that the
361
+ `<title>` gets turned into the subject, and the entire thing is also
362
+ stripped to plain text.
363
+
364
+ ### `email_bad` (currently handled by `email-409.xhtml`)
365
+
366
+ This status is returned after a user submits an e-mail address that is
367
+ syntactically bad.
368
+
369
+ ### `email_not_listed` (currently handled by `not-on-list.xhtml`)
370
+
371
+ This happens when the e-mail address is not on the permit list. Users
372
+ are given an opportunity to try a different one.
373
+
374
+ ### `email_failed` (currently handled by `basic-500.xhtml`)
375
+
376
+ This happens when the e-mailing process _itself_ fails, e.g. when the
377
+ script can't connectd to the specified SMTP server.
378
+
379
+ ### `email_sent` (currently handled by `email-sent.xhtml`)
380
+
381
+ This is the confirmation page people see when ForgetPasswords has accepted
382
+ their e-mmail address and sent the link-containing e-mail.
383
+
384
+ ### `post_only` (currently handled by `post-405.xhtml`)
385
+
386
+ This error only occurs when somebody tries to access one of the two
387
+ targets (by default `/email-link` and `/logout`) by a request method
388
+ other than `POST`, which should never happen outside of normal operation.
389
+
390
+ ## All Configuration Options
391
+
392
+ ### `host`
393
+
394
+ The host to listen on; defaults (as expected) to `localhost`.
395
+
396
+ ### `port`
397
+
398
+ The TCP port, default `10101`.
399
+
400
+ ### `state`
401
+
402
+ This is the configuration group involving the persistent state,
403
+ i.e. the database.
404
+
405
+ * `dsn` is the DSN (data source name), i.e., the connection string
406
+ that gets passed into Sequel.
407
+ * `user` is the user name, which can be rolled into the DSN or
408
+ separated out.
409
+ * Same goes for the `password`.
410
+ * `options` are additional options that get passed directly to the
411
+ Sequel constructor.
412
+ * `expiry` deals with the expiration times of the different kinds of
413
+ token, which are represented as ISO 8601 durations:
414
+ * `query` handles the expiry for the token in the link's query
415
+ string, defaulting to 10 minutes (`PT10M`)
416
+ * `cookie` handles the expiry for the cookie, defaulting to two weeks
417
+ (which gets refreshed by accessing the site; `P2W`)
418
+
419
+ ### `keys`
420
+
421
+ These are overrides for different keys in query strings and HTML
422
+ forms.
423
+
424
+ * `query` is the key for the URL query string component that contains
425
+ the nonce token; it defaults to `knock`.
426
+ * `cookie` is the key for the cookie, which defaults to `forgetpw`.
427
+ * `email` is the form key where the user's e-mail address is expected,
428
+ defaulting to `email`.
429
+ * `logout` is the form key which would be set to something true-ish
430
+ (`true`, `yes`, `on`, `1`) for whether to log out all tokens or just
431
+ the current one, defaulting to `logout`.
432
+
433
+ ### `vars`
434
+
435
+ These are overrides for the names of the environment variables that
436
+ are handed back to `mod_authnz_fcgi`, in case anything collides with
437
+ an existing setup and needs to be called something else.
438
+
439
+ * `user`is what gets retrieved and turned into `REMOTE_USER`,
440
+ defaulting to `FCGI_USER`.
441
+ * `redirect` is what gets retrieved and turned into a `Location:`
442
+ header, defaulting to `FCGI_REDIRECT`.
443
+ * `type` is what gets retrieved and turned into a `Content-Type:`
444
+ header, defaulting to `FCGI_CONTENT_TYPE`.
445
+
446
+ ### `targets`
447
+
448
+ These are (relative, but not necessarily) URLs to pages that perform
449
+ specific functions within the system, and have a stable location.
450
+
451
+ * `login` is the target that accepts the `POST` request from the `401`
452
+ page and others, that sends the e-mail and issues a confirmation. It
453
+ defaults to `/email-link`. This resource is powered by ForgetPasswords and
454
+ is used internally to configure the location of that resource.
455
+ * `logout` is the target that accepts the `POST` request to log
456
+ out. It (rather predictably) defaults to `/logout`. This location is
457
+ also handled by ForgetPasswords.
458
+ * `logout_one` is a _static_ (or other arbitrary) target (i.e., _not_
459
+ handled by ForgetPasswords) that confirms the user has logged out their
460
+ current session. It defaults to `/logged-out`.
461
+ * `logout_all` is another static target that confirms the user has
462
+ logged out of all devices.
463
+
464
+ ### `templates`
465
+
466
+ This is configuration for the various templates.
467
+
468
+ * `path` is the template root, that defaults to `content/` under the
469
+ gem root.
470
+ * `transform` is the URL of an XSLT stylesheet. Omitted if omitted.
471
+ * `mapping` is a key-value structure of templates (listed above) to
472
+ file names, relative to `path`.
473
+
474
+ ### `email`
475
+
476
+ This is configuration for the e-mail sender.
477
+
478
+ * `from` is the sender's address; it has no default.
479
+ * `method` is how the sender will send mail, defaults to `sendmail`.
480
+ * `options` is a key-value structure of additional options, e.g. for
481
+ when the `method` is `smtp`. It is fed directly into
482
+ `Mail::Message#delivery_method`.
483
+
484
+ ## Minimal Configuration
485
+
486
+ This is the absolute bare minimum configuration you will need supply
487
+ directly. All other values have defaults:
488
+
489
+ ```yaml
490
+ state:
491
+ dsn: whatever://database
492
+ templates:
493
+ # this is actually optional, but there is no default value.
494
+ transform: /transform.xsl
495
+ email:
496
+ from: robot@my.company
497
+ # additional SMTP configuration would go here, if applicable.
498
+ ```
499
+
500
+ ## Future Directions
501
+
502
+ This project began on something of a lark, with the intent to make a
503
+ quick-and-easy passwordless authentication mechanism with zero UI, or
504
+ rather, _I_ was the UI, manually e-mailing magic links to people. What
505
+ I found when I put this scheme into production was that people balked
506
+ because the experience was actually *too* seamless: a prospective
507
+ client insisted on believing a confidential proposal was just out on
508
+ the open internet for anybody to see, even though this was not the
509
+ case. As a result, I shelved this code for three years because I
510
+ didn't have time to do what was necessary to ameliorate it.
511
+
512
+ What I had here was an _optics_ problem: the user needs to _see_ that
513
+ the content is protected, and logging in has to be a positive action;
514
+ something that they _do_. This meant going from _zero_ UI, to rather
515
+ quite a bit of it. As such, I anticipate what was once a one-off
516
+ endeavour is now a significant Project™ that will have to be
517
+ maintained and expanded upon.
518
+
519
+ What follows are some remarks around where things might go.
520
+
521
+ ### How about a test suite?
522
+
523
+ My philosophy around automated tests is that they are useful for
524
+ ensuring the behaviour of a piece of code without having to look
525
+ directly at it. In my experience, getting little products like these
526
+ to a functioning state is *system*-heavy, which has a crapload of
527
+ overhead setting up a test regime, and furthermore the various
528
+ constituent parts either very obviously work or very obviously do
529
+ not. In other words, eyeballing it is a perfectly satisfactory quality
530
+ assurance regime in the early stages of development (at least until it
531
+ gets out of hand, which in this case it didn't). Now that it works (as
532
+ of 2022-04-22), the focus can shift to keeping it that way.
533
+
534
+ ### How about expanding out the templates?
535
+
536
+ Localizing the templates is definitely a possibility, as well as
537
+ making domain-specific overrides so a single ForgetPasswords daemon could
538
+ handle multiple domains with tailor-fit responses for each. I am less
539
+ sanguine about going hog-wild with the templates but I could see some
540
+ kind of future plug-in interface so people could use their favourite
541
+ flavour of templating engine.
542
+
543
+ ### Reconcile with OAuth
544
+
545
+ The authentication cookie used by ForgetPasswords bears a striking
546
+ resemblance to an [OAuth](https://oauth.net/) bearer token, such that
547
+ could actually _be_ (at least a proxy for) an OAuth bearer token.
548
+ Indeed, bearer tokens would make for an _excellent_ cleavage plane for
549
+ _segmented_ authentication: Method X to bearer token, then bearer
550
+ token to `REMOTE_USER`. This means we could have multiple
551
+ authentication mechanisms (ForgetPasswords, OAuth, X.509, Kerberos, boring
552
+ old password, whatever) operating in the same space at once.
553
+
554
+ ### The really interesting thing is `mod_authnz_fcgi`
555
+
556
+ At least in principle. The actual module itself is a bit of a dog
557
+ (although not un-groomable), but the fact that the FastCGI people had
558
+ the presence of mind to design modes for things other than content
559
+ (there is a `FILTER` role as well) is actually quite interesting.
560
+
561
+ The vast majority of Web development happens exclusively inside what
562
+ can be termed a _content handler_. This is where all server-side
563
+ platforms and frameworks operate. In reality, Web servers (like Apache
564
+ and `nginx`) have a number of phases, most of them happening _before_
565
+ the content handler, that can be addressed directly—provided you write
566
+ your module in C. What `mod_authnz_fcgi` does is tap the
567
+ _authentication_ phase of Apache's request-handling loop and open it
568
+ up to cheap scripts written in any language that speak FastCGI. This
569
+ means that stand-alone modules like ForgetPasswords can be used in
570
+ conjunction with *any* downstream Web application framework or
571
+ development strategy. Some additional observations:
572
+
573
+ * **It doesn't have to be FastCGI**: There is really no reason in
574
+ principle why, with some creative reading of the HTTP protocol, that
575
+ this functionality couldn't be handled 100% by a stand-alone Web
576
+ service that the main workhorse server proxies to.
577
+ * **This could be done for any phase**: Using said creative reading of
578
+ the HTTP protocol, this puts _any_ phase in the request-handling
579
+ process, for either Apache _or_ `nginx`, on the table, assuming the
580
+ appropriate module (in C) is written for each.
581
+
582
+ So, yeah, _big_ opportunity there to take modularity in Web
583
+ development to the next level.
584
+
585
+ ## Installation
586
+
587
+ You know how to do this:
588
+
589
+ $ gem install forget-passwords
590
+
591
+ Or, [download it off rubygems.org](https://rubygems.org/gems/forget-passwords).
592
+
593
+ ## Contributing
594
+
595
+ Bug reports and pull requests are welcome at
596
+ [the GitHub repository](https://github.com/doriantaylor/rb-forget-passwords).
597
+
598
+ ## Copyright & License
599
+
600
+ ©2019-2022 [Dorian Taylor](https://doriantaylor.com/)
601
+
602
+ This software is provided under
603
+ the [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec