rsb-auth 0.9.1
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 +7 -0
- data/LICENSE +15 -0
- data/README.md +76 -0
- data/Rakefile +25 -0
- data/app/controllers/concerns/rsb/auth/ensure_identity_complete.rb +72 -0
- data/app/controllers/concerns/rsb/auth/rate_limitable.rb +20 -0
- data/app/controllers/rsb/auth/account/login_methods_controller.rb +85 -0
- data/app/controllers/rsb/auth/account/sessions_controller.rb +31 -0
- data/app/controllers/rsb/auth/account_controller.rb +99 -0
- data/app/controllers/rsb/auth/admin/identities_controller.rb +402 -0
- data/app/controllers/rsb/auth/admin/sessions_management_controller.rb +27 -0
- data/app/controllers/rsb/auth/application_controller.rb +50 -0
- data/app/controllers/rsb/auth/invitations_controller.rb +31 -0
- data/app/controllers/rsb/auth/password_resets_controller.rb +46 -0
- data/app/controllers/rsb/auth/registrations_controller.rb +104 -0
- data/app/controllers/rsb/auth/sessions_controller.rb +109 -0
- data/app/controllers/rsb/auth/verifications_controller.rb +29 -0
- data/app/helpers/rsb/auth/user_agent_helper.rb +22 -0
- data/app/mailers/rsb/auth/application_mailer.rb +10 -0
- data/app/mailers/rsb/auth/auth_mailer.rb +33 -0
- data/app/models/rsb/auth/application_record.rb +10 -0
- data/app/models/rsb/auth/credential/email_password.rb +9 -0
- data/app/models/rsb/auth/credential/phone_password.rb +16 -0
- data/app/models/rsb/auth/credential/username_password.rb +9 -0
- data/app/models/rsb/auth/credential.rb +122 -0
- data/app/models/rsb/auth/identity.rb +62 -0
- data/app/models/rsb/auth/invitation.rb +55 -0
- data/app/models/rsb/auth/password_reset_token.rb +36 -0
- data/app/models/rsb/auth/session.rb +44 -0
- data/app/services/rsb/auth/account_service.rb +140 -0
- data/app/services/rsb/auth/authentication_service.rb +86 -0
- data/app/services/rsb/auth/invitation_service.rb +53 -0
- data/app/services/rsb/auth/password_reset_service.rb +48 -0
- data/app/services/rsb/auth/registration_service.rb +108 -0
- data/app/services/rsb/auth/session_service.rb +47 -0
- data/app/services/rsb/auth/verification_service.rb +30 -0
- data/app/views/layouts/rsb/auth/application.html.erb +76 -0
- data/app/views/rsb/auth/account/_identity_fields.html.erb +3 -0
- data/app/views/rsb/auth/account/confirm_destroy.html.erb +45 -0
- data/app/views/rsb/auth/account/login_methods/show.html.erb +92 -0
- data/app/views/rsb/auth/account/show.html.erb +110 -0
- data/app/views/rsb/auth/admin/credentials/_email_password.html.erb +34 -0
- data/app/views/rsb/auth/admin/credentials/_phone_password.html.erb +34 -0
- data/app/views/rsb/auth/admin/credentials/_username_password.html.erb +34 -0
- data/app/views/rsb/auth/admin/identities/index.html.erb +76 -0
- data/app/views/rsb/auth/admin/identities/new.html.erb +46 -0
- data/app/views/rsb/auth/admin/identities/new_credential.html.erb +45 -0
- data/app/views/rsb/auth/admin/identities/show.html.erb +180 -0
- data/app/views/rsb/auth/admin/sessions_management/index.html.erb +69 -0
- data/app/views/rsb/auth/auth_mailer/invitation.html.erb +4 -0
- data/app/views/rsb/auth/auth_mailer/password_reset.html.erb +4 -0
- data/app/views/rsb/auth/auth_mailer/verification.html.erb +4 -0
- data/app/views/rsb/auth/credentials/_email_password_login.html.erb +36 -0
- data/app/views/rsb/auth/credentials/_email_password_signup.html.erb +45 -0
- data/app/views/rsb/auth/credentials/_icon.html.erb +21 -0
- data/app/views/rsb/auth/credentials/_phone_password_login.html.erb +33 -0
- data/app/views/rsb/auth/credentials/_phone_password_signup.html.erb +45 -0
- data/app/views/rsb/auth/credentials/_selector.html.erb +43 -0
- data/app/views/rsb/auth/credentials/_username_password_login.html.erb +33 -0
- data/app/views/rsb/auth/credentials/_username_password_signup.html.erb +54 -0
- data/app/views/rsb/auth/invitations/show.html.erb +40 -0
- data/app/views/rsb/auth/password_resets/edit.html.erb +41 -0
- data/app/views/rsb/auth/password_resets/new.html.erb +27 -0
- data/app/views/rsb/auth/registrations/new.html.erb +55 -0
- data/app/views/rsb/auth/sessions/new.html.erb +47 -0
- data/config/locales/account.en.yml +65 -0
- data/config/locales/admin.en.yml +26 -0
- data/config/locales/credentials.en.yml +11 -0
- data/config/locales/seo.en.yml +28 -0
- data/config/routes.rb +34 -0
- data/db/migrate/20260208100001_create_rsb_auth_identities.rb +12 -0
- data/db/migrate/20260208100002_create_rsb_auth_credentials.rb +20 -0
- data/db/migrate/20260208100003_create_rsb_auth_sessions.rb +18 -0
- data/db/migrate/20260208100004_create_rsb_auth_password_reset_tokens.rb +15 -0
- data/db/migrate/20260208100005_add_verification_to_rsb_auth_credentials.rb +9 -0
- data/db/migrate/20260208100006_create_rsb_auth_invitations.rb +19 -0
- data/db/migrate/20260211100001_add_revoked_at_to_rsb_auth_credentials.rb +16 -0
- data/db/migrate/20260212100001_add_deleted_at_to_rsb_auth_identities.rb +10 -0
- data/db/migrate/20260214172956_add_recovery_email_to_rsb_auth_credentials.rb +8 -0
- data/lib/generators/rsb/auth/install/install_generator.rb +31 -0
- data/lib/generators/rsb/auth/views/views_generator.rb +197 -0
- data/lib/rsb/auth/configuration.rb +59 -0
- data/lib/rsb/auth/credential_conflict_error.rb +21 -0
- data/lib/rsb/auth/credential_definition.rb +39 -0
- data/lib/rsb/auth/credential_deprecation_bridge.rb +81 -0
- data/lib/rsb/auth/credential_registry.rb +96 -0
- data/lib/rsb/auth/credential_settings_registrar.rb +118 -0
- data/lib/rsb/auth/engine.rb +187 -0
- data/lib/rsb/auth/lifecycle_handler.rb +71 -0
- data/lib/rsb/auth/settings_schema.rb +74 -0
- data/lib/rsb/auth/test_helper.rb +139 -0
- data/lib/rsb/auth/version.rb +9 -0
- data/lib/rsb/auth.rb +49 -0
- metadata +192 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c3eb90b542687f335662080f73af84d46e55348664b2e99193452555715b5772
|
|
4
|
+
data.tar.gz: 5f980b6c9c0042304eb15c8f7f114461bece133f02bea55ba088006e4c915b34
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: f333c9fa678dba4bb82b544178ef99654ffbacf1108b2b2bcb7b89b48196357f2a407710a35ab975113a52521f3d00c8680e0f068ed61f76c5f8a5b1fdae9f0b
|
|
7
|
+
data.tar.gz: 8b26d96dd17ef6892d025faa04704ce502e01e632be6ae37e06c40cbe756d72c3bfaebdc634ddd9ec026aeca79a89a680b427e16bf6ae92f9cab98648d633358
|
data/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Rails SaaS Builder (RSB)
|
|
2
|
+
Copyright (C) 2026 Aleksandr Marchenko
|
|
3
|
+
|
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
|
5
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
|
+
|
|
9
|
+
This program is distributed in the hope that it will be useful,
|
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
GNU Lesser General Public License for more details.
|
|
13
|
+
|
|
14
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
15
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
data/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# rsb-auth
|
|
2
|
+
|
|
3
|
+
Identity and authentication engine for Rails SaaS Builder. Provides a flexible identity system with pluggable credential types (email+password, username+password out of the box), session management, password reset, email verification, and user invitations. Extensible via a credential registry for custom auth methods.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### As part of Rails SaaS Builder
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "rails-saas-builder"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Standalone
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "rsb-auth"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then run:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
bundle install
|
|
23
|
+
rails generate rsb_auth:install
|
|
24
|
+
rails db:migrate
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Key Features
|
|
28
|
+
|
|
29
|
+
- Pluggable credential types with auto-detection
|
|
30
|
+
- Session management with configurable TTL and max concurrent sessions
|
|
31
|
+
- Email verification with secure token flow
|
|
32
|
+
- Password reset with 2-hour expiry tokens
|
|
33
|
+
- User invitations with 7-day expiry
|
|
34
|
+
- Account management (profile, password change, deletion)
|
|
35
|
+
- Rate limiting and lockout protection
|
|
36
|
+
- Configurable registration modes (open, invite-only, disabled)
|
|
37
|
+
- Extensible identity model via `identity_concerns`
|
|
38
|
+
- Pre-built auth views (login, registration, password reset, account)
|
|
39
|
+
|
|
40
|
+
## Basic Usage
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# Register a credential type
|
|
44
|
+
RSB::Auth.credentials.register(
|
|
45
|
+
RSB::Auth::CredentialDefinition.new(
|
|
46
|
+
key: :email_password,
|
|
47
|
+
class_name: "RSB::Auth::Credential::EmailPassword",
|
|
48
|
+
authenticatable: true,
|
|
49
|
+
registerable: true,
|
|
50
|
+
label: "Email & Password"
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Extend the Identity model
|
|
55
|
+
RSB::Auth.configure do |config|
|
|
56
|
+
config.identity_concerns = [MyApp::HasProfile]
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
RSB::Auth.configure do |config|
|
|
64
|
+
config.lifecycle_handler = "MyApp::AuthHandler"
|
|
65
|
+
config.identity_concerns = [MyApp::HasProfile]
|
|
66
|
+
config.permitted_account_params = [:name, :avatar_url]
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Documentation
|
|
71
|
+
|
|
72
|
+
Part of [Rails SaaS Builder](../README.md). See the main README for the full picture.
|
|
73
|
+
|
|
74
|
+
## License
|
|
75
|
+
|
|
76
|
+
[LGPL-3.0](../LICENSE)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
require 'rake/testtask'
|
|
6
|
+
|
|
7
|
+
task :prepare_test_db do
|
|
8
|
+
ENV['RAILS_ENV'] = 'test'
|
|
9
|
+
db = File.expand_path('test/dummy/db/test.sqlite3', __dir__)
|
|
10
|
+
FileUtils.rm_f(db)
|
|
11
|
+
require_relative 'test/dummy/config/environment'
|
|
12
|
+
ActiveRecord::Migration.verbose = false
|
|
13
|
+
ActiveRecord::MigrationContext.new(Rails.application.paths['db/migrate'].to_a).migrate
|
|
14
|
+
schema = File.expand_path('test/dummy/db/schema.rb', __dir__)
|
|
15
|
+
File.open(schema, 'w') { |f| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection_pool, f) }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
Rake::TestTask.new(:test) do |t|
|
|
19
|
+
t.libs << 'test'
|
|
20
|
+
t.pattern = 'test/**/*_test.rb'
|
|
21
|
+
t.verbose = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task test: :prepare_test_db
|
|
25
|
+
task default: :test
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSB
|
|
4
|
+
module Auth
|
|
5
|
+
# Opt-in concern for host app controllers that redirects users with
|
|
6
|
+
# incomplete identities to the account edit page.
|
|
7
|
+
#
|
|
8
|
+
# Include in your ApplicationController to enforce profile completion:
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# class ApplicationController < ActionController::Base
|
|
12
|
+
# include RSB::Auth::EnsureIdentityComplete
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# The concern adds a +before_action+ that checks +current_identity.complete?+
|
|
16
|
+
# (from RFC-010). If the identity is incomplete, the user is redirected to
|
|
17
|
+
# +/auth/account+ with a flash message.
|
|
18
|
+
#
|
|
19
|
+
# Auth engine routes are skipped to prevent redirect loops (e.g., the account
|
|
20
|
+
# page itself, login, registration).
|
|
21
|
+
#
|
|
22
|
+
module EnsureIdentityComplete
|
|
23
|
+
extend ActiveSupport::Concern
|
|
24
|
+
|
|
25
|
+
included do
|
|
26
|
+
before_action :ensure_identity_complete
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# Redirects to account edit if the current identity exists but is incomplete.
|
|
32
|
+
# Skips check for auth engine routes to prevent redirect loops.
|
|
33
|
+
#
|
|
34
|
+
# @return [void]
|
|
35
|
+
def ensure_identity_complete
|
|
36
|
+
identity = current_identity_for_completion_check
|
|
37
|
+
return unless identity
|
|
38
|
+
return if identity.complete?
|
|
39
|
+
return if rsb_auth_engine_route?
|
|
40
|
+
|
|
41
|
+
redirect_to RSB::Auth::Engine.routes.url_helpers.account_path,
|
|
42
|
+
alert: t('rsb.auth.account.complete_profile')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Resolves the current identity from the session cookie so the concern
|
|
46
|
+
# works in the host app without requiring the host to define +current_identity+.
|
|
47
|
+
#
|
|
48
|
+
# @return [RSB::Auth::Identity, nil]
|
|
49
|
+
def current_identity_for_completion_check
|
|
50
|
+
session = RSB::Auth::SessionService.new.find_by_token(
|
|
51
|
+
cookies.signed[:rsb_session_token]
|
|
52
|
+
)
|
|
53
|
+
session&.identity
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Checks whether the current request is handled by the rsb-auth engine.
|
|
57
|
+
# Used to skip the completion check on auth routes (login, registration,
|
|
58
|
+
# account edit) to prevent redirect loops.
|
|
59
|
+
#
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def rsb_auth_engine_route?
|
|
62
|
+
script_name = RSB::Auth::Engine.routes.find_script_name({})
|
|
63
|
+
return self.class.module_parents.include?(RSB::Auth) if script_name.nil? || script_name.empty?
|
|
64
|
+
|
|
65
|
+
request.path.start_with?(script_name)
|
|
66
|
+
rescue StandardError
|
|
67
|
+
# Fallback: check if controller is under RSB::Auth namespace
|
|
68
|
+
self.class.module_parents.include?(RSB::Auth)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSB
|
|
4
|
+
module Auth
|
|
5
|
+
module RateLimitable
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def throttle!(key:, limit: 10, period: 60)
|
|
11
|
+
cache_key = "rsb_throttle:#{key}:#{request.remote_ip}"
|
|
12
|
+
count = Rails.cache.increment(cache_key, 1, expires_in: period.seconds, initial: 0)
|
|
13
|
+
|
|
14
|
+
return unless count > limit
|
|
15
|
+
|
|
16
|
+
render plain: 'Too many requests. Try again later.', status: :too_many_requests
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSB
|
|
4
|
+
module Auth
|
|
5
|
+
module Account
|
|
6
|
+
class LoginMethodsController < RSB::Auth::ApplicationController
|
|
7
|
+
layout 'rsb/auth/application'
|
|
8
|
+
|
|
9
|
+
include RSB::Auth::RateLimitable
|
|
10
|
+
|
|
11
|
+
before_action :require_authentication
|
|
12
|
+
before_action :set_credential
|
|
13
|
+
before_action -> { throttle!(key: 'change_password', limit: 10, period: 60) }, only: :change_password
|
|
14
|
+
|
|
15
|
+
# Renders the login method detail page for a specific credential.
|
|
16
|
+
# Shows identifier, type, verification status, change password form,
|
|
17
|
+
# and optional remove button.
|
|
18
|
+
#
|
|
19
|
+
# @route GET /auth/account/login_methods/:id
|
|
20
|
+
def show
|
|
21
|
+
@can_remove = current_identity.active_credentials.count > 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Changes the password on a specific credential.
|
|
25
|
+
# Delegates to AccountService#change_password which verifies the current
|
|
26
|
+
# password and revokes all other sessions on success.
|
|
27
|
+
#
|
|
28
|
+
# @route PATCH /auth/account/login_methods/:id/password
|
|
29
|
+
def change_password
|
|
30
|
+
result = RSB::Auth::AccountService.new.change_password(
|
|
31
|
+
credential: @credential,
|
|
32
|
+
current_password: params[:current_password],
|
|
33
|
+
new_password: params[:new_password],
|
|
34
|
+
new_password_confirmation: params[:new_password_confirmation],
|
|
35
|
+
current_session: current_session
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if result.success?
|
|
39
|
+
redirect_to account_login_method_path(@credential), notice: t('rsb.auth.account.password_changed')
|
|
40
|
+
else
|
|
41
|
+
@password_errors = result.errors
|
|
42
|
+
@can_remove = current_identity.active_credentials.count > 1
|
|
43
|
+
render :show, status: :unprocessable_entity
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Revokes (removes) a login method.
|
|
48
|
+
# Guards against removing the last active credential.
|
|
49
|
+
#
|
|
50
|
+
# @route DELETE /auth/account/login_methods/:id
|
|
51
|
+
def destroy
|
|
52
|
+
if current_identity.active_credentials.count <= 1
|
|
53
|
+
redirect_to account_path, alert: t('rsb.auth.account.cannot_remove_last')
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@credential.revoke!
|
|
58
|
+
redirect_to account_path, notice: t('rsb.auth.account.login_method_removed')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Resends verification email for an unverified credential.
|
|
62
|
+
#
|
|
63
|
+
# @route POST /auth/account/login_methods/:id/resend_verification
|
|
64
|
+
def resend_verification
|
|
65
|
+
if @credential.verified?
|
|
66
|
+
redirect_to account_login_method_path(@credential), alert: t('rsb.auth.account.already_verified')
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
RSB::Auth::VerificationService.new.send_verification(@credential)
|
|
71
|
+
redirect_to account_login_method_path(@credential), notice: t('rsb.auth.account.verification_sent')
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Scoped to current_identity.active_credentials to prevent:
|
|
77
|
+
# - Accessing another identity's credentials (RecordNotFound)
|
|
78
|
+
# - Accessing revoked credentials (not in active scope)
|
|
79
|
+
def set_credential
|
|
80
|
+
@credential = current_identity.active_credentials.find(params[:id])
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSB
|
|
4
|
+
module Auth
|
|
5
|
+
module Account
|
|
6
|
+
class SessionsController < RSB::Auth::ApplicationController
|
|
7
|
+
before_action :require_authentication
|
|
8
|
+
|
|
9
|
+
# Revokes a specific session belonging to the current identity.
|
|
10
|
+
# Scoped to current_identity.sessions to prevent accessing
|
|
11
|
+
# another identity's sessions (raises RecordNotFound).
|
|
12
|
+
#
|
|
13
|
+
# @route DELETE /auth/account/sessions/:id
|
|
14
|
+
def destroy
|
|
15
|
+
target = current_identity.sessions.find(params[:id])
|
|
16
|
+
RSB::Auth::SessionService.new.revoke(target)
|
|
17
|
+
redirect_to account_path, notice: t('rsb.auth.account.session_revoked')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Revokes all active sessions for the current identity except
|
|
21
|
+
# the current session. The user remains logged in.
|
|
22
|
+
#
|
|
23
|
+
# @route DELETE /auth/account/sessions
|
|
24
|
+
def destroy_all
|
|
25
|
+
RSB::Auth::SessionService.new.revoke_all(current_identity, except: current_session)
|
|
26
|
+
redirect_to account_path, notice: t('rsb.auth.account.all_sessions_revoked')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RSB
|
|
4
|
+
module Auth
|
|
5
|
+
class AccountController < ApplicationController
|
|
6
|
+
layout 'rsb/auth/application'
|
|
7
|
+
|
|
8
|
+
include RSB::Auth::RateLimitable
|
|
9
|
+
include RSB::Auth::UserAgentHelper
|
|
10
|
+
|
|
11
|
+
before_action :require_authentication
|
|
12
|
+
before_action :check_account_enabled
|
|
13
|
+
before_action :check_deletion_enabled, only: %i[confirm_destroy destroy]
|
|
14
|
+
|
|
15
|
+
# Renders the account hub page with four sections:
|
|
16
|
+
# login methods, identity fields, active sessions, delete account.
|
|
17
|
+
#
|
|
18
|
+
# @route GET /auth/account
|
|
19
|
+
def show
|
|
20
|
+
load_account_data
|
|
21
|
+
@rsb_page_title = t('rsb.auth.account.show.page_title', default: 'Account')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Updates identity attributes (metadata or concern-provided nested attributes).
|
|
25
|
+
#
|
|
26
|
+
# @route PATCH /auth/account
|
|
27
|
+
def update
|
|
28
|
+
result = RSB::Auth::AccountService.new.update(
|
|
29
|
+
identity: current_identity,
|
|
30
|
+
params: account_params
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if result.success?
|
|
34
|
+
redirect_to account_path, notice: t('rsb.auth.account.updated')
|
|
35
|
+
else
|
|
36
|
+
@errors = result.errors
|
|
37
|
+
load_account_data
|
|
38
|
+
render :show, status: :unprocessable_entity
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Renders the password confirmation page before account deletion.
|
|
43
|
+
#
|
|
44
|
+
# @route GET /auth/account/confirm_destroy
|
|
45
|
+
def confirm_destroy; end
|
|
46
|
+
|
|
47
|
+
# Soft-deletes the account after password verification.
|
|
48
|
+
# Clears the session cookie and redirects to login.
|
|
49
|
+
#
|
|
50
|
+
# @route DELETE /auth/account
|
|
51
|
+
def destroy
|
|
52
|
+
result = RSB::Auth::AccountService.new.delete_account(
|
|
53
|
+
identity: current_identity,
|
|
54
|
+
password: params[:password],
|
|
55
|
+
current_session: current_session
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if result.success?
|
|
59
|
+
reset_session
|
|
60
|
+
cookies.delete(:rsb_session_token)
|
|
61
|
+
redirect_to new_session_path, notice: t('rsb.auth.account.deleted')
|
|
62
|
+
else
|
|
63
|
+
@errors = result.errors
|
|
64
|
+
render :confirm_destroy, status: :unprocessable_entity
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def account_params
|
|
71
|
+
permitted = RSB::Auth.configuration.permitted_account_params
|
|
72
|
+
permitted = permitted.flat_map { |p| p == :metadata ? [{ metadata: {} }] : [p] }
|
|
73
|
+
params.require(:identity).permit(*permitted)
|
|
74
|
+
rescue ActionController::ParameterMissing
|
|
75
|
+
{}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def load_account_data
|
|
79
|
+
@identity = current_identity
|
|
80
|
+
@login_methods = current_identity.active_credentials.order(:created_at)
|
|
81
|
+
@sessions = current_identity.sessions.active.order(last_active_at: :desc)
|
|
82
|
+
@current_session = current_session
|
|
83
|
+
@deletion_enabled = RSB::Settings.get('auth.account_deletion_enabled')
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def check_account_enabled
|
|
87
|
+
return if RSB::Settings.get('auth.account_enabled')
|
|
88
|
+
|
|
89
|
+
redirect_to main_app.root_path, alert: t('rsb.auth.account.disabled')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def check_deletion_enabled
|
|
93
|
+
return if RSB::Settings.get('auth.account_deletion_enabled')
|
|
94
|
+
|
|
95
|
+
redirect_to account_path, alert: t('rsb.auth.account.deletion_disabled')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|