passwordless 0.6.0 → 0.7.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 -1
- data/Rakefile +12 -10
- data/app/controllers/passwordless/sessions_controller.rb +7 -10
- data/app/mailers/passwordless/mailer.rb +2 -2
- data/app/models/passwordless/session.rb +7 -3
- data/config/routes.rb +4 -4
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +1 -1
- data/lib/passwordless.rb +5 -3
- data/lib/passwordless/controller_helpers.rb +3 -3
- data/lib/passwordless/engine.rb +4 -4
- data/lib/passwordless/model_helpers.rb +1 -1
- data/lib/passwordless/router_helpers.rb +1 -1
- data/lib/passwordless/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb17c0cc08c9dd0062b47d67a470adeee2d0e7dc618e48967180eea61796f47a
|
4
|
+
data.tar.gz: 870dd7a03e131344f20bdee24ab614e1ef66ff3fe8faf9d4bd05bb08adce032d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e43144bae67ad300ae753fe131ec6b09fcf997c975e2fcb9a819d457fb0178797d03b4e7278c3c92c4cc18664c54c5231de428be7f6fc63c05a878516e628f7a
|
7
|
+
data.tar.gz: 5aeca84a612458d6e2dce0b0e9335d80a9f4e4af730540a148c3ef0ed6056417a33b444bfc17c19adf06744a7a2fa38dccb82189206bf2c86354352540b41e7e
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
<br />
|
5
5
|
</p>
|
6
6
|
|
7
|
-
[](https://travis-ci.org/mikker/passwordless) [](https://rubygems.org/gems/passwordless) [](https://codecov.io/gh/mikker/passwordless)
|
7
|
+
[](https://travis-ci.org/mikker/passwordless) [](https://rubygems.org/gems/passwordless) [](https://codecov.io/gh/mikker/passwordless) [](https://github.com/testdouble/standard)
|
8
8
|
|
9
9
|
Add authentication to your Rails app without all the icky-ness of passwords.
|
10
10
|
|
@@ -22,6 +22,7 @@ Add authentication to your Rails app without all the icky-ness of passwords.
|
|
22
22
|
* [Token and Session Expiry](#token-and-session-expiry)
|
23
23
|
* [Redirecting back after sign-in](#redirecting-back-after-sign-in)
|
24
24
|
* [URLs and links](#urls-and-links)
|
25
|
+
* [Customize the way to send magic link](#customize-the-way-to-send-magic-link)
|
25
26
|
* [E-mail security](#e-mail-security)
|
26
27
|
* [License](#license)
|
27
28
|
|
@@ -232,6 +233,26 @@ passwordless_for :users, at: '/', as: :auth
|
|
232
233
|
|
233
234
|
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).
|
234
235
|
|
236
|
+
|
237
|
+
### Customize the way to send magic link
|
238
|
+
|
239
|
+
By default, magic link will send by email. You can customize this method. For example, you can send magic link via SMS.
|
240
|
+
|
241
|
+
config/initializers/passwordless.rb
|
242
|
+
|
243
|
+
```
|
244
|
+
Passwordless.after_session_save = lambda do |session|
|
245
|
+
# Default behavior is
|
246
|
+
# Mailer.magic_link(session).deliver_now
|
247
|
+
|
248
|
+
# You can change behavior to do something with session model. For example,
|
249
|
+
# session.authenticatable.send_sms
|
250
|
+
end
|
251
|
+
```
|
252
|
+
|
253
|
+
You can access user model through authenticatable.
|
254
|
+
|
255
|
+
|
235
256
|
### E-mail security
|
236
257
|
|
237
258
|
There's no reason that this approach should be less secure than the usual username/password combo. In fact this is most often a more secure option, as users don't get to choose the weak passwords they still use. In a way this is just the same as having each user go through "Forgot password" on every login.
|
data/Rakefile
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
begin
|
4
|
-
require
|
4
|
+
require "bundler/setup"
|
5
5
|
rescue LoadError
|
6
|
-
puts
|
6
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
7
7
|
end
|
8
8
|
|
9
|
-
require
|
9
|
+
require "yard"
|
10
10
|
YARD::Rake::YardocTask.new
|
11
11
|
task docs: :yard
|
12
12
|
|
13
|
-
APP_RAKEFILE = File.expand_path(
|
14
|
-
load
|
15
|
-
load
|
13
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
14
|
+
load "rails/tasks/engine.rake"
|
15
|
+
load "rails/tasks/statistics.rake"
|
16
16
|
|
17
|
-
require
|
17
|
+
require "bundler/gem_tasks"
|
18
18
|
|
19
|
-
require
|
19
|
+
require "rake/testtask"
|
20
20
|
|
21
21
|
Rake::TestTask.new(:test) do |t|
|
22
|
-
t.libs <<
|
23
|
-
t.pattern =
|
22
|
+
t.libs << "test"
|
23
|
+
t.pattern = "test/**/*_test.rb"
|
24
24
|
t.verbose = false
|
25
25
|
end
|
26
26
|
|
27
27
|
task default: :test
|
28
|
+
|
29
|
+
require "standard/rake"
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "bcrypt"
|
4
4
|
|
5
5
|
module Passwordless
|
6
6
|
# Controller for managing Passwordless sessions
|
7
7
|
class SessionsController < ApplicationController
|
8
8
|
# Raise this exception when a session is expired.
|
9
|
-
class
|
9
|
+
class SessionTimedOutError < StandardError; end
|
10
10
|
|
11
11
|
include ControllerHelpers
|
12
12
|
|
@@ -26,14 +26,12 @@ module Passwordless
|
|
26
26
|
session = build_passwordless_session(find_authenticatable)
|
27
27
|
|
28
28
|
if session.save
|
29
|
-
|
29
|
+
Passwordless.after_session_save.call(session)
|
30
30
|
end
|
31
31
|
|
32
32
|
render
|
33
33
|
end
|
34
34
|
|
35
|
-
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
36
|
-
|
37
35
|
# get '/sign_in/:token'
|
38
36
|
# Looks up session record by provided token. Signs in user if a match
|
39
37
|
# is found. Redirects to either the user's original destination
|
@@ -45,7 +43,7 @@ module Passwordless
|
|
45
43
|
BCrypt::Password.create(params[:token])
|
46
44
|
|
47
45
|
session = find_session
|
48
|
-
raise
|
46
|
+
raise SessionTimedOutError if session.timed_out?
|
49
47
|
|
50
48
|
sign_in session.authenticatable
|
51
49
|
|
@@ -57,11 +55,10 @@ module Passwordless
|
|
57
55
|
else
|
58
56
|
redirect_to main_app.root_path
|
59
57
|
end
|
60
|
-
rescue
|
61
|
-
flash[:error] = I18n.t(
|
58
|
+
rescue SessionTimedOutError
|
59
|
+
flash[:error] = I18n.t(".passwordless.sessions.create.session_expired")
|
62
60
|
redirect_to main_app.root_path
|
63
61
|
end
|
64
|
-
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
65
62
|
|
66
63
|
# match '/sign_out', via: %i[get delete].
|
67
64
|
# Signs user out. Redirects to root_path
|
@@ -102,7 +99,7 @@ module Passwordless
|
|
102
99
|
end
|
103
100
|
|
104
101
|
def find_session
|
105
|
-
Session.
|
102
|
+
Session.find_by!(
|
106
103
|
authenticatable_type: authenticatable_classname,
|
107
104
|
token: params[:token]
|
108
105
|
)
|
@@ -11,12 +11,12 @@ module Passwordless
|
|
11
11
|
@session = session
|
12
12
|
|
13
13
|
@magic_link = send(Passwordless.mounted_as)
|
14
|
-
|
14
|
+
.token_sign_in_url(session.token)
|
15
15
|
|
16
16
|
email_field = @session.authenticatable.class.passwordless_email_field
|
17
17
|
mail(
|
18
18
|
to: @session.authenticatable.send(email_field),
|
19
|
-
subject: I18n.t(
|
19
|
+
subject: I18n.t("passwordless.mailer.subject")
|
20
20
|
)
|
21
21
|
end
|
22
22
|
end
|
@@ -19,22 +19,26 @@ module Passwordless
|
|
19
19
|
before_validation :set_defaults
|
20
20
|
|
21
21
|
scope :valid, lambda {
|
22
|
-
where(
|
22
|
+
where("timeout_at > ?", Time.current)
|
23
23
|
}
|
24
24
|
|
25
25
|
def expired?
|
26
26
|
expires_at <= Time.current
|
27
27
|
end
|
28
28
|
|
29
|
+
def timed_out?
|
30
|
+
timeout_at <= Time.current
|
31
|
+
end
|
32
|
+
|
29
33
|
private
|
30
34
|
|
31
35
|
def set_defaults
|
32
36
|
self.expires_at ||= Passwordless.expires_at.call
|
33
37
|
self.timeout_at ||= Passwordless.timeout_at.call
|
34
|
-
self.token ||= loop
|
38
|
+
self.token ||= loop {
|
35
39
|
token = Passwordless.token_generator.call(self)
|
36
40
|
break token unless Session.find_by(token: token)
|
37
|
-
|
41
|
+
}
|
38
42
|
end
|
39
43
|
end
|
40
44
|
end
|
data/config/routes.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
Passwordless::Engine.routes.draw do
|
4
|
-
get
|
5
|
-
post
|
6
|
-
get
|
7
|
-
match
|
4
|
+
get "/sign_in", to: "sessions#new", as: :sign_in
|
5
|
+
post "/sign_in", to: "sessions#create"
|
6
|
+
get "/sign_in/:token", to: "sessions#show", as: :token_sign_in
|
7
|
+
match "/sign_out", to: "sessions#destroy", via: %i[get delete], as: :sign_out
|
8
8
|
end
|
@@ -6,7 +6,7 @@ class CreatePasswordlessSessions < ActiveRecord::Migration[5.1]
|
|
6
6
|
t.belongs_to(
|
7
7
|
:authenticatable,
|
8
8
|
polymorphic: true,
|
9
|
-
index: {
|
9
|
+
index: {name: "authenticatable"}
|
10
10
|
)
|
11
11
|
t.datetime :timeout_at, null: false
|
12
12
|
t.datetime :expires_at, null: false
|
data/lib/passwordless.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "passwordless/engine"
|
4
|
+
require "passwordless/url_safe_base_64_generator"
|
5
5
|
|
6
6
|
# The main Passwordless module
|
7
7
|
module Passwordless
|
8
|
-
mattr_accessor(:default_from_address) {
|
8
|
+
mattr_accessor(:default_from_address) { "CHANGE_ME@example.com" }
|
9
9
|
mattr_accessor(:token_generator) { UrlSafeBase64Generator.new }
|
10
10
|
mattr_accessor(:redirect_back_after_sign_in) { true }
|
11
11
|
mattr_accessor(:mounted_as) { :configured_when_mounting_passwordless }
|
12
12
|
|
13
13
|
mattr_accessor(:expires_at) { lambda { 1.year.from_now } }
|
14
14
|
mattr_accessor(:timeout_at) { lambda { 1.hour.from_now } }
|
15
|
+
|
16
|
+
mattr_accessor(:after_session_save) { lambda { |session| Mailer.magic_link(session).deliver_now } }
|
15
17
|
end
|
@@ -12,7 +12,7 @@ module Passwordless
|
|
12
12
|
def build_passwordless_session(authenticatable)
|
13
13
|
Session.new.tap do |us|
|
14
14
|
us.remote_addr = request.remote_addr
|
15
|
-
us.user_agent = request.env[
|
15
|
+
us.user_agent = request.env["HTTP_USER_AGENT"]
|
16
16
|
us.authenticatable = authenticatable
|
17
17
|
end
|
18
18
|
end
|
@@ -38,7 +38,7 @@ module Passwordless
|
|
38
38
|
# @return [ActiveRecord::Base] the record that is passed in.
|
39
39
|
def sign_in(authenticatable)
|
40
40
|
key = cookie_name(authenticatable.class)
|
41
|
-
cookies.encrypted.permanent[key] = {
|
41
|
+
cookies.encrypted.permanent[key] = {value: authenticatable.id}
|
42
42
|
authenticatable
|
43
43
|
end
|
44
44
|
|
@@ -47,7 +47,7 @@ module Passwordless
|
|
47
47
|
# @return [boolean] Always true
|
48
48
|
def sign_out(authenticatable_class)
|
49
49
|
key = cookie_name(authenticatable_class)
|
50
|
-
cookies.encrypted.permanent[key] = {
|
50
|
+
cookies.encrypted.permanent[key] = {value: nil}
|
51
51
|
cookies.delete(key)
|
52
52
|
true
|
53
53
|
end
|
data/lib/passwordless/engine.rb
CHANGED
@@ -6,16 +6,16 @@ module Passwordless
|
|
6
6
|
isolate_namespace Passwordless
|
7
7
|
|
8
8
|
config.to_prepare do
|
9
|
-
require
|
9
|
+
require "passwordless/router_helpers"
|
10
10
|
ActionDispatch::Routing::Mapper.include RouterHelpers
|
11
|
-
require
|
11
|
+
require "passwordless/model_helpers"
|
12
12
|
ActiveRecord::Base.extend ModelHelpers
|
13
|
-
require
|
13
|
+
require "passwordless/controller_helpers"
|
14
14
|
end
|
15
15
|
|
16
16
|
config.before_initialize do |app|
|
17
17
|
app.config.i18n.load_path +=
|
18
|
-
Dir[Engine.root.join(
|
18
|
+
Dir[Engine.root.join("config", "locales", "*.yml")]
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
@@ -9,7 +9,7 @@ module Passwordless
|
|
9
9
|
# @param field [string] email submitted by user.
|
10
10
|
def passwordless_with(field)
|
11
11
|
has_many :passwordless_sessions,
|
12
|
-
class_name:
|
12
|
+
class_name: "Passwordless::Session",
|
13
13
|
as: :authenticatable
|
14
14
|
|
15
15
|
define_singleton_method(:passwordless_email_field) { field }
|
@@ -21,7 +21,7 @@ module Passwordless
|
|
21
21
|
mount_as = as || resource.to_s
|
22
22
|
mount(
|
23
23
|
Passwordless::Engine, at: mount_at, as: mount_as,
|
24
|
-
|
24
|
+
defaults: {authenticatable: resource.to_s.singularize}
|
25
25
|
)
|
26
26
|
|
27
27
|
Passwordless.mounted_as = mount_as
|
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.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: 2019-
|
11
|
+
date: 2019-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -42,16 +42,16 @@ dependencies:
|
|
42
42
|
name: sqlite3
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 1.3.6
|
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:
|
54
|
+
version: 1.3.6
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: yard
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: standard
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -127,7 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
127
|
- !ruby/object:Gem::Version
|
128
128
|
version: '0'
|
129
129
|
requirements: []
|
130
|
-
rubygems_version: 3.0.
|
130
|
+
rubygems_version: 3.0.1
|
131
131
|
signing_key:
|
132
132
|
specification_version: 4
|
133
133
|
summary: Add authentication to your app without all the ickyness of passwords.
|