rsb-auth 0.9.1 → 0.9.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 +4 -4
- data/app/controllers/rsb/auth/sessions_controller.rb +2 -1
- data/app/services/rsb/auth/authentication_service.rb +26 -8
- data/db/migrate/20260216100001_create_rsb_auth_tables.rb +80 -0
- data/lib/rsb/auth/engine.rb +2 -2
- data/lib/rsb/auth/settings_schema.rb +6 -0
- data/lib/rsb/auth/test_helper.rb +5 -0
- metadata +4 -12
- data/db/migrate/20260208100001_create_rsb_auth_identities.rb +0 -12
- data/db/migrate/20260208100002_create_rsb_auth_credentials.rb +0 -20
- data/db/migrate/20260208100003_create_rsb_auth_sessions.rb +0 -18
- data/db/migrate/20260208100004_create_rsb_auth_password_reset_tokens.rb +0 -15
- data/db/migrate/20260208100005_add_verification_to_rsb_auth_credentials.rb +0 -9
- data/db/migrate/20260208100006_create_rsb_auth_invitations.rb +0 -19
- data/db/migrate/20260211100001_add_revoked_at_to_rsb_auth_credentials.rb +0 -16
- data/db/migrate/20260212100001_add_deleted_at_to_rsb_auth_identities.rb +0 -10
- data/db/migrate/20260214172956_add_recovery_email_to_rsb_auth_credentials.rb +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 796fc07fdbd135fee58327f302a10989bba92cdbd9d060df01a9b479361b834d
|
|
4
|
+
data.tar.gz: 8b3f4d530bcedc6b96a9e139f2353474cc1f803cc5833d9a568554c4097517a3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f329589372f35a266a7ba67fde98eed5b1f09d7b5113fc0a74ac81a22b6f5284f39791ef3b5cd3cf759a14f5880b5ae503e30f7d6849b7c503420c51bcdd93e7
|
|
7
|
+
data.tar.gz: 91565f320f7a1f32db2bab78a451b83b40f0b175325647b2157d31336a21b1c970ccc6d7cfa3150ff9cbaeed5d77d5baa1c1acbe467b0b370f2b6b0cdda9353f
|
|
@@ -51,7 +51,8 @@ module RSB
|
|
|
51
51
|
cookies.signed[:rsb_session_token] = {
|
|
52
52
|
value: session_record.token,
|
|
53
53
|
httponly: true,
|
|
54
|
-
same_site: :lax
|
|
54
|
+
same_site: :lax,
|
|
55
|
+
secure: Rails.env.production?
|
|
55
56
|
}
|
|
56
57
|
if result.identity.complete?
|
|
57
58
|
redirect_to main_app.root_path, notice: 'Signed in.'
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'bcrypt'
|
|
4
|
+
|
|
3
5
|
module RSB
|
|
4
6
|
module Auth
|
|
5
7
|
class AuthenticationService
|
|
6
8
|
Result = Data.define(:success?, :identity, :credential, :error, :unverified)
|
|
7
9
|
|
|
10
|
+
# Cached bcrypt digest for timing attack prevention.
|
|
11
|
+
DUMMY_DIGEST = BCrypt::Password.create('dummy_password_for_timing')
|
|
12
|
+
|
|
8
13
|
# Authenticates by identifier and password. Only active (non-revoked) credentials
|
|
9
14
|
# are considered. Also checks that the credential's type is currently enabled
|
|
10
15
|
# via settings.
|
|
@@ -13,24 +18,29 @@ module RSB
|
|
|
13
18
|
# @param password [String] password
|
|
14
19
|
# @return [Result] success? with identity/credential, or error message
|
|
15
20
|
def call(identifier:, password:)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
normalized = identifier.strip.downcase
|
|
22
|
+
credential = RSB::Auth::Credential.active.find_by(identifier: normalized)
|
|
23
|
+
|
|
24
|
+
unless credential
|
|
25
|
+
# Perform a dummy bcrypt comparison to equalize response timing
|
|
26
|
+
DUMMY_DIGEST.is_password?(password)
|
|
27
|
+
return Result.new(success?: false, identity: nil, credential: nil, error: 'Invalid credentials.',
|
|
28
|
+
unverified: false)
|
|
29
|
+
end
|
|
19
30
|
|
|
20
|
-
return failure('Invalid credentials.') unless credential
|
|
21
31
|
return failure('Invalid credentials.') if credential.identity.deleted?
|
|
22
32
|
|
|
23
33
|
# Check credential type is enabled
|
|
24
34
|
credential_type_key = derive_credential_type_key(credential)
|
|
25
35
|
unless RSB::Auth.credentials.enabled?(credential_type_key)
|
|
26
|
-
return failure('This sign-in method is not available.')
|
|
36
|
+
return failure(error_message('This sign-in method is not available.'))
|
|
27
37
|
end
|
|
28
38
|
|
|
29
|
-
return failure('Account is locked. Try again later.') if credential.locked?
|
|
30
|
-
return failure('Account is suspended.') if credential.identity.suspended?
|
|
39
|
+
return failure(error_message('Account is locked. Try again later.')) if credential.locked?
|
|
40
|
+
return failure(error_message('Account is suspended.')) if credential.identity.suspended?
|
|
31
41
|
|
|
32
42
|
if credential.authenticate(password)
|
|
33
|
-
credential.update_columns(failed_attempts: 0)
|
|
43
|
+
credential.update_columns(failed_attempts: 0) if credential.failed_attempts.positive?
|
|
34
44
|
|
|
35
45
|
# Per-credential verification check
|
|
36
46
|
verif_required = ActiveModel::Type::Boolean.new.cast(
|
|
@@ -60,6 +70,14 @@ module RSB
|
|
|
60
70
|
|
|
61
71
|
private
|
|
62
72
|
|
|
73
|
+
def error_message(specific_message)
|
|
74
|
+
if RSB::Settings.get('auth.generic_error_messages')
|
|
75
|
+
'Invalid credentials.'
|
|
76
|
+
else
|
|
77
|
+
specific_message
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
63
81
|
# Derives the credential type key from a credential's STI type.
|
|
64
82
|
# E.g., "RSB::Auth::Credential::EmailPassword" -> :email_password
|
|
65
83
|
#
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateRSBAuthTables < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :rsb_auth_identities do |t|
|
|
6
|
+
t.string :status, null: false, default: 'active'
|
|
7
|
+
t.json :metadata, default: {}
|
|
8
|
+
t.datetime :deleted_at
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :rsb_auth_identities, :deleted_at,
|
|
14
|
+
where: 'deleted_at IS NOT NULL',
|
|
15
|
+
name: 'index_rsb_auth_identities_on_deleted_at'
|
|
16
|
+
|
|
17
|
+
create_table :rsb_auth_credentials do |t|
|
|
18
|
+
t.references :identity, null: false, foreign_key: { to_table: :rsb_auth_identities }
|
|
19
|
+
t.string :type, null: false
|
|
20
|
+
t.string :identifier, null: false
|
|
21
|
+
t.string :password_digest, null: false
|
|
22
|
+
t.json :metadata, default: {}
|
|
23
|
+
t.datetime :verified_at
|
|
24
|
+
t.string :verification_token
|
|
25
|
+
t.datetime :verification_sent_at
|
|
26
|
+
t.integer :failed_attempts, null: false, default: 0
|
|
27
|
+
t.datetime :locked_until
|
|
28
|
+
t.datetime :revoked_at
|
|
29
|
+
t.string :recovery_email
|
|
30
|
+
|
|
31
|
+
t.timestamps
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
add_index :rsb_auth_credentials, %i[type identifier],
|
|
35
|
+
unique: true,
|
|
36
|
+
where: 'revoked_at IS NULL',
|
|
37
|
+
name: 'index_rsb_auth_credentials_on_type_and_identifier_active'
|
|
38
|
+
add_index :rsb_auth_credentials, :verification_token, unique: true
|
|
39
|
+
add_index :rsb_auth_credentials, :recovery_email
|
|
40
|
+
|
|
41
|
+
create_table :rsb_auth_sessions do |t|
|
|
42
|
+
t.references :identity, null: false, foreign_key: { to_table: :rsb_auth_identities }
|
|
43
|
+
t.string :token, null: false
|
|
44
|
+
t.string :ip_address
|
|
45
|
+
t.string :user_agent
|
|
46
|
+
t.datetime :last_active_at
|
|
47
|
+
t.datetime :expires_at, null: false
|
|
48
|
+
|
|
49
|
+
t.timestamps
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
add_index :rsb_auth_sessions, :token, unique: true
|
|
53
|
+
add_index :rsb_auth_sessions, :expires_at
|
|
54
|
+
|
|
55
|
+
create_table :rsb_auth_password_reset_tokens do |t|
|
|
56
|
+
t.references :credential, null: false, foreign_key: { to_table: :rsb_auth_credentials }
|
|
57
|
+
t.string :token, null: false
|
|
58
|
+
t.datetime :expires_at, null: false
|
|
59
|
+
t.datetime :used_at
|
|
60
|
+
|
|
61
|
+
t.timestamps
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
add_index :rsb_auth_password_reset_tokens, :token, unique: true
|
|
65
|
+
|
|
66
|
+
create_table :rsb_auth_invitations do |t|
|
|
67
|
+
t.string :email, null: false
|
|
68
|
+
t.string :token, null: false
|
|
69
|
+
t.references :invited_by, polymorphic: true, null: true
|
|
70
|
+
t.datetime :accepted_at
|
|
71
|
+
t.datetime :expires_at, null: false
|
|
72
|
+
t.datetime :revoked_at
|
|
73
|
+
|
|
74
|
+
t.timestamps
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
add_index :rsb_auth_invitations, :token, unique: true
|
|
78
|
+
add_index :rsb_auth_invitations, :email
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/rsb/auth/engine.rb
CHANGED
|
@@ -28,7 +28,7 @@ module RSB
|
|
|
28
28
|
class_name: 'RSB::Auth::Credential::EmailPassword',
|
|
29
29
|
authenticatable: true,
|
|
30
30
|
registerable: true,
|
|
31
|
-
label: 'Email
|
|
31
|
+
label: 'Email',
|
|
32
32
|
icon: 'mail',
|
|
33
33
|
form_partial: 'rsb/auth/credentials/email_password',
|
|
34
34
|
admin_form_partial: 'rsb/auth/admin/credentials/email_password'
|
|
@@ -41,7 +41,7 @@ module RSB
|
|
|
41
41
|
class_name: 'RSB::Auth::Credential::UsernamePassword',
|
|
42
42
|
authenticatable: true,
|
|
43
43
|
registerable: true,
|
|
44
|
-
label: 'Username
|
|
44
|
+
label: 'Username',
|
|
45
45
|
icon: 'user',
|
|
46
46
|
form_partial: 'rsb/auth/credentials/username_password',
|
|
47
47
|
admin_form_partial: 'rsb/auth/admin/credentials/username_password'
|
|
@@ -67,6 +67,12 @@ module RSB
|
|
|
67
67
|
group: 'Features',
|
|
68
68
|
depends_on: 'auth.account_enabled',
|
|
69
69
|
description: 'Enable self-service account deletion'
|
|
70
|
+
|
|
71
|
+
setting :generic_error_messages,
|
|
72
|
+
type: :boolean,
|
|
73
|
+
default: false,
|
|
74
|
+
group: 'Security',
|
|
75
|
+
description: 'When enabled, all login failures return "Invalid credentials" regardless of the actual reason (locked, suspended, disabled). Prevents account enumeration via error message differentiation.'
|
|
70
76
|
end
|
|
71
77
|
end
|
|
72
78
|
end
|
data/lib/rsb/auth/test_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rsb-auth
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aleksandr Marchenko
|
|
@@ -43,14 +43,14 @@ dependencies:
|
|
|
43
43
|
requirements:
|
|
44
44
|
- - '='
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: 0.9.
|
|
46
|
+
version: 0.9.3
|
|
47
47
|
type: :runtime
|
|
48
48
|
prerelease: false
|
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - '='
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 0.9.
|
|
53
|
+
version: 0.9.3
|
|
54
54
|
- !ruby/object:Gem::Dependency
|
|
55
55
|
name: useragent
|
|
56
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -142,15 +142,7 @@ files:
|
|
|
142
142
|
- config/locales/credentials.en.yml
|
|
143
143
|
- config/locales/seo.en.yml
|
|
144
144
|
- config/routes.rb
|
|
145
|
-
- db/migrate/
|
|
146
|
-
- db/migrate/20260208100002_create_rsb_auth_credentials.rb
|
|
147
|
-
- db/migrate/20260208100003_create_rsb_auth_sessions.rb
|
|
148
|
-
- db/migrate/20260208100004_create_rsb_auth_password_reset_tokens.rb
|
|
149
|
-
- db/migrate/20260208100005_add_verification_to_rsb_auth_credentials.rb
|
|
150
|
-
- db/migrate/20260208100006_create_rsb_auth_invitations.rb
|
|
151
|
-
- db/migrate/20260211100001_add_revoked_at_to_rsb_auth_credentials.rb
|
|
152
|
-
- db/migrate/20260212100001_add_deleted_at_to_rsb_auth_identities.rb
|
|
153
|
-
- db/migrate/20260214172956_add_recovery_email_to_rsb_auth_credentials.rb
|
|
145
|
+
- db/migrate/20260216100001_create_rsb_auth_tables.rb
|
|
154
146
|
- lib/generators/rsb/auth/install/install_generator.rb
|
|
155
147
|
- lib/generators/rsb/auth/views/views_generator.rb
|
|
156
148
|
- lib/rsb/auth.rb
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRSBAuthIdentities < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :rsb_auth_identities do |t|
|
|
6
|
-
t.string :status, null: false, default: 'active'
|
|
7
|
-
t.json :metadata, default: {}
|
|
8
|
-
|
|
9
|
-
t.timestamps
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRSBAuthCredentials < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :rsb_auth_credentials do |t|
|
|
6
|
-
t.references :identity, null: false, foreign_key: { to_table: :rsb_auth_identities }
|
|
7
|
-
t.string :type, null: false
|
|
8
|
-
t.string :identifier, null: false
|
|
9
|
-
t.string :password_digest, null: false
|
|
10
|
-
t.json :metadata, default: {}
|
|
11
|
-
t.datetime :verified_at
|
|
12
|
-
t.integer :failed_attempts, null: false, default: 0
|
|
13
|
-
t.datetime :locked_until
|
|
14
|
-
|
|
15
|
-
t.timestamps
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
add_index :rsb_auth_credentials, %i[type identifier], unique: true
|
|
19
|
-
end
|
|
20
|
-
end
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRSBAuthSessions < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :rsb_auth_sessions do |t|
|
|
6
|
-
t.references :identity, null: false, foreign_key: { to_table: :rsb_auth_identities }
|
|
7
|
-
t.string :token, null: false
|
|
8
|
-
t.string :ip_address
|
|
9
|
-
t.string :user_agent
|
|
10
|
-
t.datetime :last_active_at
|
|
11
|
-
t.datetime :expires_at, null: false
|
|
12
|
-
t.timestamps
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
add_index :rsb_auth_sessions, :token, unique: true
|
|
16
|
-
add_index :rsb_auth_sessions, :expires_at
|
|
17
|
-
end
|
|
18
|
-
end
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRSBAuthPasswordResetTokens < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :rsb_auth_password_reset_tokens do |t|
|
|
6
|
-
t.references :credential, null: false, foreign_key: { to_table: :rsb_auth_credentials }
|
|
7
|
-
t.string :token, null: false
|
|
8
|
-
t.datetime :expires_at, null: false
|
|
9
|
-
t.datetime :used_at
|
|
10
|
-
t.timestamps
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
add_index :rsb_auth_password_reset_tokens, :token, unique: true
|
|
14
|
-
end
|
|
15
|
-
end
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddVerificationToRSBAuthCredentials < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
add_column :rsb_auth_credentials, :verification_token, :string
|
|
6
|
-
add_column :rsb_auth_credentials, :verification_sent_at, :datetime
|
|
7
|
-
add_index :rsb_auth_credentials, :verification_token, unique: true
|
|
8
|
-
end
|
|
9
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRSBAuthInvitations < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
create_table :rsb_auth_invitations do |t|
|
|
6
|
-
t.string :email, null: false
|
|
7
|
-
t.string :token, null: false
|
|
8
|
-
t.references :invited_by, polymorphic: true, null: true
|
|
9
|
-
t.datetime :accepted_at
|
|
10
|
-
t.datetime :expires_at, null: false
|
|
11
|
-
t.datetime :revoked_at
|
|
12
|
-
|
|
13
|
-
t.timestamps
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
add_index :rsb_auth_invitations, :token, unique: true
|
|
17
|
-
add_index :rsb_auth_invitations, :email
|
|
18
|
-
end
|
|
19
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddRevokedAtToRSBAuthCredentials < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
add_column :rsb_auth_credentials, :revoked_at, :datetime, null: true
|
|
6
|
-
|
|
7
|
-
# Replace full unique index with partial unique index scoped to active credentials.
|
|
8
|
-
# This allows the same [type, identifier] to exist multiple times as long as
|
|
9
|
-
# only one is active (revoked_at IS NULL). Supported by PostgreSQL and SQLite 3.8.0+.
|
|
10
|
-
remove_index :rsb_auth_credentials, %i[type identifier]
|
|
11
|
-
add_index :rsb_auth_credentials, %i[type identifier],
|
|
12
|
-
unique: true,
|
|
13
|
-
where: 'revoked_at IS NULL',
|
|
14
|
-
name: 'index_rsb_auth_credentials_on_type_and_identifier_active'
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class AddDeletedAtToRSBAuthIdentities < ActiveRecord::Migration[8.1]
|
|
4
|
-
def change
|
|
5
|
-
add_column :rsb_auth_identities, :deleted_at, :datetime, null: true
|
|
6
|
-
add_index :rsb_auth_identities, :deleted_at,
|
|
7
|
-
where: 'deleted_at IS NOT NULL',
|
|
8
|
-
name: 'index_rsb_auth_identities_on_deleted_at'
|
|
9
|
-
end
|
|
10
|
-
end
|