passwordless 1.6.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +88 -2
- data/app/controllers/passwordless/sessions_controller.rb +7 -0
- data/app/mailers/passwordless/mailer.rb +1 -1
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +1 -0
- data/lib/passwordless/config.rb +6 -0
- data/lib/passwordless/constraint.rb +40 -0
- data/lib/passwordless/controller_helpers.rb +1 -1
- data/lib/passwordless/engine.rb +0 -4
- data/lib/passwordless/version.rb +1 -1
- data/lib/passwordless.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b0e00cb666dc0018cb5789bf9f3b9186e7d107631365847374aa718d547f872e
|
4
|
+
data.tar.gz: dd7cf0cd6c95151f3987e827693f3bb78811b6eba50661a6c822a54e0788b908
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 664906f92b865e89863f5a886bacc119a1ccc1970eead36d7496a12545a8ce022553f30982bc4b8b852fee9b6cdb4580ae01e4c37d56e59ae52b026c836bbebc
|
7
|
+
data.tar.gz: b02a2dd05dae19dd283a455da70690c8086bfd6958f78986b7b8ac6442fa31bdc627362e12a59f19c9e51f1fa15a7e47fced64c09529ed3c2aa0454cd33bb84a
|
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,59 @@ 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
|
+
Note as well that `passwordless_for` accepts a custom controller. One possible application of this
|
170
|
+
is to add a `before_action` that redirects authenticated users from the sign-in routes, as in this example:
|
171
|
+
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
# config/routes.rb
|
175
|
+
passwordless_for :users, controller: "sessions"
|
176
|
+
```
|
177
|
+
|
178
|
+
```ruby
|
179
|
+
# app/controllers/sessions_controller.rb
|
180
|
+
|
181
|
+
class SessionsController < Passwordless::SessionsController
|
182
|
+
before_action :require_unauth!, only: %i[new show]
|
183
|
+
|
184
|
+
private
|
185
|
+
|
186
|
+
def require_unauth!
|
187
|
+
return unless current_user
|
188
|
+
redirect_to("/", notice: "You are already signed in.")
|
189
|
+
end
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
### Route constraints
|
194
|
+
|
195
|
+
With [constraints](https://guides.rubyonrails.org/routing.html#request-based-constraints) you can restrict access to certain routes.
|
196
|
+
Passwordless provides `Passwordless::Constraint` and it's negative counterpart `Passwordless::ConstraintNot` for this purpose.
|
197
|
+
|
198
|
+
To limit a route to only authenticated `User`s:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
constraints Passwordless::Constraint.new(User) do
|
202
|
+
# ...
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
206
|
+
The constraint takes a second `if:` argument, that expects a block and is passed the `authenticatable` record, (ie. `User`):
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
constraints Passwordless::Constraint.new(User, if: -> (user) { user.email.include?("john") }) do
|
210
|
+
# ...
|
211
|
+
end
|
212
|
+
```
|
213
|
+
|
214
|
+
The negated version has the same API but with the opposite result, ie. ensuring authenticated user **don't** have access:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
constraints Passwordless::ConstraintNot.new(User) do
|
218
|
+
get("/no-users-allowed", to: "secrets#index")
|
219
|
+
end
|
220
|
+
```
|
221
|
+
|
152
222
|
## Configuration
|
153
223
|
|
154
224
|
To customize Passwordless, create a file `config/initializers/passwordless.rb`.
|
@@ -160,7 +230,7 @@ Passwordless.configure do |config|
|
|
160
230
|
config.default_from_address = "CHANGE_ME@example.com"
|
161
231
|
config.parent_controller = "ApplicationController"
|
162
232
|
config.parent_mailer = "ActionMailer::Base"
|
163
|
-
config.restrict_token_reuse =
|
233
|
+
config.restrict_token_reuse = true # Can a token/link be used multiple times?
|
164
234
|
config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
|
165
235
|
|
166
236
|
config.expires_at = lambda { 1.year.from_now } # How long until a signed in session expires.
|
@@ -173,6 +243,8 @@ Passwordless.configure do |config|
|
|
173
243
|
config.sign_out_redirect_path = '/' # After a user signs out
|
174
244
|
|
175
245
|
config.paranoid = false # Display email sent notice even when the resource is not found.
|
246
|
+
|
247
|
+
config.after_session_confirm = ->(request, session) {} # Called after a session is confirmed.
|
176
248
|
end
|
177
249
|
```
|
178
250
|
|
@@ -194,6 +266,20 @@ Passwordless.configure do |config|
|
|
194
266
|
end
|
195
267
|
```
|
196
268
|
|
269
|
+
## After Session Confirm Hook
|
270
|
+
|
271
|
+
An `after_session_confirm` hook is called after a successful session confirmation – in other words: after a user signs in successfully.
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
Passwordless.configure do |config|
|
275
|
+
config.after_session_confirm = ->(session, request) {
|
276
|
+
user = session.authenticatable
|
277
|
+
user.update!(
|
278
|
+
email_verified: true.
|
279
|
+
last_login_ip: request.remote_ip
|
280
|
+
)
|
281
|
+
}
|
282
|
+
end
|
197
283
|
### Token generation
|
198
284
|
|
199
285
|
By default Passwordless generates short, 6-digit, alpha numeric tokens. You can change the generator using `Passwordless.config.token_generator` to something else that responds to `call(session)` eg.:
|
@@ -150,6 +150,7 @@ module Passwordless
|
|
150
150
|
def authenticate_and_sign_in(session, token)
|
151
151
|
if session.authenticate(token)
|
152
152
|
sign_in(session)
|
153
|
+
call_after_session_confirm(session, request)
|
153
154
|
redirect_to(
|
154
155
|
passwordless_success_redirect_path(session.authenticatable),
|
155
156
|
status: :see_other,
|
@@ -188,6 +189,12 @@ module Passwordless
|
|
188
189
|
end
|
189
190
|
end
|
190
191
|
|
192
|
+
def call_after_session_confirm(session, request)
|
193
|
+
return unless Passwordless.config.after_session_confirm.respond_to?(:call)
|
194
|
+
|
195
|
+
Passwordless.config.after_session_confirm.call(session, request)
|
196
|
+
end
|
197
|
+
|
191
198
|
def find_authenticatable
|
192
199
|
if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
|
193
200
|
authenticatable_class.fetch_resource_for_passwordless(normalized_email_param)
|
data/lib/passwordless/config.rb
CHANGED
@@ -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
|
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)
|
data/lib/passwordless/engine.rb
CHANGED
@@ -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
|
data/lib/passwordless/version.rb
CHANGED
data/lib/passwordless.rb
CHANGED
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.
|
4
|
+
version: 1.8.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-
|
11
|
+
date: 2024-10-25 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.
|
95
|
+
rubygems_version: 3.5.22
|
95
96
|
signing_key:
|
96
97
|
specification_version: 4
|
97
98
|
summary: Add authentication to your app without all the ickyness of passwords.
|