forget-passwords 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
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