passwordless 1.1.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d3bfd49106dd713d65f26a575911bdcb1a903a62273d741f08f1c0b36ea9a77
4
- data.tar.gz: 239fdcce54d30e39f39bb6eb2d7ffdd6c2f2f50e04fe38ec7f53bca000b449d6
3
+ metadata.gz: b88192b582d0e4f8cb601b00f2cf5d51250dbe9f747d6413728cf2e5da7ddd1b
4
+ data.tar.gz: 054c733891aa4e4f98a1f684b6d6107414b4214589f76ec5aa20c7337a5b2098
5
5
  SHA512:
6
- metadata.gz: f7afa9aed4245ed2a3ab13bd7624ec8561d8c0e47db9435a1e415442f6c1d4e6e1770cd9d799095888a189a0485fc9bd5d3fa22e9e3145f2e351e8deee04277b
7
- data.tar.gz: 5f4d8142044cdaff3f9746bad1d184d587b293241ab4f2d0344924fc452cb65590f91840b0db698d535588098cb72098c67f09c2243530177e45f395ab1466a9
6
+ metadata.gz: a760c9c2ade52b80be4a482abb17a2ff9c71579d07bc55eb1268980a39f69540d354bf1988bb4a7ea35d08e801042d9ff266cc2c98913b7ea5c331a1556b882c
7
+ data.tar.gz: f66c443aa783a9f490dac97e4a9720ce077594e4d170ad10053bd42168033ca5c57e1b4f20288e0b5a7bd63eaac67b90b8cd52ba80ac8b9a290d2de2bfb2078e
data/README.md CHANGED
@@ -122,7 +122,7 @@ class UsersController < ApplicationController
122
122
  @user = User.new(user_params)
123
123
 
124
124
  if @user.save
125
- sign_in(build_passwordless_session(@user)) # <-- This!
125
+ sign_in(create_passwordless_session(@user)) # <-- This!
126
126
  redirect_to(@user, flash: { notice: 'Welcome!' })
127
127
  else
128
128
  render(:new)
@@ -146,7 +146,13 @@ passwordless_for :users, at: '/', as: :auth
146
146
  ```
147
147
 
148
148
  Also be sure to
149
- [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
149
+ [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views) and tell the routes as well:
150
+
151
+ ```ruby
152
+ # config/application.rb for example:
153
+ config.action_mailer.default_url_options = {host: "www.example.com"}
154
+ routes.default_url_options[:host] ||= "www.example.com"
155
+ ```
150
156
 
151
157
  ## Configuration
152
158
 
@@ -37,7 +37,11 @@ module Passwordless
37
37
  end
38
38
 
39
39
  redirect_to(
40
- url_for(id: @session.identifier, action: "show"),
40
+ Passwordless.context.path_for(
41
+ @session,
42
+ id: @session.to_param,
43
+ action: "show"
44
+ ),
41
45
  flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
42
46
  )
43
47
  else
@@ -133,6 +137,8 @@ module Passwordless
133
137
  private
134
138
 
135
139
  def artificially_slow_down_brute_force_attacks(token)
140
+ return unless Passwordless.config.combat_brute_force_attacks
141
+
136
142
  # Make it "slow" on purpose to make brute-force attacks more of a hassle
137
143
  BCrypt::Password.create(token)
138
144
  end
@@ -12,16 +12,14 @@ module Passwordless
12
12
  # is still in memory (optional)
13
13
  def sign_in(session, token = nil)
14
14
  @token = token || session.token
15
- @magic_link = url_for(
16
- {
17
- controller: "passwordless/sessions",
18
- action: "confirm",
19
- id: session.identifier,
20
- token: token,
21
- authenticatable: "user",
22
- resource: "users"
23
- }
15
+
16
+ @magic_link = Passwordless.context.url_for(
17
+ session,
18
+ action: "confirm",
19
+ id: session.to_param,
20
+ token: @token
24
21
  )
22
+
25
23
  email_field = session.authenticatable.class.passwordless_email_field
26
24
 
27
25
  mail(
@@ -3,9 +3,10 @@
3
3
  <%= f.label email_field_name,
4
4
  t("passwordless.sessions.new.email.label"),
5
5
  for: "passwordless_#{email_field}" %>
6
- <%= text_field_tag email_field_name,
6
+ <%= email_field_tag email_field_name,
7
7
  params.fetch(email_field_name, nil),
8
8
  required: true,
9
+ autofocus: true,
9
10
  placeholder: t("passwordless.sessions.new.email.placeholder") %>
10
11
  <%= f.submit t("passwordless.sessions.new.submit") %>
11
12
  <% end %>
@@ -31,6 +31,7 @@ module Passwordless
31
31
  option :parent_mailer, default: "ActionMailer::Base"
32
32
  option :restrict_token_reuse, default: true
33
33
  option :token_generator, default: ShortTokenGenerator.new
34
+ option :combat_brute_force_attacks, default: !Rails.env.test?
34
35
 
35
36
  option :expires_at, default: lambda { 1.year.from_now }
36
37
  option :timeout_at, default: lambda { 10.minutes.from_now }
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Passwordless
4
+ class Resource
5
+ def initialize(resource, controller:)
6
+ @resource = resource
7
+ @authenticatable = resource.to_s.singularize.to_sym
8
+ @controller = controller
9
+ end
10
+
11
+ attr_reader :resource, :authenticatable, :controller
12
+
13
+ def defaults
14
+ @defaults ||= {
15
+ authenticatable: authenticatable,
16
+ resource: resource,
17
+ controller: controller
18
+ }
19
+ end
20
+ end
21
+
22
+ class Context
23
+ def initialize
24
+ @resources = {}
25
+ end
26
+
27
+ attr_reader :resources
28
+
29
+ def resource_for(session_or_authenticatable)
30
+ if session_or_authenticatable.is_a?(Session)
31
+ session_or_authenticatable = session_or_authenticatable.authenticatable.model_name.to_s.tableize.to_sym
32
+ end
33
+
34
+ resources[session_or_authenticatable.to_sym]
35
+ end
36
+
37
+ def url_for(session_or_authenticatable, **options)
38
+ unless (resource = resource_for(session_or_authenticatable))
39
+ raise ArgumentError, "No resource registered for #{session_or_authenticatable}"
40
+ end
41
+
42
+ Rails.application.routes.url_helpers.url_for(
43
+ resource.defaults.merge(options)
44
+ )
45
+ end
46
+
47
+ def path_for(session_or_authenticatable, **options)
48
+ url_for(session_or_authenticatable, only_path: true, **options)
49
+ end
50
+ end
51
+ end
@@ -27,15 +27,9 @@ module Passwordless
27
27
 
28
28
  as = as.to_s + "_" unless !as || as.to_s.end_with?("_")
29
29
 
30
- plural = resource.to_s
31
- singular = plural.singularize
30
+ pwless_resource = Passwordless.add_resource(resource, controller: controller)
32
31
 
33
- defaults = {
34
- authenticatable: singular,
35
- resource: resource
36
- }
37
-
38
- scope(defaults: defaults) do
32
+ scope(defaults: pwless_resource.defaults) do
39
33
  get("#{at}/sign_in", to: "#{controller}#new", as: :"#{as}sign_in")
40
34
  post("#{at}/sign_in", to: "#{controller}#create")
41
35
  get("#{at}/sign_in/:id", to: "#{controller}#show", as: :"verify_#{as}sign_in")
@@ -1,33 +1,42 @@
1
1
  module Passwordless
2
2
  module TestHelpers
3
- module TestCase
3
+ module ControllerTestCase
4
+ class H
5
+ extend ControllerHelpers
6
+ end
7
+
4
8
  def passwordless_sign_out(cls = nil)
5
9
  cls ||= "User".constantize
6
- dest = url_for(
7
- {
8
- controller: "passwordless/sessions",
9
- action: "destroy",
10
- authenticatable: cls.model_name.singular,
11
- resource: cls.model_name.to_s.tableize
12
- }
13
- )
10
+ @request.session.delete(H.session_key(cls))
11
+ end
12
+
13
+ def passwordless_sign_in(resource)
14
+ session = Passwordless::Session.create!(authenticatable: resource)
15
+ @request.session[H.session_key(resource.class)] = session.id
16
+ end
17
+ end
18
+
19
+ module RequestTestCase
20
+ def passwordless_sign_out(cls = nil)
21
+ cls ||= "User".constantize
22
+ resource = cls.model_name.to_s.tableize
23
+
24
+ dest = Passwordless.context.path_for(resource, action: "destroy")
14
25
  delete(dest)
26
+
15
27
  follow_redirect!
16
28
  end
17
29
 
18
30
  def passwordless_sign_in(resource)
19
- cls = resource.class
20
31
  session = Passwordless::Session.create!(authenticatable: resource)
21
- magic_link = url_for(
22
- {
23
- controller: "passwordless/sessions",
24
- action: "confirm",
25
- id: session.id,
26
- token: session.token,
27
- authenticatable: cls.model_name.singular,
28
- resource: cls.model_name.to_s.tableize
29
- }
32
+
33
+ magic_link = Passwordless.context.path_for(
34
+ session,
35
+ action: "confirm",
36
+ id: session.to_param,
37
+ token: session.token
30
38
  )
39
+
31
40
  get(magic_link)
32
41
  follow_redirect!
33
42
  end
@@ -36,31 +45,21 @@ module Passwordless
36
45
  module SystemTestCase
37
46
  def passwordless_sign_out(cls = nil)
38
47
  cls ||= "User".constantize
39
- visit(
40
- url_for(
41
- {
42
- controller: "passwordless/sessions",
43
- action: "destroy",
44
- authenticatable: cls.model_name.singular,
45
- resource: cls.model_name.to_s.tableize
46
- }
47
- )
48
- )
48
+ resource = cls.model_name.to_s.tableize
49
+
50
+ visit(Passwordless.context.url_for(resource, action: "destroy"))
49
51
  end
50
52
 
51
53
  def passwordless_sign_in(resource)
52
- cls = resource.class
53
54
  session = Passwordless::Session.create!(authenticatable: resource)
54
- magic_link = url_for(
55
- {
56
- controller: "passwordless/sessions",
57
- action: "confirm",
58
- id: session.id,
59
- token: session.token,
60
- authenticatable: cls.model_name.singular,
61
- resource: cls.model_name.to_s.tableize
62
- }
55
+
56
+ magic_link = Passwordless.context.url_for(
57
+ session,
58
+ action: "confirm",
59
+ id: session.to_param,
60
+ token: session.token
63
61
  )
62
+
64
63
  visit(magic_link)
65
64
  end
66
65
  end
@@ -68,7 +67,7 @@ module Passwordless
68
67
  end
69
68
 
70
69
  if defined?(ActiveSupport::TestCase)
71
- ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers::TestCase)
70
+ ActiveSupport::TestCase.send(:include, ::Passwordless::TestHelpers::ControllerTestCase)
72
71
  end
73
72
 
74
73
  if defined?(ActionDispatch::SystemTestCase)
@@ -77,8 +76,8 @@ end
77
76
 
78
77
  if defined?(RSpec)
79
78
  RSpec.configure do |config|
80
- config.include(::Passwordless::TestHelpers::TestCase, type: :request)
81
- config.include(::Passwordless::TestHelpers::TestCase, type: :controller)
79
+ config.include(::Passwordless::TestHelpers::ControllerTestCase, type: :controller)
80
+ config.include(::Passwordless::TestHelpers::RequestTestCase, type: :request)
82
81
  config.include(::Passwordless::TestHelpers::SystemTestCase, type: :system)
83
82
  end
84
83
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Passwordless
4
4
  # :nodoc:
5
- VERSION = "1.1.0"
5
+ VERSION = "1.2.0"
6
6
  end
data/lib/passwordless.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support"
4
4
  require "passwordless/config"
5
+ require "passwordless/context"
5
6
  require "passwordless/errors"
6
7
  require "passwordless/engine"
7
8
  require "passwordless/token_digest"
@@ -10,6 +11,14 @@ require "passwordless/token_digest"
10
11
  module Passwordless
11
12
  extend Configurable
12
13
 
14
+ def self.context
15
+ @context ||= Context.new
16
+ end
17
+
18
+ def self.add_resource(resource, controller:, **defaults)
19
+ context.resources[resource] = Resource.new(resource, controller: controller)
20
+ end
21
+
13
22
  def self.digest(token)
14
23
  TokenDigest.new(token).digest
15
24
  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: 1.1.0
4
+ version: 1.2.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-11-07 00:00:00.000000000 Z
11
+ date: 2023-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -62,6 +62,7 @@ files:
62
62
  - lib/generators/passwordless/views_generator.rb
63
63
  - lib/passwordless.rb
64
64
  - lib/passwordless/config.rb
65
+ - lib/passwordless/context.rb
65
66
  - lib/passwordless/controller_helpers.rb
66
67
  - lib/passwordless/engine.rb
67
68
  - lib/passwordless/errors.rb
@@ -90,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
91
  - !ruby/object:Gem::Version
91
92
  version: '0'
92
93
  requirements: []
93
- rubygems_version: 3.4.21
94
+ rubygems_version: 3.4.22
94
95
  signing_key:
95
96
  specification_version: 4
96
97
  summary: Add authentication to your app without all the ickyness of passwords.