passwordless 1.5.0 → 1.7.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: 472a9d736665b6a1e1a71bf5175214f3acc414e0c918a33825c98a1d7ac02624
4
- data.tar.gz: 9e12898751e19fc7104c62826eaf7c9ee4e17a14a12efc66419eab4850dae720
3
+ metadata.gz: 7e765c341b12f05bb0156802dae574f7a0db31288181d4f71779a6ca49e48cd6
4
+ data.tar.gz: 746f09e1aefaea54d0f926c9bff27548ba1f8a4fdeeea0cfbd87ddc4b22ad65f
5
5
  SHA512:
6
- metadata.gz: 58b5bac3c0260d68a86fc7d9c3258457405a3670e0f8333959aa1ee54774ab68774728522d2e40c5d3b67a430f3b952af8903bb5d29b4ee12b6a291e3415fd17
7
- data.tar.gz: 42252595f95f48896cd08ec1be77a28657553ae287cc52213bed5cc343c38121bf12c8aba1db8ea002237ea8b5c1d706a8fc8f10dc7b9522c72c80218e4d35a5
6
+ metadata.gz: 8792ad81a4efbd5a071bb8a119d3e1ad057f2112e2bebbb888bbfdc9a84b95a77e4e2cb68ffb957fecb98f75cf05efb6c17cbd5c97b67a41f1372257eeb1cae9
7
+ data.tar.gz: 763dbbba33a8568976b5a5df2c28f0d157dcf9fc92504bf9ca016e85f8239c59b1e6ae48f4fa4e2e5ab2391aba9239ce956f7706c5437e168c27756a80fc9f33
data/README.md CHANGED
@@ -8,7 +8,24 @@
8
8
 
9
9
  Add authentication to your Rails app without all the icky-ness of passwords. _Magic link_ authentication, if you will. We call it _passwordless_.
10
10
 
11
- ---
11
+ - [Installation](#installation)
12
+ - [Upgrading](#upgrading)
13
+ - [Usage](#usage)
14
+ - [Getting the current user, restricting access, the usual](#getting-the-current-user-restricting-access-the-usual)
15
+ - [Providing your own templates](#providing-your-own-templates)
16
+ - [Registering new users](#registering-new-users)
17
+ - [URLs and links](#urls-and-links)
18
+ - [Route constraints](#route-constraints)
19
+ - [Configuration](#configuration)
20
+ - [Delivery method](#delivery-method)
21
+ - [Token generation](#token-generation)
22
+ - [Timeout and Expiry](#timeout-and-expiry)
23
+ - [Redirection after sign-in](#redirection-after-sign-in)
24
+ - [Looking up the user](#looking-up-the-user)
25
+ - [Test helpers](#test-helpers)
26
+ - [Security considerations](#security-considerations)
27
+ - [Alternatives](#alternatives)
28
+ - [License](#license)
12
29
 
13
30
  ## Installation
14
31
 
@@ -149,6 +166,35 @@ config.action_mailer.default_url_options = {host: "www.example.com"}
149
166
  routes.default_url_options[:host] ||= "www.example.com"
150
167
  ```
151
168
 
169
+ ### Route constraints
170
+
171
+ With [constraints](https://guides.rubyonrails.org/routing.html#request-based-constraints) you can restrict access to certain routes.
172
+ Passwordless provides `Passwordless::Constraint` and it's negative counterpart `Passwordless::NotConstraint` for this purpose.
173
+
174
+ To limit a route to only authenticated `User`s:
175
+
176
+ ```ruby
177
+ constraints Passwordless::Constraint.new(User) do
178
+ # ...
179
+ end
180
+ ```
181
+
182
+ The constraint takes a second `if:` argument, that expects a block and is passed the `authenticatable` record, (ie. `User`):
183
+
184
+ ```ruby
185
+ constraints Passwordless::Constraint.new(User, if: -> (user) { user.email.include?("john") }) do
186
+ # ...
187
+ end
188
+ ```
189
+
190
+ The negated version has the same API but with the opposite result, ie. ensuring authenticated user **don't** have access:
191
+
192
+ ```ruby
193
+ constraints Passwordless::NotConstraint.new(User) do
194
+ get("/no-users-allowed", to: "secrets#index")
195
+ end
196
+ ```
197
+
152
198
  ## Configuration
153
199
 
154
200
  To customize Passwordless, create a file `config/initializers/passwordless.rb`.
@@ -160,7 +206,7 @@ Passwordless.configure do |config|
160
206
  config.default_from_address = "CHANGE_ME@example.com"
161
207
  config.parent_controller = "ApplicationController"
162
208
  config.parent_mailer = "ActionMailer::Base"
163
- config.restrict_token_reuse = false # Can a token/link be used multiple times?
209
+ config.restrict_token_reuse = true # Can a token/link be used multiple times?
164
210
  config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
165
211
 
166
212
  config.expires_at = lambda { 1.year.from_now } # How long until a signed in session expires.
@@ -30,19 +30,20 @@ module Passwordless
30
30
  Passwordless.context.path_for(
31
31
  @session,
32
32
  id: @session.to_param,
33
- action: "show"
33
+ action: "show",
34
+ **default_url_options
34
35
  ),
35
36
  flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
36
37
  )
37
38
  else
38
- flash[:error] = I18n.t("passwordless.sessions.create.error")
39
+ flash.alert = I18n.t("passwordless.sessions.create.error")
39
40
  render(:new, status: :unprocessable_entity)
40
41
  end
41
42
 
42
43
  rescue ActiveRecord::RecordNotFound
43
44
  @session = Session.new
44
45
 
45
- flash[:error] = I18n.t("passwordless.sessions.create.not_found")
46
+ flash.alert = I18n.t("passwordless.sessions.create.not_found")
46
47
  render(:new, status: :not_found)
47
48
  end
48
49
 
@@ -155,15 +156,15 @@ module Passwordless
155
156
  **redirect_to_options
156
157
  )
157
158
  else
158
- flash[:error] = I18n.t("passwordless.sessions.errors.invalid_token")
159
+ flash.alert = I18n.t("passwordless.sessions.errors.invalid_token")
159
160
  render(status: :forbidden, action: "show")
160
161
  end
161
162
 
162
163
  rescue Errors::TokenAlreadyClaimedError
163
- flash[:error] = I18n.t("passwordless.sessions.errors.token_claimed")
164
+ flash.alert = I18n.t("passwordless.sessions.errors.token_claimed")
164
165
  redirect_to(passwordless_failure_redirect_path, status: :see_other, **redirect_to_options)
165
166
  rescue Errors::SessionTimedOutError
166
- flash[:error] = I18n.t("passwordless.sessions.errors.session_expired")
167
+ flash.alert = I18n.t("passwordless.sessions.errors.session_expired")
167
168
  redirect_to(passwordless_failure_redirect_path, status: :see_other, **redirect_to_options)
168
169
  end
169
170
 
@@ -9,7 +9,8 @@ module Passwordless
9
9
  belongs_to(
10
10
  :authenticatable,
11
11
  polymorphic: true,
12
- inverse_of: :passwordless_sessions
12
+ inverse_of: :passwordless_sessions,
13
+ autosave: false
13
14
  )
14
15
 
15
16
  validates(
@@ -1,5 +1,5 @@
1
1
  <%= form_with(model: @session, url: url_for(action: 'update'), scope: 'passwordless', method: 'patch', data: { turbo: false }) do |f| %>
2
- <%= f.label :token %>
2
+ <%= f.label :token, t(".token") %>
3
3
  <%= f.text_field :token,
4
4
  required: true,
5
5
  autofocus: true,
@@ -12,6 +12,7 @@ en:
12
12
  not_found: "We couldn't find a user with that email address"
13
13
  error: "An error occured"
14
14
  show:
15
+ token: "Token"
15
16
  confirm: "Confirm"
16
17
  errors:
17
18
  invalid_token: "Token is invalid"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class CreatePasswordlessSessions < ActiveRecord::Migration[5.1]
3
+ class CreatePasswordlessSessions < ActiveRecord::Migration[6.0]
4
4
  def change
5
5
  create_table(:passwordless_sessions) do |t|
6
6
  t.belongs_to(
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "passwordless/controller_helpers"
4
+
5
+ module Passwordless
6
+ # A class the constraint routes to authenticated records
7
+ class Constraint
8
+ include ControllerHelpers
9
+
10
+ attr_reader :authenticatable_type, :predicate, :session
11
+
12
+ # @param [Class] authenticatable_type Authenticatable class
13
+ # @option options [Proc] :if A lambda that takes an authenticatable and returns a boolean
14
+ def initialize(authenticatable_type, **options)
15
+ @authenticatable_type = authenticatable_type
16
+ # `if' is a keyword but so we do this instead of keyword arguments
17
+ @predicate = options.fetch(:if) { -> (_) { true } }
18
+ end
19
+
20
+ def matches?(request)
21
+ # used in authenticate_by_session
22
+ @session = request.session
23
+ authenticatable = authenticate_by_session(authenticatable_type)
24
+ !!(authenticatable && predicate.call(authenticatable))
25
+ end
26
+ end
27
+
28
+ # A class the constraint routes to NOT authenticated records
29
+ class ConstraintNot < Constraint
30
+ # @param [Class] authenticatable_type Authenticatable class
31
+ # @option options [Proc] :if A lambda that takes an authenticatable and returns a boolean
32
+ def initialize(authenticatable_type, **options)
33
+ super
34
+ end
35
+
36
+ def matches?(request)
37
+ !super
38
+ end
39
+ end
40
+ end
@@ -54,7 +54,7 @@ module Passwordless
54
54
  end
55
55
 
56
56
  # Signs in session
57
- # @param authenticatable [Passwordless::Session] Instance of {Passwordless::Session}
57
+ # @param passwordless_session [Passwordless::Session] Instance of {Passwordless::Session}
58
58
  # to sign in
59
59
  # @return [ActiveRecord::Base] the record that is passed in.
60
60
  def sign_in(passwordless_session)
@@ -11,9 +11,5 @@ module Passwordless
11
11
  ActionDispatch::Routing::Mapper.include RouterHelpers
12
12
  ActiveRecord::Base.extend ModelHelpers
13
13
  end
14
-
15
- config.before_initialize do |app|
16
- app.config.i18n.load_path += Dir[Engine.root.join("config", "locales", "*.yml")]
17
- end
18
14
  end
19
15
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Passwordless
4
4
  # :nodoc:
5
- VERSION = "1.5.0"
5
+ VERSION = "1.7.0"
6
6
  end
data/lib/passwordless.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "active_support"
4
4
  require "passwordless/config"
5
5
  require "passwordless/context"
6
+ require "passwordless/constraint"
6
7
  require "passwordless/errors"
7
8
  require "passwordless/engine"
8
9
  require "passwordless/token_digest"
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.5.0
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-11 00:00:00.000000000 Z
11
+ date: 2024-05-28 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/constraint.rb
65
66
  - lib/passwordless/context.rb
66
67
  - lib/passwordless/controller_helpers.rb
67
68
  - lib/passwordless/engine.rb
@@ -91,7 +92,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
92
  - !ruby/object:Gem::Version
92
93
  version: '0'
93
94
  requirements: []
94
- rubygems_version: 3.5.6
95
+ rubygems_version: 3.5.10
95
96
  signing_key:
96
97
  specification_version: 4
97
98
  summary: Add authentication to your app without all the ickyness of passwords.