passwordless 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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.