forget-passwords 0.2.12 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +14 -3
- data/lib/forget-passwords/template.rb +64 -13
- data/lib/forget-passwords/version.rb +1 -1
- data/lib/forget-passwords.rb +21 -14
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dacb56a002005cc14bb46809690604cccd905eee5c5fc8c5efb78557629611e6
|
|
4
|
+
data.tar.gz: 704b521acb64f33a934c8768e8bc1496a86ae55d27a0598f4172a7ddc6c8d0e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1e79d40cbf1151f1a6fb056e17e90f022f65cf575e9eac4a2d1931b4c9c32723924a7e27f83ec893acbafa46710c6da68de3e2791005066ba74eea2a880f0f39
|
|
7
|
+
data.tar.gz: c306b865ed182f590338d26a7a41a165c38626519dbfc627b77fac4f8a271f7a9d83274a10ec8ae7629a9992407fe92524a4b2a9eb5e4c74ebf1b3b5907e67de
|
data/README.md
CHANGED
|
@@ -524,6 +524,15 @@ email:
|
|
|
524
524
|
# additional SMTP configuration would go here, if applicable.
|
|
525
525
|
```
|
|
526
526
|
|
|
527
|
+
## Alternate Authentication Methods
|
|
528
|
+
|
|
529
|
+
It is possible to take the token in the cookie and feed it in as
|
|
530
|
+
either a `Basic` authentication password or `Bearer` token. In the
|
|
531
|
+
case of `Basic`, the username is ignored. This enables `curl` or API
|
|
532
|
+
access, or other automated things like feed readers. There is
|
|
533
|
+
currently no UI for this, but an "app password" management screen is
|
|
534
|
+
potentially on the horizon.
|
|
535
|
+
|
|
527
536
|
## Future Directions
|
|
528
537
|
|
|
529
538
|
This project began on something of a lark, with the intent to make a
|
|
@@ -569,9 +578,11 @@ favourite flavour of templating engine.
|
|
|
569
578
|
|
|
570
579
|
### Reconcile with OAuth
|
|
571
580
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
581
|
+
Let's face it: this thing is 98% of what [OAuth](https://oauth.net/)
|
|
582
|
+
does: it trades one token for another over a more-or-less secure side
|
|
583
|
+
channel. It could be made a heck of a lot simpler by just…wrapping
|
|
584
|
+
OAuth.
|
|
585
|
+
|
|
575
586
|
Indeed, bearer tokens would make for an _excellent_ cleavage plane for
|
|
576
587
|
_segmented_ authentication: Method X to bearer token, then bearer
|
|
577
588
|
token to `REMOTE_USER`. This means we could have multiple
|
|
@@ -4,6 +4,7 @@ require 'xml-mixup'
|
|
|
4
4
|
require 'http-negotiate'
|
|
5
5
|
require 'forget-passwords/types'
|
|
6
6
|
require 'uri'
|
|
7
|
+
require 'time'
|
|
7
8
|
|
|
8
9
|
module ForgetPasswords
|
|
9
10
|
|
|
@@ -18,6 +19,8 @@ module ForgetPasswords
|
|
|
18
19
|
DEFAULT_PATH = (
|
|
19
20
|
Pathname(__FILE__).parent + '../../content').expand_path.freeze
|
|
20
21
|
|
|
22
|
+
# this is a default document root subpath
|
|
23
|
+
DEFAULT_DOCROOT = '.forgetpw'.freeze
|
|
21
24
|
|
|
22
25
|
# Normalize the input to symbol `:like_this`.
|
|
23
26
|
#
|
|
@@ -57,21 +60,67 @@ module ForgetPasswords
|
|
|
57
60
|
@path = Pathname(path).expand_path
|
|
58
61
|
@base = base
|
|
59
62
|
@transform = transform
|
|
60
|
-
@mapping = mapping
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
@mapping = mapping
|
|
64
|
+
@templates = {
|
|
65
|
+
@path => mapping.map do |k, v|
|
|
66
|
+
name = normalize k
|
|
67
|
+
template = v.is_a?(ForgetPasswords::Template) ? v :
|
|
68
|
+
ForgetPasswords::Template.new(self, k, @path + v)
|
|
69
|
+
[name, template]
|
|
70
|
+
end.to_h
|
|
71
|
+
}
|
|
66
72
|
end
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
74
|
+
# Fetch the appropriate template, optionally relative to a given root.
|
|
75
|
+
#
|
|
76
|
+
# @param key [Symbol, #to_sym] the template key.
|
|
77
|
+
# @param root [nil, String] an optional document root.
|
|
78
|
+
#
|
|
79
|
+
# @return [nil, ForgetPasswords::Template] a template
|
|
80
|
+
#
|
|
81
|
+
def [] key, root = nil
|
|
82
|
+
key = normalize key
|
|
83
|
+
# bail early if we don't know the key
|
|
84
|
+
return unless @mapping[key]
|
|
85
|
+
|
|
86
|
+
# obtain optional root
|
|
87
|
+
root = if root
|
|
88
|
+
r = root.respond_to?(:env) ? root.env['DOCUMENT_ROOT'] : root
|
|
89
|
+
r = (Pathname(r) + DEFAULT_DOCROOT).expand_path
|
|
90
|
+
r.readable? ? r : nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if root
|
|
94
|
+
# get the full file path
|
|
95
|
+
fp = root + @mapping[key]
|
|
96
|
+
if fp.readable?
|
|
97
|
+
mt = fp.mtime
|
|
98
|
+
rootmap = @templates[root] ||= {}
|
|
99
|
+
template = rootmap[key]
|
|
100
|
+
|
|
101
|
+
# congratulations, you found it
|
|
102
|
+
return template if template and mt <= template.modified
|
|
103
|
+
|
|
104
|
+
# XXX this could explode obvs
|
|
105
|
+
begin
|
|
106
|
+
template = ForgetPasswords::Template.new(self, key, fp, mt)
|
|
107
|
+
rootmap[key] = template
|
|
108
|
+
return template
|
|
109
|
+
rescue
|
|
110
|
+
# XXX duhh what do we do here
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# otherwise just return the default
|
|
117
|
+
@templates[@path][key]
|
|
70
118
|
end
|
|
71
119
|
|
|
72
120
|
def []= key, path
|
|
73
121
|
name = normalize key
|
|
74
|
-
|
|
122
|
+
# XXX do something less dumb here
|
|
123
|
+
@templates[@path][name] = path.is_a?(ForgetPasswords::Template) ? path :
|
|
75
124
|
ForgetPasswords::Template.new(self, key, @path + path)
|
|
76
125
|
end
|
|
77
126
|
|
|
@@ -132,12 +181,13 @@ module ForgetPasswords
|
|
|
132
181
|
|
|
133
182
|
public
|
|
134
183
|
|
|
135
|
-
attr_reader :name, :doc, :mapper
|
|
184
|
+
attr_reader :name, :doc, :mapper, :modified
|
|
136
185
|
|
|
137
|
-
def initialize mapper, name, content
|
|
186
|
+
def initialize mapper, name, content, modified = Time.now
|
|
138
187
|
# boring members
|
|
139
|
-
@mapper
|
|
140
|
-
@name
|
|
188
|
+
@mapper = mapper
|
|
189
|
+
@name = name
|
|
190
|
+
@modified = modified
|
|
141
191
|
|
|
142
192
|
# resolve content
|
|
143
193
|
@doc = case content
|
|
@@ -253,6 +303,7 @@ module ForgetPasswords
|
|
|
253
303
|
# @return [Rack::Response] the response object, updated in place
|
|
254
304
|
#
|
|
255
305
|
def populate resp, headers = {}, vars = {}, base: nil
|
|
306
|
+
|
|
256
307
|
if (body, type = serialize(
|
|
257
308
|
process(vars: vars, base: base), headers, full: true))
|
|
258
309
|
#resp.length = body.bytesize # not sure if necessary
|
data/lib/forget-passwords.rb
CHANGED
|
@@ -11,6 +11,7 @@ require 'rack'
|
|
|
11
11
|
require 'rack/request'
|
|
12
12
|
require 'rack/response'
|
|
13
13
|
|
|
14
|
+
require 'base64'
|
|
14
15
|
require 'mail'
|
|
15
16
|
|
|
16
17
|
module ForgetPasswords
|
|
@@ -310,7 +311,7 @@ module ForgetPasswords
|
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
# grab the template since we'll use it
|
|
313
|
-
template = @templates[:email]
|
|
314
|
+
template = @templates[:email, req]
|
|
314
315
|
|
|
315
316
|
# process the templates
|
|
316
317
|
doc = template.process vars: vars
|
|
@@ -335,7 +336,7 @@ module ForgetPasswords
|
|
|
335
336
|
uri = req_uri req
|
|
336
337
|
resp = Rack::Response.new
|
|
337
338
|
resp.status = status
|
|
338
|
-
@templates[key].populate resp, req, vars, base: uri
|
|
339
|
+
@templates[key, req].populate resp, req, vars, base: uri
|
|
339
340
|
resp.set_header "Variable-#{@vars[:type]}", resp.content_type
|
|
340
341
|
raise ForgetPasswords::ErrorResponse, resp
|
|
341
342
|
end
|
|
@@ -346,7 +347,7 @@ module ForgetPasswords
|
|
|
346
347
|
uri = req_uri req
|
|
347
348
|
resp = Rack::Response.new
|
|
348
349
|
resp.status = 401
|
|
349
|
-
@templates[:default_401].populate resp, req, {
|
|
350
|
+
@templates[:default_401, req].populate resp, req, {
|
|
350
351
|
FORWARD: req_uri(req).to_s, LOGIN: @targets[:login] }, base: uri
|
|
351
352
|
resp.set_header "Variable-#{@vars[:type]}", resp.content_type
|
|
352
353
|
resp
|
|
@@ -385,7 +386,7 @@ module ForgetPasswords
|
|
|
385
386
|
target != uri # (note this should always be true)
|
|
386
387
|
resp.set_cookie @keys[:cookie], {
|
|
387
388
|
value: token, secure: req.ssl?, httponly: true,
|
|
388
|
-
domain: uri.host, path: ?/, same_site: :strict
|
|
389
|
+
domain: uri.host, path: ?/, same_site: :lax, # strict is too strict
|
|
389
390
|
expires: time_delta(@state.expiry[:cookie]),
|
|
390
391
|
}
|
|
391
392
|
|
|
@@ -443,7 +444,7 @@ module ForgetPasswords
|
|
|
443
444
|
# update the cookie expiration
|
|
444
445
|
resp.set_cookie @keys[:cookie], {
|
|
445
446
|
value: token, secure: req.ssl?, httponly: true,
|
|
446
|
-
domain: uri.host, path: ?/, same_site: :strict
|
|
447
|
+
domain: uri.host, path: ?/, same_site: :lax, # strict is too strict
|
|
447
448
|
expires: time_delta(@state.expiry[:cookie], now),
|
|
448
449
|
}
|
|
449
450
|
|
|
@@ -494,7 +495,7 @@ module ForgetPasswords
|
|
|
494
495
|
|
|
495
496
|
# return 200 now because this is now a content handler
|
|
496
497
|
resp.status = 200
|
|
497
|
-
@templates[:email_sent].populate resp, req, {
|
|
498
|
+
@templates[:email_sent, req].populate resp, req, {
|
|
498
499
|
FORWARD: forward.to_s, FROM: @email[:from].to_s, EMAIL: address.to_s },
|
|
499
500
|
base: uri
|
|
500
501
|
end
|
|
@@ -531,27 +532,33 @@ module ForgetPasswords
|
|
|
531
532
|
end
|
|
532
533
|
|
|
533
534
|
def handle_auth req
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
535
|
+
auth = req.get_header('Authorization') || req.env['HTTP_AUTHORIZATION']
|
|
536
|
+
if auth and !auth.strip.empty?
|
|
537
|
+
# warn "has authorization header #{auth}"
|
|
538
|
+
mech, *auth = auth.strip.split
|
|
539
|
+
token = case mech.downcase
|
|
540
|
+
when 'basic'
|
|
541
|
+
# can't trust/use rack here
|
|
542
|
+
Base64.decode64(auth.first || '').split(?:, 2).last
|
|
543
|
+
when 'bearer'
|
|
544
|
+
auth.first
|
|
540
545
|
end
|
|
546
|
+
|
|
541
547
|
if token
|
|
542
548
|
handle_token req, token
|
|
543
549
|
else
|
|
544
|
-
# XXX one day maybe this can be more descriptive??
|
|
545
550
|
default_401 req
|
|
546
551
|
end
|
|
547
552
|
elsif knock = req.GET[@keys[:query]]
|
|
548
|
-
# check for a knock
|
|
553
|
+
# check for a knock; this overrides an existing cookie
|
|
554
|
+
# warn "has knock token #{knock}"
|
|
549
555
|
handle_knock req, knock
|
|
550
556
|
# elsif req.post?
|
|
551
557
|
# # next check for a login/logout attempt
|
|
552
558
|
# handle_post req
|
|
553
559
|
elsif token = req.cookies[@keys[:cookie]]
|
|
554
560
|
# next check for a cookie
|
|
561
|
+
# warn "has cookie #{token}"
|
|
555
562
|
handle_cookie req, token
|
|
556
563
|
else
|
|
557
564
|
default_401 req
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forget-passwords
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dorian Taylor
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2023-03-09 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -290,7 +290,7 @@ homepage: https://github.com/doriantaylor/rb-forget-passwords
|
|
|
290
290
|
licenses:
|
|
291
291
|
- Apache-2.0
|
|
292
292
|
metadata: {}
|
|
293
|
-
post_install_message:
|
|
293
|
+
post_install_message:
|
|
294
294
|
rdoc_options: []
|
|
295
295
|
require_paths:
|
|
296
296
|
- lib
|
|
@@ -305,8 +305,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
305
305
|
- !ruby/object:Gem::Version
|
|
306
306
|
version: '0'
|
|
307
307
|
requirements: []
|
|
308
|
-
rubygems_version: 3.
|
|
309
|
-
signing_key:
|
|
308
|
+
rubygems_version: 3.1.2
|
|
309
|
+
signing_key:
|
|
310
310
|
specification_version: 4
|
|
311
311
|
summary: Web authentication module for the extremely lazy
|
|
312
312
|
test_files: []
|