passwordless 1.0.1 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52e22eadd44664d2cf56ea239705a56a897ee1d3cb995344e6b9f25c1f984d26
4
- data.tar.gz: '02787fc37322c5bb248574ea1a59e384d6a1fdc9de42e680b7bfb2711bed2983'
3
+ metadata.gz: 399b4c9ace35d6780f2f22cf50c47c2717ead7c9b6b999d64120b7e574b465ec
4
+ data.tar.gz: 5e7e35e3eab146d5e93c7588c49552fb3f46d27ed27e183ba7367e755ecfccfd
5
5
  SHA512:
6
- metadata.gz: 6c70a17498a9690a146bb69b8bcaf37f1e1755ee0eef3b82ce8751ca7d29a98aa21e144bca49e8f8ac9ab2ad78d4e3ea1808508be5b31cbb2f6ec295de6a4aeb
7
- data.tar.gz: 6a4085ab2ac296b9967c26e77af43ddf0f77cf2fad716623be4400089f17f27adabd8fb09223ff055692ac84e6385944da4a2e15597b490648b5ec0da0240972
6
+ metadata.gz: d9fe89a70ba2f35cc417f03f5ca23a9a1bb1bc967d3c80828aba6a60a643e8de2d880f7bb038e1f17b60e80f250117df5ad41df1a15c96c9dcfa8317767ddd2f
7
+ data.tar.gz: 315a2b802c1b21cf08ad61003c6b15d2d8eedfcfb7c440c58fbc7991bd73c254261dc3e7535d25469a01522e8476cb0ca9fdede7af3d15f0eaed14ff1b80ce2a
data/README.md CHANGED
@@ -16,7 +16,7 @@ Add to your bundle and copy over the migrations:
16
16
 
17
17
  ```sh
18
18
  $ bundle add passwordless
19
- $ bin/rails passwordless:install:migrations
19
+ $ bin/rails passwordless_engine:install:migrations
20
20
  ```
21
21
 
22
22
  ### Upgrading
@@ -122,7 +122,7 @@ class UsersController < ApplicationController
122
122
  @user = User.new(user_params)
123
123
 
124
124
  if @user.save
125
- sign_in(build_passwordless_session(@user)) # <-- This!
125
+ sign_in(create_passwordless_session(@user)) # <-- This!
126
126
  redirect_to(@user, flash: { notice: 'Welcome!' })
127
127
  else
128
128
  render(:new)
@@ -37,7 +37,11 @@ module Passwordless
37
37
  end
38
38
 
39
39
  redirect_to(
40
- url_for(id: @session.id, action: "show"),
40
+ Passwordless.context.path_for(
41
+ @session,
42
+ id: @session.to_param,
43
+ action: "show"
44
+ ),
41
45
  flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
42
46
  )
43
47
  else
@@ -54,7 +58,7 @@ module Passwordless
54
58
  # Shows the form for confirming a Session record.
55
59
  # renders sessions/show.html.erb.
56
60
  def show
57
- @session = find_session
61
+ @session = passwordless_session
58
62
  end
59
63
 
60
64
  # patch "/:resource/sign_in/:id"
@@ -66,7 +70,7 @@ module Passwordless
66
70
  # @see ControllerHelpers#sign_in
67
71
  # @see ControllerHelpers#save_passwordless_redirect_location!
68
72
  def update
69
- @session = find_session
73
+ @session = passwordless_session
70
74
 
71
75
  artificially_slow_down_brute_force_attacks(passwordless_session_params[:token])
72
76
 
@@ -86,7 +90,7 @@ module Passwordless
86
90
  # safe. We don't want to sign in the user in that case.
87
91
  return head(:ok) if request.head?
88
92
 
89
- @session = find_session
93
+ @session = passwordless_session
90
94
 
91
95
  artificially_slow_down_brute_force_attacks(params[:token])
92
96
 
@@ -98,7 +102,12 @@ module Passwordless
98
102
  # @see ControllerHelpers#sign_out
99
103
  def destroy
100
104
  sign_out(authenticatable_class)
101
- redirect_to(passwordless_sign_out_redirect_path, Passwordless.config.redirect_to_response_options.dup)
105
+
106
+ redirect_to(
107
+ passwordless_sign_out_redirect_path,
108
+ notice: I18n.t("passwordless.sessions.destroy.signed_out"),
109
+ **redirect_to_options
110
+ )
102
111
  end
103
112
 
104
113
  protected
@@ -161,10 +170,6 @@ module Passwordless
161
170
  authenticatable_type.constantize
162
171
  end
163
172
 
164
- def find_session
165
- Session.find_by!(id: params[:id], authenticatable_type: authenticatable_type)
166
- end
167
-
168
173
  def find_authenticatable
169
174
  email = passwordless_session_params[email_field].downcase.strip
170
175
 
@@ -196,7 +201,7 @@ module Passwordless
196
201
 
197
202
  def passwordless_session
198
203
  @passwordless_session ||= Session.find_by!(
199
- id: params[:id],
204
+ identifier: params[:id],
200
205
  authenticatable_type: authenticatable_type
201
206
  )
202
207
  end
@@ -12,7 +12,14 @@ module Passwordless
12
12
  # is still in memory (optional)
13
13
  def sign_in(session, token = nil)
14
14
  @token = token || session.token
15
- @magic_link = send(:"confirm_#{session.authenticatable_type.tableize}_sign_in_url", session, token)
15
+
16
+ @magic_link = Passwordless.context.url_for(
17
+ session,
18
+ action: "confirm",
19
+ id: session.to_param,
20
+ token: @token
21
+ )
22
+
16
23
  email_field = session.authenticatable.class.passwordless_email_field
17
24
 
18
25
  mail(
@@ -61,6 +61,10 @@ module Passwordless
61
61
  !expired?
62
62
  end
63
63
 
64
+ def to_param
65
+ identifier
66
+ end
67
+
64
68
  private
65
69
 
66
70
  def token_digest_available?(token_digest)
@@ -68,6 +72,7 @@ module Passwordless
68
72
  end
69
73
 
70
74
  def set_defaults
75
+ self.identifier = SecureRandom.uuid
71
76
  self.expires_at ||= Passwordless.config.expires_at.call
72
77
  self.timeout_at ||= Passwordless.config.timeout_at.call
73
78
 
@@ -3,9 +3,10 @@
3
3
  <%= f.label email_field_name,
4
4
  t("passwordless.sessions.new.email.label"),
5
5
  for: "passwordless_#{email_field}" %>
6
- <%= text_field_tag email_field_name,
6
+ <%= email_field_tag email_field_name,
7
7
  params.fetch(email_field_name, nil),
8
8
  required: true,
9
+ autofocus: true,
9
10
  placeholder: t("passwordless.sessions.new.email.placeholder") %>
10
11
  <%= f.submit t("passwordless.sessions.new.submit") %>
11
12
  <% end %>
@@ -17,6 +17,8 @@ en:
17
17
  invalid_token: "Token is invalid"
18
18
  session_expired: "Your session has expired, please sign in again."
19
19
  token_claimed: "This link has already been used, try requesting the link again"
20
+ destroy:
21
+ signed_out: "Signed out successfully"
20
22
  mailer:
21
23
  sign_in:
22
24
  subject: "Signing in ✨"
@@ -13,6 +13,7 @@ class CreatePasswordlessSessions < ActiveRecord::Migration[5.1]
13
13
  t.datetime(:expires_at, null: false)
14
14
  t.datetime(:claimed_at)
15
15
  t.string(:token_digest, null: false)
16
+ t.string(:identifier, null: false, index: {unique: true}, length: 36)
16
17
 
17
18
  t.timestamps
18
19
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Passwordless
4
+ class Resource
5
+ def initialize(resource, controller:)
6
+ @resource = resource
7
+ @authenticatable = resource.to_s.singularize.to_sym
8
+ @controller = controller
9
+ end
10
+
11
+ attr_reader :resource, :authenticatable, :controller
12
+
13
+ def defaults
14
+ @defaults ||= {
15
+ authenticatable: authenticatable,
16
+ resource: resource,
17
+ controller: controller
18
+ }
19
+ end
20
+ end
21
+
22
+ class Context
23
+ def initialize
24
+ @resources = {}
25
+ end
26
+
27
+ attr_reader :resources
28
+
29
+ def resource_for(session_or_authenticatable)
30
+ if session_or_authenticatable.is_a?(Session)
31
+ session_or_authenticatable = session_or_authenticatable.authenticatable.model_name.to_s.tableize.to_sym
32
+ end
33
+
34
+ resources[session_or_authenticatable.to_sym]
35
+ end
36
+
37
+ def url_for(session_or_authenticatable, **options)
38
+ unless (resource = resource_for(session_or_authenticatable))
39
+ raise ArgumentError, "No resource registered for #{session_or_authenticatable}"
40
+ end
41
+
42
+ Rails.application.routes.url_helpers.url_for(
43
+ resource.defaults.merge(options)
44
+ )
45
+ end
46
+
47
+ def path_for(session_or_authenticatable, **options)
48
+ url_for(session_or_authenticatable, only_path: true, **options)
49
+ end
50
+ end
51
+ end
@@ -23,17 +23,13 @@ module Passwordless
23
23
  # (Default: 'passwordless/sessions')
24
24
  def passwordless_for(resource, at: :na, as: :na, controller: "passwordless/sessions")
25
25
  at == :na && at = "/#{resource.to_s}"
26
- as == :na && as = "#{resource.to_s}_"
26
+ as == :na && as = resource.to_s
27
27
 
28
- plural = resource.to_s
29
- singular = plural.singularize
28
+ as = as.to_s + "_" unless !as || as.to_s.end_with?("_")
30
29
 
31
- defaults = {
32
- authenticatable: singular,
33
- resource: resource,
34
- }
30
+ pwless_resource = Passwordless.add_resource(resource, controller: controller)
35
31
 
36
- scope(defaults: defaults) do
32
+ scope(defaults: pwless_resource.defaults) do
37
33
  get("#{at}/sign_in", to: "#{controller}#new", as: :"#{as}sign_in")
38
34
  post("#{at}/sign_in", to: "#{controller}#create")
39
35
  get("#{at}/sign_in/:id", to: "#{controller}#show", as: :"verify_#{as}sign_in")
@@ -1,35 +1,65 @@
1
1
  module Passwordless
2
2
  module TestHelpers
3
- module TestCase
4
- def passwordless_sign_out
5
- delete(Passwordless::Engine.routes.url_helpers.sign_out_path)
3
+ module ControllerTestCase
4
+ class H
5
+ extend ControllerHelpers
6
+ end
7
+
8
+ def passwordless_sign_out(cls = nil)
9
+ cls ||= "User".constantize
10
+ @request.session.delete(H.session_key(cls))
11
+ end
12
+
13
+ def passwordless_sign_in(resource)
14
+ session = Passwordless::Session.create!(authenticatable: resource)
15
+ @request.session[H.session_key(resource.class)] = session.id
16
+ end
17
+ end
18
+
19
+ module RequestTestCase
20
+ def passwordless_sign_out(cls = nil)
21
+ cls ||= "User".constantize
22
+ resource = cls.model_name.to_s.tableize
23
+
24
+ dest = Passwordless.context.path_for(resource, action: "destroy")
25
+ delete(dest)
26
+
6
27
  follow_redirect!
7
28
  end
8
29
 
9
30
  def passwordless_sign_in(resource)
10
31
  session = Passwordless::Session.create!(authenticatable: resource)
11
- magic_link = Passwordless::Engine.routes.url_helpers.send(
12
- :"confirm_#{session.authenticatable_type.tableize}_sign_in_url",
32
+
33
+ magic_link = Passwordless.context.path_for(
13
34
  session,
14
- session.token
35
+ action: "confirm",
36
+ id: session.to_param,
37
+ token: session.token
15
38
  )
39
+
16
40
  get(magic_link)
17
41
  follow_redirect!
18
42
  end
19
43
  end
20
44
 
21
45
  module SystemTestCase
22
- def passwordless_sign_out
23
- visit(Passwordless::Engine.routes.url_helpers.sign_out_path)
46
+ def passwordless_sign_out(cls = nil)
47
+ cls ||= "User".constantize
48
+ resource = cls.model_name.to_s.tableize
49
+
50
+ visit(Passwordless.context.url_for(resource, action: "destroy"))
24
51
  end
25
52
 
26
53
  def passwordless_sign_in(resource)
27
54
  session = Passwordless::Session.create!(authenticatable: resource)
28
- magic_link = Passwordless::Engine.routes.url_helpers.send(
29
- :"confirm_#{session.authenticatable_type.tableize}_sign_in_url",
55
+
56
+ magic_link = Passwordless.context.url_for(
30
57
  session,
31
- session.token
58
+ action: "confirm",
59
+ id: session.to_param,
60
+ token: session.token
32
61
  )
62
+
33
63
  visit(magic_link)
34
64
  end
35
65
  end
@@ -37,7 +67,7 @@ module Passwordless
37
67
  end
38
68
 
39
69
  if defined?(ActiveSupport::TestCase)
40
- ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers::TestCase)
70
+ ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers::ControllerTestCase)
41
71
  end
42
72
 
43
73
  if defined?(ActionDispatch::SystemTestCase)
@@ -46,8 +76,8 @@ end
46
76
 
47
77
  if defined?(RSpec)
48
78
  RSpec.configure do |config|
49
- config.include(::Passwordless::TestHelpers::TestCase, type: :request)
50
- config.include(::Passwordless::TestHelpers::TestCase, type: :controller)
79
+ config.include(::Passwordless::TestHelpers::ControllerTestCase, type: :controller)
80
+ config.include(::Passwordless::TestHelpers::RequestTestCase, type: :request)
51
81
  config.include(::Passwordless::TestHelpers::SystemTestCase, type: :system)
52
82
  end
53
83
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Passwordless
4
4
  # :nodoc:
5
- VERSION = "1.0.1"
5
+ VERSION = "1.1.1"
6
6
  end
data/lib/passwordless.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support"
4
4
  require "passwordless/config"
5
+ require "passwordless/context"
5
6
  require "passwordless/errors"
6
7
  require "passwordless/engine"
7
8
  require "passwordless/token_digest"
@@ -10,6 +11,14 @@ require "passwordless/token_digest"
10
11
  module Passwordless
11
12
  extend Configurable
12
13
 
14
+ def self.context
15
+ @context ||= Context.new
16
+ end
17
+
18
+ def self.add_resource(resource, controller:, **defaults)
19
+ context.resources[resource] = Resource.new(resource, controller: controller)
20
+ end
21
+
13
22
  def self.digest(token)
14
23
  TokenDigest.new(token).digest
15
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passwordless
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-22 00:00:00.000000000 Z
11
+ date: 2023-11-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -62,6 +62,7 @@ files:
62
62
  - lib/generators/passwordless/views_generator.rb
63
63
  - lib/passwordless.rb
64
64
  - lib/passwordless/config.rb
65
+ - lib/passwordless/context.rb
65
66
  - lib/passwordless/controller_helpers.rb
66
67
  - lib/passwordless/engine.rb
67
68
  - lib/passwordless/errors.rb