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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75c3878f6173f614504aec1073969f86c5c62b52f987c2c3402ea75771d705b4
4
- data.tar.gz: d5f7d2cbd330c33ac40213860653087a2d8977bcad1d936431fd5c41a03e60e7
3
+ metadata.gz: a3dbc0b122174ab4d4f115f47ef829bfe0ccfdf49cb559725e8833d5abaef517
4
+ data.tar.gz: ba545dc28ee08852e3c2caf2c9d920343aac14d2039bd3dc4f48e399d99c8bf4
5
5
  SHA512:
6
- metadata.gz: 0a1c0c33379fc8da5b45fb26e1bf89d27848be3c0d627bdf84257966cf5256e85c0e5e2129f5da2f39602a8346649d082dc9de1387886110580d1693619df922
7
- data.tar.gz: 59528a2ec29c12b02de00616fd9b184b998d20d04124b5dbe08143fe7b9ca77f632395b60ae18052500b5b0ea84d9e4b7307df50200fed2a621d52c893106824
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 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
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("../test/dummy/Rakefile", __FILE__)
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 = authenticatable_class.passwordless_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
- email_field = authenticatable_class.passwordless_email_field
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 = Session.valid.find_by!(
39
- authenticatable_type: authenticatable_classname,
40
- token: params[:token]
41
- )
42
-
50
+ session = find_session
43
51
  sign_in session.authenticatable
44
52
 
45
- enabled = Passwordless.redirect_back_after_sign_in
46
- destination = dest = reset_passwordless_redirect_location!(User)
53
+ redirect_enabled = Passwordless.redirect_back_after_sign_in
54
+ destination = reset_passwordless_redirect_location!(User)
47
55
 
48
- if enabled && destination
49
- redirect_to dest
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 to: @session.authenticatable.send(email_field), subject: "Your magic link ✨"
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,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passwordless
4
+ # Base record for Passordless' models
2
5
  class ApplicationRecord < ActiveRecord::Base
3
6
  self.abstract_class = true
4
7
  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, polymorphic: true
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Passwordless::Engine.routes.draw do
2
4
  get '/sign_in', to: 'sessions#new', as: :sign_in
3
5
  post '/sign_in', to: 'sessions#create'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CreatePasswordlessSessions < ActiveRecord::Migration[5.1]
2
4
  def change
3
5
  create_table :passwordless_sessions do |t|
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) do
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
@@ -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
 
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Passwordless
2
- VERSION = '0.4.2'
4
+ VERSION = '0.4.3' # :nodoc:
3
5
  end
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.2
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-24 00:00:00.000000000 Z
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
- description: Description of Passwordless.
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: Summary of Passwordless.
133
+ summary: Add authentication to your app without all the ickyness of passwords.
107
134
  test_files: []
@@ -1,4 +0,0 @@
1
- # desc "Explaining what the task does"
2
- # task :passwordless do
3
- # # Task goes here
4
- # end