passwordless 0.4.2 → 0.4.3
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/Rakefile +6 -11
- data/app/controllers/passwordless/application_controller.rb +6 -0
- data/app/controllers/passwordless/sessions_controller.rb +43 -15
- data/app/mailers/passwordless/mailer.rb +9 -1
- data/app/models/passwordless/application_record.rb +3 -0
- data/app/models/passwordless/session.rb +6 -1
- data/config/routes.rb +2 -0
- data/db/migrate/20171104221735_create_passwordless_sessions.rb +2 -0
- data/lib/passwordless.rb +4 -3
- data/lib/passwordless/controller_helpers.rb +27 -0
- data/lib/passwordless/engine.rb +3 -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
- metadata +32 -5
- 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: a3dbc0b122174ab4d4f115f47ef829bfe0ccfdf49cb559725e8833d5abaef517
|
4
|
+
data.tar.gz: ba545dc28ee08852e3c2caf2c9d920343aac14d2039bd3dc4f48e399d99c8bf4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79e1c14bcc3d71e438f0f7b146642460d3116a3d681a2deb17bc6e860d819b990e68565de94a72caa640fa5958fc9587769b6ee0c6af7b16a2e3bac2475d6605
|
7
|
+
data.tar.gz: 1695fc3178204892a35ee1a35869836b09d26005f4b4e4c99d2e9d02c813a87a1fefad6f00664302f191ecb4f3e3fe9cc57888aeadf2b8a7c6355fb1f1581873
|
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,22 +1,28 @@
|
|
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
|
5
8
|
include ControllerHelpers
|
6
9
|
|
7
10
|
helper_method :authenticatable_resource
|
8
11
|
|
12
|
+
# get '/sign_in'
|
13
|
+
# Assigns an email_field and new Session to be used by new view.
|
14
|
+
# renders sessions/new.html.erb.
|
9
15
|
def new
|
10
|
-
@email_field =
|
11
|
-
|
16
|
+
@email_field = email_field
|
12
17
|
@session = Session.new
|
13
18
|
end
|
14
19
|
|
20
|
+
# post '/sign_in'
|
21
|
+
# Creates a new Session record then sends the magic link
|
22
|
+
# renders sessions/create.html.erb.
|
23
|
+
# @see Mailer#magic_link Mailer#magic_link
|
15
24
|
def create
|
16
|
-
|
17
|
-
email = params.require(:passwordless).fetch(email_field).downcase
|
18
|
-
authenticatable =
|
19
|
-
authenticatable_class.where("lower(#{email_field}) = ?", email).first
|
25
|
+
authenticatable = find_authenticatable
|
20
26
|
|
21
27
|
session = Session.new.tap do |us|
|
22
28
|
us.remote_addr = request.remote_addr
|
@@ -31,27 +37,32 @@ module Passwordless
|
|
31
37
|
render
|
32
38
|
end
|
33
39
|
|
40
|
+
# get '/sign_in/:token'
|
41
|
+
# Looks up session record by provided token. Signs in user if a match
|
42
|
+
# is found. Redirects to either the user's original destination
|
43
|
+
# or _root_path_
|
44
|
+
# @see ControllerHelpers#sign_in
|
45
|
+
# @see ControllerHelpers#save_passwordless_redirect_location!
|
34
46
|
def show
|
35
47
|
# Make it "slow" on purpose to make brute-force attacks more of a hassle
|
36
48
|
BCrypt::Password.create(params[:token])
|
37
49
|
|
38
|
-
session =
|
39
|
-
authenticatable_type: authenticatable_classname,
|
40
|
-
token: params[:token]
|
41
|
-
)
|
42
|
-
|
50
|
+
session = find_session
|
43
51
|
sign_in session.authenticatable
|
44
52
|
|
45
|
-
|
46
|
-
destination =
|
53
|
+
redirect_enabled = Passwordless.redirect_back_after_sign_in
|
54
|
+
destination = reset_passwordless_redirect_location!(User)
|
47
55
|
|
48
|
-
if
|
49
|
-
redirect_to
|
56
|
+
if redirect_enabled && destination
|
57
|
+
redirect_to destination
|
50
58
|
else
|
51
59
|
redirect_to main_app.root_path
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
63
|
+
# match '/sign_out', via: %i[get delete].
|
64
|
+
# Signs user out. Redirects to root_path
|
65
|
+
# @see ControllerHelpers#sign_out
|
55
66
|
def destroy
|
56
67
|
sign_out authenticatable_class
|
57
68
|
redirect_to main_app.root_path
|
@@ -74,5 +85,22 @@ module Passwordless
|
|
74
85
|
def authenticatable_resource
|
75
86
|
authenticatable.pluralize
|
76
87
|
end
|
88
|
+
|
89
|
+
def email_field
|
90
|
+
authenticatable_class.passwordless_email_field
|
91
|
+
end
|
92
|
+
|
93
|
+
def find_authenticatable
|
94
|
+
authenticatable_class.where(
|
95
|
+
"lower(#{email_field}) = ?", params[:passwordless][email_field]
|
96
|
+
).first
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_session
|
100
|
+
Session.valid.find_by!(
|
101
|
+
authenticatable_type: authenticatable_classname,
|
102
|
+
token: params[:token]
|
103
|
+
)
|
104
|
+
end
|
77
105
|
end
|
78
106
|
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: 'Your magic link ✨'
|
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,
|
data/config/routes.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
|
@@ -1,5 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Passwordless
|
4
|
+
# Helpers to work with Passwordless sessions from controllers
|
2
5
|
module ControllerHelpers
|
6
|
+
# Authenticate a record using cookies. Looks for a cookie corresponding to
|
7
|
+
# the _authenticatable_class_. If found try to find it in the database.
|
8
|
+
# @param authenticatable_class [ActiveRecord::Base] any Model connected to
|
9
|
+
# passwordless. (e.g - _User_ or _Admin_).
|
10
|
+
# @return [ActiveRecord::Base|nil] an instance of Model found by id stored
|
11
|
+
# in cookies.encrypted or nil if nothing is found.
|
12
|
+
# @see ModelHelpers#passwordless_with
|
3
13
|
def authenticate_by_cookie(authenticatable_class)
|
4
14
|
key = cookie_name(authenticatable_class)
|
5
15
|
authenticatable_id = cookies.encrypted[key]
|
@@ -8,22 +18,39 @@ module Passwordless
|
|
8
18
|
authenticatable_class.find_by(id: authenticatable_id)
|
9
19
|
end
|
10
20
|
|
21
|
+
# Signs in user by assigning their id to a permanent cookie.
|
22
|
+
# @param authenticatable [ActiveRecord::Base] Instance of Model to sign in
|
23
|
+
# (e.g - @user when @user = User.find(id: some_id)).
|
24
|
+
# @return [ActiveRecord::Base] the record that is passed in.
|
11
25
|
def sign_in(authenticatable)
|
12
26
|
key = cookie_name(authenticatable.class)
|
13
27
|
cookies.encrypted.permanent[key] = { value: authenticatable.id }
|
14
28
|
authenticatable
|
15
29
|
end
|
16
30
|
|
31
|
+
# Signs out user by deleting their encrypted cookie.
|
32
|
+
# @param (see #authenticate_by_cookie)
|
33
|
+
# @return [boolean] Always true
|
17
34
|
def sign_out(authenticatable_class)
|
18
35
|
key = cookie_name(authenticatable_class)
|
19
36
|
cookies.encrypted.permanent[key] = { value: nil }
|
20
37
|
cookies.delete(key)
|
38
|
+
true
|
21
39
|
end
|
22
40
|
|
41
|
+
# Saves request.original_url as the redirect location for a
|
42
|
+
# passwordless Model.
|
43
|
+
# @param (see #authenticate_by_cookie)
|
44
|
+
# @return [String] the redirect url that was just saved.
|
23
45
|
def save_passwordless_redirect_location!(authenticatable_class)
|
24
46
|
session[session_key(authenticatable_class)] = request.original_url
|
25
47
|
end
|
26
48
|
|
49
|
+
# Resets the redirect_location to root_path by deleting the redirect_url
|
50
|
+
# from session.
|
51
|
+
# @param (see #authenticate_by_cookie)
|
52
|
+
# @return [String, nil] the redirect url that was just deleted,
|
53
|
+
# or nil if no url found for given Model.
|
27
54
|
def reset_passwordless_redirect_location!(authenticatable_class)
|
28
55
|
session.delete session_key(authenticatable_class)
|
29
56
|
end
|
data/lib/passwordless/engine.rb
CHANGED
@@ -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
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.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mikkel Malmberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -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: []
|
@@ -79,7 +107,6 @@ files:
|
|
79
107
|
- lib/passwordless/router_helpers.rb
|
80
108
|
- lib/passwordless/url_safe_base_64_generator.rb
|
81
109
|
- lib/passwordless/version.rb
|
82
|
-
- lib/tasks/passwordless_tasks.rake
|
83
110
|
homepage: https://github.com/mikker/passwordless
|
84
111
|
licenses:
|
85
112
|
- MIT
|
@@ -103,5 +130,5 @@ rubyforge_project:
|
|
103
130
|
rubygems_version: 2.7.3
|
104
131
|
signing_key:
|
105
132
|
specification_version: 4
|
106
|
-
summary:
|
133
|
+
summary: Add authentication to your app without all the ickyness of passwords.
|
107
134
|
test_files: []
|