passwordless 1.0.1 → 1.1.1

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: 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