passwordless 0.7.0 → 0.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 +62 -24
- data/app/controllers/passwordless/sessions_controller.rb +16 -18
- data/app/models/passwordless/session.rb +21 -1
- data/config/locales/en.yml +2 -1
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +1 -0
- data/lib/passwordless.rb +9 -1
- data/lib/passwordless/controller_helpers.rb +89 -17
- data/lib/passwordless/errors.rb +11 -0
- data/lib/passwordless/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5f6c0006a3a1aa712922ef4c48b3afc4b5f1b722d9f37f624b367cb2e66ea895
|
4
|
+
data.tar.gz: 2c06ad86a47e11757a978fd879b7ab23e888589ecc1e734740886f148d1abb5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1cecfce2bcc7a6427cd037123aa4733c034126f2f55d47445d8439144db325aa00796cadfa9da40daa429a54073a7fc5c4e61d6f088fd2c8feb8fc653cd593fb
|
7
|
+
data.tar.gz: 673f6c504efd94c5f01f2bd073c427012e91d4c6bacf1a113cdc851b1bcb083183a7634ded733ac22f307e9ef4ccf4fbd2a30919dddce5ce0683e3c61b04380e
|
data/README.md
CHANGED
@@ -16,6 +16,7 @@ Add authentication to your Rails app without all the icky-ness of passwords.
|
|
16
16
|
* [Usage](#usage)
|
17
17
|
* [Getting the current user, restricting access, the usual](#getting-the-current-user-restricting-access-the-usual)
|
18
18
|
* [Providing your own templates](#providing-your-own-templates)
|
19
|
+
* [Claming tokens](#claiming-tokens)
|
19
20
|
* [Overrides](#overrides)
|
20
21
|
* [Registering new users](#registering-new-users)
|
21
22
|
* [Generating tokens](#generating-tokens)
|
@@ -43,7 +44,7 @@ $ bin/rails passwordless:install:migrations
|
|
43
44
|
|
44
45
|
## Usage
|
45
46
|
|
46
|
-
Passwordless creates a single model called `Passwordless::Session`. It doesn't come with its own `User` model, it expects you to create one
|
47
|
+
Passwordless creates a single model called `Passwordless::Session`. It doesn't come with its own `User` model, it expects you to create one:
|
47
48
|
|
48
49
|
```
|
49
50
|
$ bin/rails generate model User email
|
@@ -71,7 +72,7 @@ end
|
|
71
72
|
|
72
73
|
### Getting the current user, restricting access, the usual
|
73
74
|
|
74
|
-
Passwordless doesn't give you `current_user` automatically
|
75
|
+
Passwordless doesn't give you `current_user` automatically. Here's how you could add it:
|
75
76
|
|
76
77
|
```ruby
|
77
78
|
class ApplicationController < ActionController::Base
|
@@ -84,7 +85,7 @@ class ApplicationController < ActionController::Base
|
|
84
85
|
private
|
85
86
|
|
86
87
|
def current_user
|
87
|
-
@current_user ||=
|
88
|
+
@current_user ||= authenticate_by_session(User)
|
88
89
|
end
|
89
90
|
|
90
91
|
def require_user!
|
@@ -121,36 +122,20 @@ app/views/passwordless/mailer/magic_link.text.erb
|
|
121
122
|
|
122
123
|
See [the bundled views](https://github.com/mikker/passwordless/tree/master/app/views/passwordless).
|
123
124
|
|
124
|
-
### Overrides
|
125
|
-
|
126
|
-
By default `passwordless` uses the `passwordless_with` column you specify in the model to case insensitively fetch the resource during authentication. You can override this and provide your own customer fetcher by defining a class method `fetch_resource_for_passwordless` in your passwordless model. The method will be supplied with the downcased email and should return an `ActiveRecord` instance of the model.
|
127
|
-
|
128
|
-
Example time:
|
129
|
-
|
130
|
-
Let's say we would like to fetch the record and if it doesn't exist, create automatically.
|
131
|
-
|
132
|
-
```ruby
|
133
|
-
class User < ApplicationRecord
|
134
|
-
def self.fetch_resource_for_passwordless(email)
|
135
|
-
find_or_create_by(email: email)
|
136
|
-
end
|
137
|
-
end
|
138
|
-
```
|
139
|
-
|
140
125
|
### Registering new users
|
141
126
|
|
142
|
-
Because your `User` record is like any other record, you create one like you normally would. Passwordless provides a helper method
|
127
|
+
Because your `User` record is like any other record, you create one like you normally would. Passwordless provides a helper method to sign in the created user after it is saved – like so:
|
143
128
|
|
144
129
|
```ruby
|
145
130
|
class UsersController < ApplicationController
|
146
131
|
include Passwordless::ControllerHelpers # <-- This!
|
147
|
-
#
|
132
|
+
# (unless you already have it in your ApplicationController)
|
148
133
|
|
149
134
|
def create
|
150
135
|
@user = User.new user_params
|
151
136
|
|
152
137
|
if @user.save
|
153
|
-
sign_in @user # <--
|
138
|
+
sign_in @user # <-- This!
|
154
139
|
redirect_to @user, flash: {notice: 'Welcome!'}
|
155
140
|
else
|
156
141
|
render :new
|
@@ -163,7 +148,7 @@ end
|
|
163
148
|
|
164
149
|
### Generating tokens
|
165
150
|
|
166
|
-
By default Passwordless generates tokens using
|
151
|
+
By default Passwordless generates tokens using `SecureRandom.urlsafe_base64` but you can change that by setting `Passwordless.token_generator` to something else that responds to `call(session)` eg.:
|
167
152
|
|
168
153
|
```ruby
|
169
154
|
Passwordless.token_generator = -> (session) {
|
@@ -241,7 +226,7 @@ By default, magic link will send by email. You can customize this method. For ex
|
|
241
226
|
config/initializers/passwordless.rb
|
242
227
|
|
243
228
|
```
|
244
|
-
Passwordless.after_session_save = lambda do |session|
|
229
|
+
Passwordless.after_session_save = lambda do |session, request|
|
245
230
|
# Default behavior is
|
246
231
|
# Mailer.magic_link(session).deliver_now
|
247
232
|
|
@@ -252,6 +237,55 @@ end
|
|
252
237
|
|
253
238
|
You can access user model through authenticatable.
|
254
239
|
|
240
|
+
### Claiming tokens
|
241
|
+
|
242
|
+
Opt-in for marking tokens as `claimed` so they can only be used once.
|
243
|
+
|
244
|
+
config/initializers/passwordless.rb
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
# Default is `false`
|
248
|
+
Passwordless.restrict_token_reuse = true
|
249
|
+
```
|
250
|
+
|
251
|
+
#### Upgrading an existing Rails app
|
252
|
+
|
253
|
+
The simplest way to update your sessions table is with a single migration:
|
254
|
+
|
255
|
+
<details>
|
256
|
+
<summary>Example migration</summary>
|
257
|
+
|
258
|
+
```bash
|
259
|
+
bin/rails generate migration add_claimed_at_to_passwordless_sessions
|
260
|
+
```
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
class AddClaimedAtToPasswordlessSessions < ActiveRecord::Migration[5.2]
|
264
|
+
def change
|
265
|
+
add_column :passwordless_sessions, :claimed_at, :datetime
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
```
|
270
|
+
</details>
|
271
|
+
|
272
|
+
### Overrides
|
273
|
+
|
274
|
+
By default `passwordless` uses the `passwordless_with` column to _case insensitively_ fetch the resource.
|
275
|
+
|
276
|
+
You can override this and provide your own customer fetcher by defining a class method `fetch_resource_for_passwordless` in your passwordless model. The method will be called with the downcased email and should return an `ActiveRecord` instance of the model.
|
277
|
+
|
278
|
+
Example time:
|
279
|
+
|
280
|
+
Let's say we would like to fetch the record and if it doesn't exist, create automatically.
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
class User < ApplicationRecord
|
284
|
+
def self.fetch_resource_for_passwordless(email)
|
285
|
+
find_or_create_by(email: email)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
```
|
255
289
|
|
256
290
|
### E-mail security
|
257
291
|
|
@@ -261,6 +295,10 @@ But be aware that when everyone authenticates via emails you send, the way you s
|
|
261
295
|
|
262
296
|
Ideally you should set up your email provider to not log these mails. And be sure to turn on 2-factor auth if your provider supports it.
|
263
297
|
|
298
|
+
# Alternatives
|
299
|
+
|
300
|
+
- [OTP JWT](https://github.com/stas/otp-jwt) -- Passwordless JSON Web Tokens
|
301
|
+
|
264
302
|
# License
|
265
303
|
|
266
304
|
MIT
|
@@ -5,9 +5,6 @@ require "bcrypt"
|
|
5
5
|
module Passwordless
|
6
6
|
# Controller for managing Passwordless sessions
|
7
7
|
class SessionsController < ApplicationController
|
8
|
-
# Raise this exception when a session is expired.
|
9
|
-
class SessionTimedOutError < StandardError; end
|
10
|
-
|
11
8
|
include ControllerHelpers
|
12
9
|
|
13
10
|
# get '/sign_in'
|
@@ -26,7 +23,11 @@ module Passwordless
|
|
26
23
|
session = build_passwordless_session(find_authenticatable)
|
27
24
|
|
28
25
|
if session.save
|
29
|
-
Passwordless.after_session_save.
|
26
|
+
if Passwordless.after_session_save.arity == 2
|
27
|
+
Passwordless.after_session_save.call(session, request)
|
28
|
+
else
|
29
|
+
Passwordless.after_session_save.call(session)
|
30
|
+
end
|
30
31
|
end
|
31
32
|
|
32
33
|
render
|
@@ -42,20 +43,17 @@ module Passwordless
|
|
42
43
|
# Make it "slow" on purpose to make brute-force attacks more of a hassle
|
43
44
|
BCrypt::Password.create(params[:token])
|
44
45
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
sign_in session.authenticatable
|
46
|
+
destination =
|
47
|
+
Passwordless.redirect_back_after_sign_in &&
|
48
|
+
reset_passwordless_redirect_location!(User)
|
49
49
|
|
50
|
-
|
51
|
-
destination = reset_passwordless_redirect_location!(User)
|
50
|
+
sign_in passwordless_session
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
rescue SessionTimedOutError
|
52
|
+
redirect_to destination || main_app.root_path
|
53
|
+
rescue Errors::TokenAlreadyClaimedError
|
54
|
+
flash[:error] = I18n.t(".passwordless.sessions.create.token_claimed")
|
55
|
+
redirect_to main_app.root_path
|
56
|
+
rescue Errors::SessionTimedOutError
|
59
57
|
flash[:error] = I18n.t(".passwordless.sessions.create.session_expired")
|
60
58
|
redirect_to main_app.root_path
|
61
59
|
end
|
@@ -98,8 +96,8 @@ module Passwordless
|
|
98
96
|
end
|
99
97
|
end
|
100
98
|
|
101
|
-
def
|
102
|
-
Session.find_by!(
|
99
|
+
def passwordless_session
|
100
|
+
@passwordless_session ||= Session.find_by!(
|
103
101
|
authenticatable_type: authenticatable_classname,
|
104
102
|
token: params[:token]
|
105
103
|
)
|
@@ -18,10 +18,17 @@ module Passwordless
|
|
18
18
|
|
19
19
|
before_validation :set_defaults
|
20
20
|
|
21
|
-
scope :
|
21
|
+
scope :available, lambda {
|
22
22
|
where("timeout_at > ?", Time.current)
|
23
23
|
}
|
24
24
|
|
25
|
+
def self.valid
|
26
|
+
available
|
27
|
+
end
|
28
|
+
class << self
|
29
|
+
deprecate :valid, deprecator: SessionValidDeprecation
|
30
|
+
end
|
31
|
+
|
25
32
|
def expired?
|
26
33
|
expires_at <= Time.current
|
27
34
|
end
|
@@ -30,6 +37,19 @@ module Passwordless
|
|
30
37
|
timeout_at <= Time.current
|
31
38
|
end
|
32
39
|
|
40
|
+
def claim!
|
41
|
+
raise Errors::TokenAlreadyClaimedError if claimed?
|
42
|
+
touch(:claimed_at)
|
43
|
+
end
|
44
|
+
|
45
|
+
def claimed?
|
46
|
+
!!claimed_at
|
47
|
+
end
|
48
|
+
|
49
|
+
def available?
|
50
|
+
!timed_out? && !expired?
|
51
|
+
end
|
52
|
+
|
33
53
|
private
|
34
54
|
|
35
55
|
def set_defaults
|
data/config/locales/en.yml
CHANGED
@@ -5,8 +5,9 @@ en:
|
|
5
5
|
create:
|
6
6
|
session_expired: 'Your session has expired, please sign in again.'
|
7
7
|
email_sent_if_record_found: "If we found you in the system, we've sent you an email."
|
8
|
+
token_claimed: "This link has already been used, try requesting the link again"
|
8
9
|
new:
|
9
10
|
submit: 'Send magic link'
|
10
11
|
mailer:
|
11
|
-
subject: "Your magic link ✨
|
12
|
+
subject: "Your magic link ✨"
|
12
13
|
magic_link: "Here's your link: %{link}"
|
@@ -10,6 +10,7 @@ class CreatePasswordlessSessions < ActiveRecord::Migration[5.1]
|
|
10
10
|
)
|
11
11
|
t.datetime :timeout_at, null: false
|
12
12
|
t.datetime :expires_at, null: false
|
13
|
+
t.datetime :claimed_at
|
13
14
|
t.text :user_agent, null: false
|
14
15
|
t.string :remote_addr, null: false
|
15
16
|
t.string :token, null: false
|
data/lib/passwordless.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
4
|
+
require "passwordless/errors"
|
3
5
|
require "passwordless/engine"
|
4
6
|
require "passwordless/url_safe_base_64_generator"
|
5
7
|
|
@@ -7,11 +9,17 @@ require "passwordless/url_safe_base_64_generator"
|
|
7
9
|
module Passwordless
|
8
10
|
mattr_accessor(:default_from_address) { "CHANGE_ME@example.com" }
|
9
11
|
mattr_accessor(:token_generator) { UrlSafeBase64Generator.new }
|
12
|
+
mattr_accessor(:restrict_token_reuse) { false }
|
10
13
|
mattr_accessor(:redirect_back_after_sign_in) { true }
|
11
14
|
mattr_accessor(:mounted_as) { :configured_when_mounting_passwordless }
|
12
15
|
|
13
16
|
mattr_accessor(:expires_at) { lambda { 1.year.from_now } }
|
14
17
|
mattr_accessor(:timeout_at) { lambda { 1.hour.from_now } }
|
15
18
|
|
16
|
-
mattr_accessor(:after_session_save)
|
19
|
+
mattr_accessor(:after_session_save) do
|
20
|
+
lambda { |session, _request| Mailer.magic_link(session).deliver_now }
|
21
|
+
end
|
22
|
+
|
23
|
+
CookieDeprecation = ActiveSupport::Deprecation.new("0.9", "passwordless")
|
24
|
+
SessionValidDeprecation = ActiveSupport::Deprecation.new("0.9", "passwordless")
|
17
25
|
end
|
@@ -3,6 +3,12 @@
|
|
3
3
|
module Passwordless
|
4
4
|
# Helpers to work with Passwordless sessions from controllers
|
5
5
|
module ControllerHelpers
|
6
|
+
# Returns the {Passwordless::Session} (if set) from the session.
|
7
|
+
# @return [Session, nil]
|
8
|
+
def find_passwordless_session_for(authenticatable_class)
|
9
|
+
Passwordless::Session.find_by(id: session[session_key(authenticatable_class)])
|
10
|
+
end
|
11
|
+
|
6
12
|
# Build a new Passwordless::Session from an _authenticatable_ record.
|
7
13
|
# Set's `user_agent` and `remote_addr` from Rails' `request`.
|
8
14
|
# @param authenticatable [ActiveRecord::Base] Instance of an
|
@@ -17,6 +23,7 @@ module Passwordless
|
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
26
|
+
# @deprecated Use {ControllerHelpers#authenticate_by_session}
|
20
27
|
# Authenticate a record using cookies. Looks for a cookie corresponding to
|
21
28
|
# the _authenticatable_class_. If found try to find it in the database.
|
22
29
|
# @param authenticatable_class [ActiveRecord::Base] any Model connected to
|
@@ -27,54 +34,119 @@ module Passwordless
|
|
27
34
|
def authenticate_by_cookie(authenticatable_class)
|
28
35
|
key = cookie_name(authenticatable_class)
|
29
36
|
authenticatable_id = cookies.encrypted[key]
|
30
|
-
return unless authenticatable_id
|
31
37
|
|
32
|
-
authenticatable_class.find_by(id: authenticatable_id)
|
38
|
+
return authenticatable_class.find_by(id: authenticatable_id) if authenticatable_id
|
39
|
+
|
40
|
+
authenticate_by_session(authenticatable_class)
|
41
|
+
end
|
42
|
+
deprecate :authenticate_by_cookie, deprecator: CookieDeprecation
|
43
|
+
|
44
|
+
def upgrade_passwordless_cookie(authenticatable_class)
|
45
|
+
key = cookie_name(authenticatable_class)
|
46
|
+
|
47
|
+
return unless authenticatable_id = cookies.encrypted[key]
|
48
|
+
cookies.encrypted.permanent[key] = {value: nil}
|
49
|
+
cookies.delete(key)
|
50
|
+
|
51
|
+
return unless record = authenticatable_class.find_by(id: authenticatable_id)
|
52
|
+
new_session = build_passwordless_session(record).tap { |s| s.save! }
|
53
|
+
|
54
|
+
sign_in new_session
|
55
|
+
|
56
|
+
new_session.authenticatable
|
57
|
+
end
|
58
|
+
|
59
|
+
# Authenticate a record using the session. Looks for a session key corresponding to
|
60
|
+
# the _authenticatable_class_. If found try to find it in the database.
|
61
|
+
# @param authenticatable_class [ActiveRecord::Base] any Model connected to
|
62
|
+
# passwordless. (e.g - _User_ or _Admin_).
|
63
|
+
# @return [ActiveRecord::Base|nil] an instance of Model found by id stored
|
64
|
+
# in cookies.encrypted or nil if nothing is found.
|
65
|
+
# @see ModelHelpers#passwordless_with
|
66
|
+
def authenticate_by_session(authenticatable_class)
|
67
|
+
return unless find_passwordless_session_for(authenticatable_class)&.available?
|
68
|
+
find_passwordless_session_for(authenticatable_class).authenticatable
|
33
69
|
end
|
34
70
|
|
35
|
-
# Signs in
|
36
|
-
# @param authenticatable [
|
37
|
-
#
|
71
|
+
# Signs in session
|
72
|
+
# @param authenticatable [Passwordless::Session] Instance of {Passwordless::Session}
|
73
|
+
# to sign in
|
38
74
|
# @return [ActiveRecord::Base] the record that is passed in.
|
39
|
-
def sign_in(
|
40
|
-
|
41
|
-
|
42
|
-
|
75
|
+
def sign_in(record)
|
76
|
+
passwordless_session =
|
77
|
+
if record.is_a?(Passwordless::Session)
|
78
|
+
record
|
79
|
+
else
|
80
|
+
warn "Passwordless::ControllerHelpers#sign_in with authenticatable " \
|
81
|
+
"(`#{record.class}') is deprecated. Falling back to creating a " \
|
82
|
+
"new Passwordless::Session"
|
83
|
+
build_passwordless_session(record).tap { |s| s.save! }
|
84
|
+
end
|
85
|
+
|
86
|
+
passwordless_session.claim! if Passwordless.restrict_token_reuse
|
87
|
+
|
88
|
+
raise Passwordless::Errors::SessionTimedOutError if passwordless_session.timed_out?
|
89
|
+
|
90
|
+
key = session_key(passwordless_session.authenticatable_type)
|
91
|
+
session[key] = passwordless_session.id
|
92
|
+
|
93
|
+
if record.is_a?(Passwordless::Session)
|
94
|
+
passwordless_session
|
95
|
+
else
|
96
|
+
passwordless_session.authenticatable
|
97
|
+
end
|
43
98
|
end
|
44
99
|
|
45
|
-
# Signs out user by deleting
|
46
|
-
# @param (see #
|
100
|
+
# Signs out user by deleting the session key.
|
101
|
+
# @param (see #authenticate_by_session)
|
47
102
|
# @return [boolean] Always true
|
48
103
|
def sign_out(authenticatable_class)
|
104
|
+
# Deprecated - cookies
|
49
105
|
key = cookie_name(authenticatable_class)
|
50
106
|
cookies.encrypted.permanent[key] = {value: nil}
|
51
107
|
cookies.delete(key)
|
108
|
+
# /deprecated
|
109
|
+
|
110
|
+
reset_session
|
52
111
|
true
|
53
112
|
end
|
54
113
|
|
55
114
|
# Saves request.original_url as the redirect location for a
|
56
115
|
# passwordless Model.
|
57
|
-
# @param (see #
|
116
|
+
# @param (see #authenticate_by_session)
|
58
117
|
# @return [String] the redirect url that was just saved.
|
59
118
|
def save_passwordless_redirect_location!(authenticatable_class)
|
60
|
-
session[
|
119
|
+
session[redirect_session_key(authenticatable_class)] = request.original_url
|
61
120
|
end
|
62
121
|
|
63
122
|
# Resets the redirect_location to root_path by deleting the redirect_url
|
64
123
|
# from session.
|
65
|
-
# @param (see #
|
124
|
+
# @param (see #authenticate_by_session)
|
66
125
|
# @return [String, nil] the redirect url that was just deleted,
|
67
126
|
# or nil if no url found for given Model.
|
68
127
|
def reset_passwordless_redirect_location!(authenticatable_class)
|
69
|
-
session.delete
|
128
|
+
session.delete(redirect_session_key(authenticatable_class))
|
129
|
+
end
|
130
|
+
|
131
|
+
def session_key(authenticatable_class)
|
132
|
+
:"passwordless_session_id--#{authenticatable_class_parameterized(authenticatable_class)}"
|
70
133
|
end
|
71
134
|
|
72
135
|
private
|
73
136
|
|
74
|
-
def
|
75
|
-
|
137
|
+
def authenticatable_class_parameterized(authenticatable_class)
|
138
|
+
if authenticatable_class.is_a?(String)
|
139
|
+
authenticatable_class = authenticatable_class.constantize
|
140
|
+
end
|
141
|
+
|
142
|
+
authenticatable_class.base_class.to_s.parameterize
|
143
|
+
end
|
144
|
+
|
145
|
+
def redirect_session_key(authenticatable_class)
|
146
|
+
:"passwordless_prev_location--#{authenticatable_class_parameterized(authenticatable_class)}"
|
76
147
|
end
|
77
148
|
|
149
|
+
# Deprecated
|
78
150
|
def cookie_name(authenticatable_class)
|
79
151
|
:"#{authenticatable_class.base_class.to_s.underscore}_id"
|
80
152
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Passwordless
|
4
|
+
module Errors
|
5
|
+
# Raise this exception when a session is expired.
|
6
|
+
class SessionTimedOutError < StandardError; end
|
7
|
+
|
8
|
+
# Raise this exception when the token has been previously claimed
|
9
|
+
class TokenAlreadyClaimedError < StandardError; end
|
10
|
+
end
|
11
|
+
end
|
data/lib/passwordless/version.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: 0.
|
4
|
+
version: 0.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: 2019-
|
11
|
+
date: 2019-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 1.
|
47
|
+
version: 1.4.1
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 1.
|
54
|
+
version: 1.4.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: yard
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,6 +104,7 @@ files:
|
|
104
104
|
- lib/passwordless.rb
|
105
105
|
- lib/passwordless/controller_helpers.rb
|
106
106
|
- lib/passwordless/engine.rb
|
107
|
+
- lib/passwordless/errors.rb
|
107
108
|
- lib/passwordless/model_helpers.rb
|
108
109
|
- lib/passwordless/router_helpers.rb
|
109
110
|
- lib/passwordless/url_safe_base_64_generator.rb
|
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
128
|
- !ruby/object:Gem::Version
|
128
129
|
version: '0'
|
129
130
|
requirements: []
|
130
|
-
rubygems_version: 3.0.
|
131
|
+
rubygems_version: 3.0.4
|
131
132
|
signing_key:
|
132
133
|
specification_version: 4
|
133
134
|
summary: Add authentication to your app without all the ickyness of passwords.
|