passwordless 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|