passwordless 0.4.0 → 0.5.0
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 +4 -4
- data/README.md +22 -5
- data/Rakefile +6 -11
- data/app/controllers/passwordless/application_controller.rb +6 -0
- data/app/controllers/passwordless/sessions_controller.rb +53 -20
- data/app/mailers/passwordless/mailer.rb +9 -1
- data/app/models/passwordless/application_record.rb +3 -0
- data/app/models/passwordless/session.rb +11 -2
- data/app/views/passwordless/mailer/magic_link.text.erb +1 -2
- data/app/views/passwordless/sessions/create.html.erb +1 -1
- data/app/views/passwordless/sessions/new.html.erb +1 -1
- data/config/locales/en.yml +12 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +2 -0
- data/lib/passwordless/controller_helpers.rb +49 -4
- data/lib/passwordless/engine.rb +8 -0
- data/lib/passwordless/model_helpers.rb +8 -0
- data/lib/passwordless/router_helpers.rb +16 -0
- data/lib/passwordless/url_safe_base_64_generator.rb +8 -0
- data/lib/passwordless/version.rb +3 -1
- data/lib/passwordless.rb +4 -3
- metadata +36 -8
- data/lib/tasks/passwordless_tasks.rake +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d79c41fb0ddc3797af28166832ab336c3c3c2347c9336be03e04441554347772
|
|
4
|
+
data.tar.gz: 0f24f481eff8e766e7546e21c53fa196fa61421037a6543c58c7bca81e78899e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c3a7038ffd29a1cb8218905fb974fd3d59b45b11190e3df9e125e8fc5c200638d7f7cf1c954d3a17b256594c4429a144fcd5259a210fa3fed5b13a05551af6c4
|
|
7
|
+
data.tar.gz: 3a1506b2dfba720a3ca82df7ce992fb74d450691dd31e8b250d961a7c882680fda2fb36674a6d474fa7114dc922839448547ade16b6c1c57348c08b1c9c8de25
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<br />
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[](https://travis-ci.org/mikker/passwordless) [](https://rubygems.org/gems/passwordless) [](https://codecov.io/gh/mikker/passwordless)
|
|
8
8
|
|
|
9
9
|
Add authentication to your Rails app without all the icky-ness of passwords.
|
|
10
10
|
|
|
@@ -19,6 +19,7 @@ Add authentication to your Rails app without all the icky-ness of passwords.
|
|
|
19
19
|
* [Registering new users](#registering-new-users)
|
|
20
20
|
* [Generating tokens](#generating-tokens)
|
|
21
21
|
* [Redirecting back after sign-in](#redirecting-back-after-sign-in)
|
|
22
|
+
* [URLs and links](#urls-and-links)
|
|
22
23
|
* [License](#license)
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
@@ -48,7 +49,9 @@ Then specify which field on your `User` record is the email field with:
|
|
|
48
49
|
|
|
49
50
|
```ruby
|
|
50
51
|
class User < ApplicationRecord
|
|
51
|
-
|
|
52
|
+
validates :email, presence: true, uniqueness: { case_sensitive: false }
|
|
53
|
+
|
|
54
|
+
passwordless_with :email # <-- here!
|
|
52
55
|
end
|
|
53
56
|
```
|
|
54
57
|
|
|
@@ -87,11 +90,11 @@ class ApplicationController < ActionController::Base
|
|
|
87
90
|
end
|
|
88
91
|
```
|
|
89
92
|
|
|
90
|
-
Et voil
|
|
93
|
+
Et voilà:
|
|
91
94
|
|
|
92
95
|
```ruby
|
|
93
96
|
class VerySecretThingsController < ApplicationController
|
|
94
|
-
|
|
97
|
+
before_action :require_user!
|
|
95
98
|
|
|
96
99
|
def index
|
|
97
100
|
@things = current_user.very_secret_things
|
|
@@ -162,7 +165,7 @@ class ApplicationController < ActionController::Base
|
|
|
162
165
|
|
|
163
166
|
def require_user!
|
|
164
167
|
return if current_user
|
|
165
|
-
save_passwordless_redirect_location! # <-- here we go!
|
|
168
|
+
save_passwordless_redirect_location!(User) # <-- here we go!
|
|
166
169
|
redirect_to root_path, flash: {error: 'You are not worthy!'}
|
|
167
170
|
end
|
|
168
171
|
end
|
|
@@ -170,6 +173,20 @@ end
|
|
|
170
173
|
|
|
171
174
|
This can be turned off with `Passwordless.redirect_back_after_sign_in = false` but if you just don't save the previous destination, you'll be fine.
|
|
172
175
|
|
|
176
|
+
### URLs and links
|
|
177
|
+
|
|
178
|
+
By default, Passwordless uses the resource name given to `passwordless_for` to generate its routes and helpers.
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
passwordless_for :users
|
|
182
|
+
# <%= users.sign_in_path %> # => /users/sign_in
|
|
183
|
+
|
|
184
|
+
passwordless_for :users, at: '/', as: :auth
|
|
185
|
+
# <%= auth.sign_in_path %> # => /sign_in
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Also be sure to [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
|
|
189
|
+
|
|
173
190
|
# License
|
|
174
191
|
|
|
175
192
|
MIT
|
data/Rakefile
CHANGED
|
@@ -1,22 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
begin
|
|
2
4
|
require 'bundler/setup'
|
|
3
5
|
rescue LoadError
|
|
4
6
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
5
7
|
end
|
|
6
8
|
|
|
7
|
-
require '
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
11
|
-
rdoc.title = 'Passwordless'
|
|
12
|
-
rdoc.options << '--line-numbers'
|
|
13
|
-
rdoc.rdoc_files.include('README.md')
|
|
14
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
15
|
-
end
|
|
9
|
+
require 'yard'
|
|
10
|
+
YARD::Rake::YardocTask.new
|
|
11
|
+
task docs: :yard
|
|
16
12
|
|
|
17
|
-
APP_RAKEFILE = File.expand_path(
|
|
13
|
+
APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
|
|
18
14
|
load 'rails/tasks/engine.rake'
|
|
19
|
-
|
|
20
15
|
load 'rails/tasks/statistics.rake'
|
|
21
16
|
|
|
22
17
|
require 'bundler/gem_tasks'
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Base for Passwordless controllers
|
|
2
5
|
class ApplicationController < ::ApplicationController
|
|
6
|
+
# Always returns true. Use to check if <Some>Controller inherits
|
|
7
|
+
# from ApplicationController.
|
|
8
|
+
# @return [boolean]
|
|
3
9
|
def passwordless_controller?
|
|
4
10
|
true
|
|
5
11
|
end
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'bcrypt'
|
|
2
4
|
|
|
3
5
|
module Passwordless
|
|
6
|
+
# Controller for managing Passwordless sessions
|
|
4
7
|
class SessionsController < ApplicationController
|
|
8
|
+
# Raise this exception when a session is expired.
|
|
9
|
+
class ExpiredSessionError < StandardError; end
|
|
10
|
+
|
|
5
11
|
include ControllerHelpers
|
|
6
12
|
|
|
7
13
|
helper_method :authenticatable_resource
|
|
8
14
|
|
|
15
|
+
# get '/sign_in'
|
|
16
|
+
# Assigns an email_field and new Session to be used by new view.
|
|
17
|
+
# renders sessions/new.html.erb.
|
|
9
18
|
def new
|
|
10
|
-
@email_field =
|
|
11
|
-
|
|
19
|
+
@email_field = email_field
|
|
12
20
|
@session = Session.new
|
|
13
21
|
end
|
|
14
22
|
|
|
23
|
+
# post '/sign_in'
|
|
24
|
+
# Creates a new Session record then sends the magic link
|
|
25
|
+
# renders sessions/create.html.erb.
|
|
26
|
+
# @see Mailer#magic_link Mailer#magic_link
|
|
15
27
|
def create
|
|
16
|
-
|
|
17
|
-
authenticatable = authenticatable_class.where(
|
|
18
|
-
"lower(#{email_field}) = ?", params[:passwordless][email_field]
|
|
19
|
-
).first
|
|
20
|
-
|
|
21
|
-
session = Session.new.tap do |us|
|
|
22
|
-
us.remote_addr = request.remote_addr
|
|
23
|
-
us.user_agent = request.env['HTTP_USER_AGENT']
|
|
24
|
-
us.authenticatable = authenticatable
|
|
25
|
-
end
|
|
28
|
+
session = build_passwordless_session(find_authenticatable)
|
|
26
29
|
|
|
27
30
|
if session.save
|
|
28
31
|
Mailer.magic_link(session).deliver_now
|
|
@@ -31,27 +34,40 @@ module Passwordless
|
|
|
31
34
|
render
|
|
32
35
|
end
|
|
33
36
|
|
|
37
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
38
|
+
|
|
39
|
+
# get '/sign_in/:token'
|
|
40
|
+
# Looks up session record by provided token. Signs in user if a match
|
|
41
|
+
# is found. Redirects to either the user's original destination
|
|
42
|
+
# or _root_path_
|
|
43
|
+
# @see ControllerHelpers#sign_in
|
|
44
|
+
# @see ControllerHelpers#save_passwordless_redirect_location!
|
|
34
45
|
def show
|
|
35
46
|
# Make it "slow" on purpose to make brute-force attacks more of a hassle
|
|
36
47
|
BCrypt::Password.create(params[:token])
|
|
37
48
|
|
|
38
|
-
session =
|
|
39
|
-
|
|
40
|
-
token: params[:token]
|
|
41
|
-
)
|
|
49
|
+
session = find_session
|
|
50
|
+
raise ExpiredSessionError if session.expired?
|
|
42
51
|
|
|
43
52
|
sign_in session.authenticatable
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
destination =
|
|
54
|
+
redirect_enabled = Passwordless.redirect_back_after_sign_in
|
|
55
|
+
destination = reset_passwordless_redirect_location!(User)
|
|
47
56
|
|
|
48
|
-
if
|
|
49
|
-
redirect_to
|
|
57
|
+
if redirect_enabled && destination
|
|
58
|
+
redirect_to destination
|
|
50
59
|
else
|
|
51
60
|
redirect_to main_app.root_path
|
|
52
61
|
end
|
|
62
|
+
rescue ExpiredSessionError
|
|
63
|
+
flash[:error] = I18n.t('.passwordless.sessions.create.session_expired')
|
|
64
|
+
redirect_to main_app.root_path
|
|
53
65
|
end
|
|
66
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
54
67
|
|
|
68
|
+
# match '/sign_out', via: %i[get delete].
|
|
69
|
+
# Signs user out. Redirects to root_path
|
|
70
|
+
# @see ControllerHelpers#sign_out
|
|
55
71
|
def destroy
|
|
56
72
|
sign_out authenticatable_class
|
|
57
73
|
redirect_to main_app.root_path
|
|
@@ -74,5 +90,22 @@ module Passwordless
|
|
|
74
90
|
def authenticatable_resource
|
|
75
91
|
authenticatable.pluralize
|
|
76
92
|
end
|
|
93
|
+
|
|
94
|
+
def email_field
|
|
95
|
+
authenticatable_class.passwordless_email_field
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def find_authenticatable
|
|
99
|
+
authenticatable_class.where(
|
|
100
|
+
"lower(#{email_field}) = ?", params[:passwordless][email_field]
|
|
101
|
+
).first
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def find_session
|
|
105
|
+
Session.valid.find_by!(
|
|
106
|
+
authenticatable_type: authenticatable_classname,
|
|
107
|
+
token: params[:token]
|
|
108
|
+
)
|
|
109
|
+
end
|
|
77
110
|
end
|
|
78
111
|
end
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# The mailer responsible for sending Passwordless' mails.
|
|
2
5
|
class Mailer < ActionMailer::Base
|
|
3
6
|
default from: Passwordless.default_from_address
|
|
4
7
|
|
|
8
|
+
# Sends a magic link (secret token) email.
|
|
9
|
+
# @param session [Session] A Passwordless Session
|
|
5
10
|
def magic_link(session)
|
|
6
11
|
@session = session
|
|
7
12
|
|
|
@@ -11,7 +16,10 @@ module Passwordless
|
|
|
11
16
|
send(authenticatable_resource_name).token_sign_in_url(session.token)
|
|
12
17
|
|
|
13
18
|
email_field = @session.authenticatable.class.passwordless_email_field
|
|
14
|
-
mail
|
|
19
|
+
mail(
|
|
20
|
+
to: @session.authenticatable.send(email_field),
|
|
21
|
+
subject: I18n.t('passwordless.mailer.subject')
|
|
22
|
+
)
|
|
15
23
|
end
|
|
16
24
|
end
|
|
17
25
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# The session responsible for holding the connection between the record
|
|
5
|
+
# trying to log in and the unique tokens.
|
|
2
6
|
class Session < ApplicationRecord
|
|
3
|
-
belongs_to :authenticatable,
|
|
7
|
+
belongs_to :authenticatable,
|
|
8
|
+
polymorphic: true, inverse_of: :passwordless_sessions
|
|
4
9
|
|
|
5
10
|
validates \
|
|
6
11
|
:timeout_at,
|
|
@@ -13,9 +18,13 @@ module Passwordless
|
|
|
13
18
|
before_validation :set_defaults
|
|
14
19
|
|
|
15
20
|
scope :valid, lambda {
|
|
16
|
-
where('timeout_at > ?
|
|
21
|
+
where('timeout_at > ?', Time.current)
|
|
17
22
|
}
|
|
18
23
|
|
|
24
|
+
def expired?
|
|
25
|
+
expires_at <= Time.current
|
|
26
|
+
end
|
|
27
|
+
|
|
19
28
|
private
|
|
20
29
|
|
|
21
30
|
def set_defaults
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
<%= @magic_link %>
|
|
1
|
+
<%= I18n.t('passwordless.mailer.magic_link', link: @magic_link) %>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<p
|
|
1
|
+
<p><%= I18n.t('passwordless.sessions.success.email_sent_if_record_found') %></p>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<%= form_for @session, url: send(authenticatable_resource).sign_in_path do |f| %>
|
|
2
2
|
<% email_field_name = :"passwordless[#{@email_field}]" %>
|
|
3
3
|
<%= text_field_tag email_field_name, params.fetch(email_field_name, nil) %>
|
|
4
|
-
<%= f.submit '
|
|
4
|
+
<%= f.submit I18n.t('passwordless.sessions.new.submit') %>
|
|
5
5
|
<% end %>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
en:
|
|
3
|
+
passwordless:
|
|
4
|
+
sessions:
|
|
5
|
+
create:
|
|
6
|
+
session_expired: 'Your session has expired, please sign in again.'
|
|
7
|
+
email_sent_if_record_found: "If we found you in the system, we've sent you an email."
|
|
8
|
+
new:
|
|
9
|
+
submit: 'Send magic link'
|
|
10
|
+
mailer:
|
|
11
|
+
subject: "Your magic link ✨'"
|
|
12
|
+
magic_link: "Here's your link: %{link}"
|
data/config/routes.rb
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Helpers to work with Passwordless sessions from controllers
|
|
2
5
|
module ControllerHelpers
|
|
6
|
+
# Build a new Passwordless::Session from an _authenticatable_ record.
|
|
7
|
+
# Set's `user_agent` and `remote_addr` from Rails' `request`.
|
|
8
|
+
# @param authenticatable [ActiveRecord::Base] Instance of an
|
|
9
|
+
# authenticatable Rails model
|
|
10
|
+
# @return [Session] the new Session object
|
|
11
|
+
# @see ModelHelpers#passwordless_with
|
|
12
|
+
def build_passwordless_session(authenticatable)
|
|
13
|
+
Session.new.tap do |us|
|
|
14
|
+
us.remote_addr = request.remote_addr
|
|
15
|
+
us.user_agent = request.env['HTTP_USER_AGENT']
|
|
16
|
+
us.authenticatable = authenticatable
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Authenticate a record using cookies. Looks for a cookie corresponding to
|
|
21
|
+
# the _authenticatable_class_. If found try to find it in the database.
|
|
22
|
+
# @param authenticatable_class [ActiveRecord::Base] any Model connected to
|
|
23
|
+
# passwordless. (e.g - _User_ or _Admin_).
|
|
24
|
+
# @return [ActiveRecord::Base|nil] an instance of Model found by id stored
|
|
25
|
+
# in cookies.encrypted or nil if nothing is found.
|
|
26
|
+
# @see ModelHelpers#passwordless_with
|
|
3
27
|
def authenticate_by_cookie(authenticatable_class)
|
|
4
28
|
key = cookie_name(authenticatable_class)
|
|
5
29
|
authenticatable_id = cookies.encrypted[key]
|
|
@@ -8,28 +32,49 @@ module Passwordless
|
|
|
8
32
|
authenticatable_class.find_by(id: authenticatable_id)
|
|
9
33
|
end
|
|
10
34
|
|
|
35
|
+
# Signs in user by assigning their id to a permanent cookie.
|
|
36
|
+
# @param authenticatable [ActiveRecord::Base] Instance of Model to sign in
|
|
37
|
+
# (e.g - @user when @user = User.find(id: some_id)).
|
|
38
|
+
# @return [ActiveRecord::Base] the record that is passed in.
|
|
11
39
|
def sign_in(authenticatable)
|
|
12
40
|
key = cookie_name(authenticatable.class)
|
|
13
41
|
cookies.encrypted.permanent[key] = { value: authenticatable.id }
|
|
14
42
|
authenticatable
|
|
15
43
|
end
|
|
16
44
|
|
|
45
|
+
# Signs out user by deleting their encrypted cookie.
|
|
46
|
+
# @param (see #authenticate_by_cookie)
|
|
47
|
+
# @return [boolean] Always true
|
|
17
48
|
def sign_out(authenticatable_class)
|
|
18
49
|
key = cookie_name(authenticatable_class)
|
|
19
50
|
cookies.encrypted.permanent[key] = { value: nil }
|
|
20
51
|
cookies.delete(key)
|
|
52
|
+
true
|
|
21
53
|
end
|
|
22
54
|
|
|
23
|
-
|
|
24
|
-
|
|
55
|
+
# Saves request.original_url as the redirect location for a
|
|
56
|
+
# passwordless Model.
|
|
57
|
+
# @param (see #authenticate_by_cookie)
|
|
58
|
+
# @return [String] the redirect url that was just saved.
|
|
59
|
+
def save_passwordless_redirect_location!(authenticatable_class)
|
|
60
|
+
session[session_key(authenticatable_class)] = request.original_url
|
|
25
61
|
end
|
|
26
62
|
|
|
27
|
-
|
|
28
|
-
|
|
63
|
+
# Resets the redirect_location to root_path by deleting the redirect_url
|
|
64
|
+
# from session.
|
|
65
|
+
# @param (see #authenticate_by_cookie)
|
|
66
|
+
# @return [String, nil] the redirect url that was just deleted,
|
|
67
|
+
# or nil if no url found for given Model.
|
|
68
|
+
def reset_passwordless_redirect_location!(authenticatable_class)
|
|
69
|
+
session.delete session_key(authenticatable_class)
|
|
29
70
|
end
|
|
30
71
|
|
|
31
72
|
private
|
|
32
73
|
|
|
74
|
+
def session_key(authenticatable_class)
|
|
75
|
+
:"passwordless_prev_location--#{authenticatable_class}"
|
|
76
|
+
end
|
|
77
|
+
|
|
33
78
|
def cookie_name(authenticatable_class)
|
|
34
79
|
:"#{authenticatable_class.to_s.underscore}_id"
|
|
35
80
|
end
|
data/lib/passwordless/engine.rb
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Engine that runs the passwordless gem.
|
|
2
5
|
class Engine < ::Rails::Engine
|
|
3
6
|
isolate_namespace Passwordless
|
|
4
7
|
|
|
@@ -9,5 +12,10 @@ module Passwordless
|
|
|
9
12
|
ActiveRecord::Base.extend ModelHelpers
|
|
10
13
|
require 'passwordless/controller_helpers'
|
|
11
14
|
end
|
|
15
|
+
|
|
16
|
+
config.before_initialize do |app|
|
|
17
|
+
app.config.i18n.load_path +=
|
|
18
|
+
Dir[Engine.root.join('config', 'locales', '*.yml')]
|
|
19
|
+
end
|
|
12
20
|
end
|
|
13
21
|
end
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Some helpers for models that can sign in passswordlessly.
|
|
2
5
|
module ModelHelpers
|
|
6
|
+
# Creates relationship - has_many :passwordless_sessions
|
|
7
|
+
# Defines a method `Class.passwordless_email_field` returning its email
|
|
8
|
+
# field name (e.g. `:email`)
|
|
9
|
+
# @param field [string] email submitted by user.
|
|
3
10
|
def passwordless_with(field)
|
|
11
|
+
has_many :passwordless_sessions, class_name: 'Passwordless::Session'
|
|
4
12
|
define_singleton_method(:passwordless_email_field) { field }
|
|
5
13
|
end
|
|
6
14
|
end
|
|
@@ -1,5 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Helpers for generating passwordless routes.
|
|
2
5
|
module RouterHelpers
|
|
6
|
+
# Generates passwordless routes for a given Model
|
|
7
|
+
# Example usage:
|
|
8
|
+
# passwordless_for :users
|
|
9
|
+
# # or with options ...
|
|
10
|
+
# passwordless_for :users, at: 'session_stuff', as: :user_session_things
|
|
11
|
+
# @param resource [Symbol] the pluralized symbol of a Model (e.g - :users).
|
|
12
|
+
# @param at [String] Optional - provide custom path for the passwordless
|
|
13
|
+
# engine to get mounted at (using the above example your URLs end
|
|
14
|
+
# up like: /session_stuff/sign_in). (Default: resource.to_s)
|
|
15
|
+
# @param as [Symbol] Optional - provide custom scope for url
|
|
16
|
+
# helpers (using the above example in a view:
|
|
17
|
+
# <%= link_to 'Sign in', user_session_things.sign_in_path %>).
|
|
18
|
+
# (Default: resource.to_s)
|
|
3
19
|
def passwordless_for(resource, at: nil, as: nil)
|
|
4
20
|
mount(
|
|
5
21
|
Passwordless::Engine,
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Passwordless
|
|
4
|
+
# Generates random numbers for Session records
|
|
2
5
|
class UrlSafeBase64Generator
|
|
6
|
+
# Passwordless' default random string strategy. Generates a url safe
|
|
7
|
+
# base64 random string.
|
|
8
|
+
# @param _session [Session] Optional - Passwordless passes the sesion Record
|
|
9
|
+
# to generators so you can (optionally) use it for generating your tokens.
|
|
10
|
+
# @return [String] 32 byte base64 string
|
|
3
11
|
def call(_session)
|
|
4
12
|
SecureRandom.urlsafe_base64(32)
|
|
5
13
|
end
|
data/lib/passwordless/version.rb
CHANGED
data/lib/passwordless.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'passwordless/engine'
|
|
2
4
|
require 'passwordless/url_safe_base_64_generator'
|
|
3
5
|
|
|
6
|
+
# The main Passwordless module
|
|
4
7
|
module Passwordless
|
|
5
8
|
mattr_accessor(:default_from_address) { 'CHANGE_ME@example.com' }
|
|
6
|
-
mattr_accessor(:token_generator)
|
|
7
|
-
UrlSafeBase64Generator.new
|
|
8
|
-
end
|
|
9
|
+
mattr_accessor(:token_generator) { UrlSafeBase64Generator.new }
|
|
9
10
|
mattr_accessor(:redirect_back_after_sign_in) { true }
|
|
10
11
|
end
|
metadata
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: passwordless
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.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:
|
|
11
|
+
date: 2018-02-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 5.1.4
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 5.1.4
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
@@ -52,7 +52,35 @@ dependencies:
|
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '0'
|
|
55
|
-
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: yard
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
description:
|
|
56
84
|
email:
|
|
57
85
|
- mikkel@brnbw.com
|
|
58
86
|
executables: []
|
|
@@ -70,6 +98,7 @@ files:
|
|
|
70
98
|
- app/views/passwordless/mailer/magic_link.text.erb
|
|
71
99
|
- app/views/passwordless/sessions/create.html.erb
|
|
72
100
|
- app/views/passwordless/sessions/new.html.erb
|
|
101
|
+
- config/locales/en.yml
|
|
73
102
|
- config/routes.rb
|
|
74
103
|
- db/migrate/20171104221735_create_passwordless_sessions.rb
|
|
75
104
|
- lib/passwordless.rb
|
|
@@ -79,7 +108,6 @@ files:
|
|
|
79
108
|
- lib/passwordless/router_helpers.rb
|
|
80
109
|
- lib/passwordless/url_safe_base_64_generator.rb
|
|
81
110
|
- lib/passwordless/version.rb
|
|
82
|
-
- lib/tasks/passwordless_tasks.rake
|
|
83
111
|
homepage: https://github.com/mikker/passwordless
|
|
84
112
|
licenses:
|
|
85
113
|
- MIT
|
|
@@ -100,8 +128,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
100
128
|
version: '0'
|
|
101
129
|
requirements: []
|
|
102
130
|
rubyforge_project:
|
|
103
|
-
rubygems_version: 2.7.
|
|
131
|
+
rubygems_version: 2.7.3
|
|
104
132
|
signing_key:
|
|
105
133
|
specification_version: 4
|
|
106
|
-
summary:
|
|
134
|
+
summary: Add authentication to your app without all the ickyness of passwords.
|
|
107
135
|
test_files: []
|