passwordless 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,17 +3,13 @@
3
3
  module Passwordless
4
4
  # Engine that runs the passwordless gem.
5
5
  class Engine < ::Rails::Engine
6
- isolate_namespace Passwordless
7
-
8
6
  config.to_prepare do
9
7
  require "passwordless/router_helpers"
10
-
11
- ActionDispatch::Routing::Mapper.include RouterHelpers
12
8
  require "passwordless/model_helpers"
13
-
14
- ActiveRecord::Base.extend ModelHelpers
15
9
  require "passwordless/controller_helpers"
16
10
 
11
+ ActionDispatch::Routing::Mapper.include RouterHelpers
12
+ ActiveRecord::Base.extend ModelHelpers
17
13
  end
18
14
 
19
15
  config.before_initialize do |app|
@@ -2,6 +2,10 @@
2
2
 
3
3
  module Passwordless
4
4
  module Errors
5
+ # Raised when the authenticatable class is not found.
6
+ class MissingEmailFieldError < StandardError
7
+ end
8
+
5
9
  # Raise this exception when a session is expired.
6
10
  class SessionTimedOutError < StandardError
7
11
  end
@@ -8,6 +8,8 @@ module Passwordless
8
8
  # passwordless_for :users
9
9
  # # or with options ...
10
10
  # passwordless_for :users, at: 'session_stuff', as: :user_session_things
11
+ # # or with a custom controller ...
12
+ # passwordless_for :users, controller: 'my_custom_controller'
11
13
  # @param resource [Symbol] the pluralized symbol of a Model (e.g - :users).
12
14
  # @param at [String] Optional - provide custom path for the passwordless
13
15
  # engine to get mounted at (using the above example your URLs end
@@ -16,17 +18,29 @@ module Passwordless
16
18
  # helpers (using the above example in a view:
17
19
  # <%= link_to 'Sign in', user_session_things.sign_in_path %>).
18
20
  # (Default: resource.to_s)
19
- def passwordless_for(resource, at: nil, as: nil)
20
- mount_at = at || resource.to_s
21
- mount_as = as || resource.to_s
22
- mount(
23
- Passwordless::Engine,
24
- at: mount_at,
25
- as: mount_as,
26
- defaults: {authenticatable: resource.to_s.singularize}
27
- )
21
+ # @param controller [String] Optional - provide a custom controller for
22
+ # sessions to use (using the above example the controller called would be MyCustomController
23
+ # (Default: 'passwordless/sessions')
24
+ def passwordless_for(resource, at: :na, as: :na, controller: "passwordless/sessions")
25
+ at == :na && at = "/#{resource.to_s}"
26
+ as == :na && as = "#{resource.to_s}_"
28
27
 
29
- Passwordless.mounted_as = mount_as
28
+ plural = resource.to_s
29
+ singular = plural.singularize
30
+
31
+ defaults = {
32
+ authenticatable: singular,
33
+ resource: resource,
34
+ }
35
+
36
+ scope(defaults: defaults) do
37
+ get("#{at}/sign_in", to: "#{controller}#new", as: :"#{as}sign_in")
38
+ post("#{at}/sign_in", to: "#{controller}#create")
39
+ get("#{at}/sign_in/:id", to: "#{controller}#show", as: :"verify_#{as}sign_in")
40
+ get("#{at}/sign_in/:id/:token", to: "#{controller}#confirm", as: :"confirm_#{as}sign_in")
41
+ patch("#{at}/sign_in/:id", to: "#{controller}#update")
42
+ match("#{at}/sign_out", to: "#{controller}#destroy", via: %i[get delete], as: :"#{as}sign_out")
43
+ end
30
44
  end
31
45
  end
32
46
  end
@@ -0,0 +1,9 @@
1
+ module Passwordless
2
+ class ShortTokenGenerator
3
+ CHARS = [*"A".."Z", *"0".."9"].freeze
4
+
5
+ def call(_session)
6
+ CHARS.sample(6).join
7
+ end
8
+ end
9
+ end
@@ -2,25 +2,35 @@ module Passwordless
2
2
  module TestHelpers
3
3
  module TestCase
4
4
  def passwordless_sign_out
5
- delete Passwordless::Engine.routes.url_helpers.sign_out_path
5
+ delete(Passwordless::Engine.routes.url_helpers.sign_out_path)
6
6
  follow_redirect!
7
7
  end
8
8
 
9
9
  def passwordless_sign_in(resource)
10
- session = Passwordless::Session.create!(authenticatable: resource, user_agent: "TestAgent", remote_addr: "unknown")
11
- get Passwordless::Engine.routes.url_helpers.token_sign_in_path(session.token)
10
+ session = Passwordless::Session.create!(authenticatable: resource)
11
+ magic_link = Passwordless::Engine.routes.url_helpers.send(
12
+ :"confirm_#{session.authenticatable_type.tableize}_sign_in_url",
13
+ session,
14
+ session.token
15
+ )
16
+ get(magic_link)
12
17
  follow_redirect!
13
18
  end
14
19
  end
15
20
 
16
21
  module SystemTestCase
17
22
  def passwordless_sign_out
18
- visit Passwordless::Engine.routes.url_helpers.sign_out_path
23
+ visit(Passwordless::Engine.routes.url_helpers.sign_out_path)
19
24
  end
20
25
 
21
26
  def passwordless_sign_in(resource)
22
- session = Passwordless::Session.create!(authenticatable: resource, user_agent: "TestAgent", remote_addr: "unknown")
23
- visit Passwordless::Engine.routes.url_helpers.token_sign_in_path(session.token)
27
+ session = Passwordless::Session.create!(authenticatable: resource)
28
+ magic_link = Passwordless::Engine.routes.url_helpers.send(
29
+ :"confirm_#{session.authenticatable_type.tableize}_sign_in_url",
30
+ session,
31
+ session.token
32
+ )
33
+ visit(magic_link)
24
34
  end
25
35
  end
26
36
  end
@@ -36,8 +46,8 @@ end
36
46
 
37
47
  if defined?(RSpec)
38
48
  RSpec.configure do |config|
39
- config.include ::Passwordless::TestHelpers::TestCase, type: :request
40
- config.include ::Passwordless::TestHelpers::TestCase, type: :controller
41
- config.include ::Passwordless::TestHelpers::SystemTestCase, type: :system
49
+ config.include(::Passwordless::TestHelpers::TestCase, type: :request)
50
+ config.include(::Passwordless::TestHelpers::TestCase, type: :controller)
51
+ config.include(::Passwordless::TestHelpers::SystemTestCase, type: :system)
42
52
  end
43
53
  end
@@ -0,0 +1,18 @@
1
+ module Passwordless
2
+ class TokenDigest
3
+ ALGORITHM = "SHA256"
4
+
5
+ def initialize(str)
6
+ @str = str
7
+ end
8
+
9
+ def digest
10
+ key = self.class.key()
11
+ OpenSSL::HMAC.hexdigest(ALGORITHM, key, @str)
12
+ end
13
+
14
+ def self.key
15
+ @key ||= ActiveSupport::KeyGenerator.new(Rails.application.secret_key_base).generate_key("passwordless")
16
+ end
17
+ end
18
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Passwordless
4
4
  # :nodoc:
5
- VERSION = "0.12.0"
5
+ VERSION = "1.0.0"
6
6
  end
data/lib/passwordless.rb CHANGED
@@ -1,30 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support"
4
+ require "passwordless/config"
4
5
  require "passwordless/errors"
5
6
  require "passwordless/engine"
6
- require "passwordless/url_safe_base_64_generator"
7
+ require "passwordless/token_digest"
7
8
 
8
9
  # The main Passwordless module
9
10
  module Passwordless
10
- mattr_accessor(:parent_mailer) { "ActionMailer::Base" }
11
- mattr_accessor(:default_from_address) { "CHANGE_ME@example.com" }
12
- mattr_accessor(:token_generator) { UrlSafeBase64Generator.new }
13
- mattr_accessor(:restrict_token_reuse) { false }
14
- mattr_accessor(:redirect_back_after_sign_in) { true }
15
- mattr_accessor(:mounted_as) { :configured_when_mounting_passwordless }
11
+ extend Configurable
16
12
 
17
- mattr_accessor(:expires_at) { lambda { 1.year.from_now } }
18
- mattr_accessor(:timeout_at) { lambda { 1.hour.from_now } }
19
- mattr_accessor(:redirect_to_response_options) { {} }
20
- mattr_accessor(:success_redirect_path) { "/" }
21
- mattr_accessor(:failure_redirect_path) { "/" }
22
- mattr_accessor(:sign_out_redirect_path) { "/" }
23
-
24
- mattr_accessor(:after_session_save) do
25
- lambda { |session, _request| Mailer.magic_link(session).deliver_now }
13
+ def self.digest(token)
14
+ TokenDigest.new(token).digest
26
15
  end
27
-
28
- CookieDeprecation = ActiveSupport::Deprecation.new("0.9", "passwordless")
29
- SessionValidDeprecation = ActiveSupport::Deprecation.new("0.9", "passwordless")
30
16
  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.12.0
4
+ version: 1.0.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: 2023-06-16 00:00:00.000000000 Z
11
+ date: 2023-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -28,58 +28,16 @@ dependencies:
28
28
  name: bcrypt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: 3.1.11
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 3.1.11
41
- - !ruby/object:Gem::Dependency
42
- name: sqlite3
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: 1.4.1
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: 1.4.1
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: standard
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
36
  version_requirements: !ruby/object:Gem::Requirement
79
37
  requirements:
80
38
  - - ">="
81
39
  - !ruby/object:Gem::Version
82
- version: '0'
40
+ version: 3.1.11
83
41
  description:
84
42
  email:
85
43
  - mikkel@brnbw.com
@@ -95,20 +53,23 @@ files:
95
53
  - app/mailers/passwordless/mailer.rb
96
54
  - app/models/passwordless/application_record.rb
97
55
  - app/models/passwordless/session.rb
98
- - app/views/passwordless/mailer/magic_link.text.erb
56
+ - app/views/passwordless/mailer/sign_in.text.erb
99
57
  - app/views/passwordless/sessions/new.html.erb
58
+ - app/views/passwordless/sessions/show.html.erb
100
59
  - config/locales/en.yml
101
60
  - config/routes.rb
102
61
  - db/migrate/20171104221735_create_passwordless_sessions.rb
103
62
  - lib/generators/passwordless/views_generator.rb
104
63
  - lib/passwordless.rb
64
+ - lib/passwordless/config.rb
105
65
  - lib/passwordless/controller_helpers.rb
106
66
  - lib/passwordless/engine.rb
107
67
  - lib/passwordless/errors.rb
108
68
  - lib/passwordless/model_helpers.rb
109
69
  - lib/passwordless/router_helpers.rb
70
+ - lib/passwordless/short_token_generator.rb
110
71
  - lib/passwordless/test_helpers.rb
111
- - lib/passwordless/url_safe_base_64_generator.rb
72
+ - lib/passwordless/token_digest.rb
112
73
  - lib/passwordless/version.rb
113
74
  homepage: https://github.com/mikker/passwordless
114
75
  licenses:
@@ -129,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
90
  - !ruby/object:Gem::Version
130
91
  version: '0'
131
92
  requirements: []
132
- rubygems_version: 3.4.14
93
+ rubygems_version: 3.4.20
133
94
  signing_key:
134
95
  specification_version: 4
135
96
  summary: Add authentication to your app without all the ickyness of passwords.
@@ -1 +0,0 @@
1
- <%= I18n.t('passwordless.mailer.magic_link', link: @magic_link) %>
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Passwordless
4
- # Generates random numbers for Session records
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
11
- def call(_session)
12
- SecureRandom.urlsafe_base64(32)
13
- end
14
- end
15
- end