forget-passwords 0.2.13 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4db3ce83ab06fe2e7ba9fbf453370f1182e8fc6e7f888e1edd2851e1eb4dfe1a
4
- data.tar.gz: '049d12c72b928352235e422c2c737a7c7827d5284a68054ea09f50c7d3a962d0'
3
+ metadata.gz: dacb56a002005cc14bb46809690604cccd905eee5c5fc8c5efb78557629611e6
4
+ data.tar.gz: 704b521acb64f33a934c8768e8bc1496a86ae55d27a0598f4172a7ddc6c8d0e0
5
5
  SHA512:
6
- metadata.gz: 2d3bc2353a0b41009cb7b537a67bc58210670095949afbe21ada7221912c3d6a91c580240d56803b159a32a73c3d4e1c756f7b34c288879be61f842740c6bf20
7
- data.tar.gz: 3c7771eb7709deeeca3c7617be519168d6387c619b5e0de0a61243905bb8a4f5a9a2667ab6a63ada192c962a426301e2d83400a5370dc212aaa7e2ee1f69eefb
6
+ metadata.gz: 1e79d40cbf1151f1a6fb056e17e90f022f65cf575e9eac4a2d1931b4c9c32723924a7e27f83ec893acbafa46710c6da68de3e2791005066ba74eea2a880f0f39
7
+ data.tar.gz: c306b865ed182f590338d26a7a41a165c38626519dbfc627b77fac4f8a271f7a9d83274a10ec8ae7629a9992407fe92524a4b2a9eb5e4c74ebf1b3b5907e67de
data/README.md CHANGED
@@ -578,9 +578,11 @@ favourite flavour of templating engine.
578
578
 
579
579
  ### Reconcile with OAuth
580
580
 
581
- The authentication cookie used by Forget Passwords bears a striking
582
- resemblance to an [OAuth](https://oauth.net/) bearer token, such that
583
- could actually _be_ (at least a proxy for) an OAuth bearer token.
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
+
584
586
  Indeed, bearer tokens would make for an _excellent_ cleavage plane for
585
587
  _segmented_ authentication: Method X to bearer token, then bearer
586
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.map do |k, v|
61
- name = normalize k
62
- template = v.is_a?(ForgetPasswords::Template) ? v :
63
- ForgetPasswords::Template.new(self, k, @path + v)
64
- [name, template]
65
- end.to_h
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
- def [] key
69
- @mapping[normalize key]
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
- @mapping[name] = path.is_a?(ForgetPasswords::Template) ? path :
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 = mapper
140
- @name = 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
@@ -1,3 +1,3 @@
1
1
  module ForgetPasswords
2
- VERSION = '0.2.13'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -311,7 +311,7 @@ module ForgetPasswords
311
311
  }
312
312
 
313
313
  # grab the template since we'll use it
314
- template = @templates[:email]
314
+ template = @templates[:email, req]
315
315
 
316
316
  # process the templates
317
317
  doc = template.process vars: vars
@@ -336,7 +336,7 @@ module ForgetPasswords
336
336
  uri = req_uri req
337
337
  resp = Rack::Response.new
338
338
  resp.status = status
339
- @templates[key].populate resp, req, vars, base: uri
339
+ @templates[key, req].populate resp, req, vars, base: uri
340
340
  resp.set_header "Variable-#{@vars[:type]}", resp.content_type
341
341
  raise ForgetPasswords::ErrorResponse, resp
342
342
  end
@@ -347,7 +347,7 @@ module ForgetPasswords
347
347
  uri = req_uri req
348
348
  resp = Rack::Response.new
349
349
  resp.status = 401
350
- @templates[:default_401].populate resp, req, {
350
+ @templates[:default_401, req].populate resp, req, {
351
351
  FORWARD: req_uri(req).to_s, LOGIN: @targets[:login] }, base: uri
352
352
  resp.set_header "Variable-#{@vars[:type]}", resp.content_type
353
353
  resp
@@ -386,7 +386,7 @@ module ForgetPasswords
386
386
  target != uri # (note this should always be true)
387
387
  resp.set_cookie @keys[:cookie], {
388
388
  value: token, secure: req.ssl?, httponly: true,
389
- domain: uri.host, path: ?/, same_site: :strict,
389
+ domain: uri.host, path: ?/, same_site: :lax, # strict is too strict
390
390
  expires: time_delta(@state.expiry[:cookie]),
391
391
  }
392
392
 
@@ -444,7 +444,7 @@ module ForgetPasswords
444
444
  # update the cookie expiration
445
445
  resp.set_cookie @keys[:cookie], {
446
446
  value: token, secure: req.ssl?, httponly: true,
447
- domain: uri.host, path: ?/, same_site: :strict,
447
+ domain: uri.host, path: ?/, same_site: :lax, # strict is too strict
448
448
  expires: time_delta(@state.expiry[:cookie], now),
449
449
  }
450
450
 
@@ -495,7 +495,7 @@ module ForgetPasswords
495
495
 
496
496
  # return 200 now because this is now a content handler
497
497
  resp.status = 200
498
- @templates[:email_sent].populate resp, req, {
498
+ @templates[:email_sent, req].populate resp, req, {
499
499
  FORWARD: forward.to_s, FROM: @email[:from].to_s, EMAIL: address.to_s },
500
500
  base: uri
501
501
  end
@@ -534,6 +534,7 @@ module ForgetPasswords
534
534
  def handle_auth req
535
535
  auth = req.get_header('Authorization') || req.env['HTTP_AUTHORIZATION']
536
536
  if auth and !auth.strip.empty?
537
+ # warn "has authorization header #{auth}"
537
538
  mech, *auth = auth.strip.split
538
539
  token = case mech.downcase
539
540
  when 'basic'
@@ -549,13 +550,15 @@ module ForgetPasswords
549
550
  default_401 req
550
551
  end
551
552
  elsif knock = req.GET[@keys[:query]]
552
- # check for a knock first; this overrides everything
553
+ # check for a knock; this overrides an existing cookie
554
+ # warn "has knock token #{knock}"
553
555
  handle_knock req, knock
554
556
  # elsif req.post?
555
557
  # # next check for a login/logout attempt
556
558
  # handle_post req
557
559
  elsif token = req.cookies[@keys[:cookie]]
558
560
  # next check for a cookie
561
+ # warn "has cookie #{token}"
559
562
  handle_cookie req, token
560
563
  else
561
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.2.13
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: 2022-12-08 00:00:00.000000000 Z
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.3.11
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: []