rails_iam 0.1.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 +7 -0
- data/CHANGELOG.md +23 -0
- data/CONTRIBUTING.md +145 -0
- data/LICENSE +20 -0
- data/README.md +192 -0
- data/Rakefile +14 -0
- data/app/controllers/concerns/rails_iam/auth_concern.rb +60 -0
- data/app/controllers/concerns/rails_iam/authentication/authenticatable.rb +44 -0
- data/app/controllers/concerns/rails_iam/authorization/authorizable.rb +170 -0
- data/app/controllers/rails_iam/application_controller.rb +7 -0
- data/app/controllers/rails_iam/auth_controller.rb +113 -0
- data/app/helpers/rails_iam/config_loader.rb +71 -0
- data/app/models/concerns/rails_iam/auditable.rb +57 -0
- data/app/models/concerns/rails_iam/user_concern.rb +59 -0
- data/app/models/rails_iam/application_record.rb +8 -0
- data/app/models/rails_iam/permission.rb +14 -0
- data/app/models/rails_iam/refresh_token.rb +7 -0
- data/app/models/rails_iam/role.rb +14 -0
- data/app/models/rails_iam/role_permission.rb +20 -0
- data/app/models/rails_iam/user.rb +16 -0
- data/app/models/rails_iam/user_denied_permission.rb +12 -0
- data/app/models/rails_iam/user_permission.rb +12 -0
- data/app/models/rails_iam/user_role.rb +12 -0
- data/app/services/application_service.rb +4 -0
- data/app/services/rails_iam/authentication/authenticated_user.rb +13 -0
- data/app/services/rails_iam/authentication/authenticator.rb +39 -0
- data/app/services/rails_iam/authentication/current.rb +9 -0
- data/app/services/rails_iam/authentication/exceptions/authentication_error.rb +10 -0
- data/app/services/rails_iam/authentication/jwt_decoder.rb +38 -0
- data/app/services/rails_iam/authentication/jwt_encoder.rb +45 -0
- data/app/services/rails_iam/authentication/login.rb +67 -0
- data/app/services/rails_iam/authentication/logout.rb +35 -0
- data/app/services/rails_iam/authentication/refresh_token_encoder.rb +11 -0
- data/app/services/rails_iam/authentication/skip_authentication_rule.rb +20 -0
- data/app/services/rails_iam/authentication/token_extractor.rb +64 -0
- data/app/services/rails_iam/authorization/authorization_context.rb +41 -0
- data/app/services/rails_iam/authorization/authorization_rule.rb +38 -0
- data/app/services/rails_iam/authorization/authorizer.rb +35 -0
- data/app/services/rails_iam/authorization/cache.rb +39 -0
- data/app/services/rails_iam/authorization/denied_permission_checker.rb +33 -0
- data/app/services/rails_iam/authorization/exceptions/authorization_error.rb +10 -0
- data/app/services/rails_iam/authorization/exceptions/missing_permission_error.rb +35 -0
- data/app/services/rails_iam/authorization/exceptions/missing_role_error.rb +35 -0
- data/app/services/rails_iam/authorization/permission_checker.rb +42 -0
- data/app/services/rails_iam/authorization/permission_resolver.rb +70 -0
- data/app/services/rails_iam/authorization/permission_sources/role_permission_source.rb +37 -0
- data/app/services/rails_iam/authorization/permission_sources/user_denied_permission_source.rb +29 -0
- data/app/services/rails_iam/authorization/permission_sources/user_permission_source.rb +28 -0
- data/app/services/rails_iam/authorization/resolved_permissions.rb +63 -0
- data/app/services/rails_iam/authorization/role_checker.rb +35 -0
- data/app/services/rails_iam/authorization/role_resolver.rb +30 -0
- data/app/services/rails_iam/authorization/skip_authorization_rule.rb +19 -0
- data/app/services/rails_iam/rails_iam_service.rb +11 -0
- data/config/routes.rb +25 -0
- data/lib/generators/rails_iam/install_generator.rb +49 -0
- data/lib/generators/rails_iam/templates/create_rails_iam_tables.rb.erb +119 -0
- data/lib/generators/rails_iam/templates/initializer.rb +162 -0
- data/lib/rails_iam/config/authentication.rb +65 -0
- data/lib/rails_iam/config/authorization.rb +36 -0
- data/lib/rails_iam/config/jwt.rb +27 -0
- data/lib/rails_iam/configuration.rb +30 -0
- data/lib/rails_iam/controller_dsl.rb +41 -0
- data/lib/rails_iam/engine.rb +34 -0
- data/lib/rails_iam/model_dsl.rb +13 -0
- data/lib/rails_iam/version.rb +5 -0
- data/lib/rails_iam.rb +27 -0
- data/lib/tasks/rails_iam_tasks.rake +6 -0
- data/rails_iam.gemspec +46 -0
- metadata +161 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Authorization
|
|
5
|
+
class RoleChecker < RailsIam::RailsIamService
|
|
6
|
+
def initialize(context)
|
|
7
|
+
super()
|
|
8
|
+
@context = context
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
return true if @context.required_roles.empty?
|
|
13
|
+
|
|
14
|
+
success =
|
|
15
|
+
case @context.role_match
|
|
16
|
+
when :any
|
|
17
|
+
(@context.roles.to_a & @context.required_roles).any?
|
|
18
|
+
|
|
19
|
+
when :all
|
|
20
|
+
(@context.required_roles - @context.roles.to_a).empty?
|
|
21
|
+
|
|
22
|
+
else
|
|
23
|
+
raise ArgumentError, "Unknown role_match #{@context.role_match}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return true if success
|
|
27
|
+
|
|
28
|
+
raise RailsIam::Authorization::Exceptions::MissingRoleError.new(
|
|
29
|
+
required: @context.required_roles,
|
|
30
|
+
actual: @context.roles,
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Authorization
|
|
5
|
+
class RoleResolver < RailsIam::RailsIamService
|
|
6
|
+
def initialize(user)
|
|
7
|
+
super()
|
|
8
|
+
@user = user
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call
|
|
12
|
+
Cache.read_or_write(Cache::ROLES, user.id) do
|
|
13
|
+
resolve
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :user
|
|
20
|
+
|
|
21
|
+
def resolve
|
|
22
|
+
user.roles
|
|
23
|
+
.pluck(:name)
|
|
24
|
+
.map!(&:to_s)
|
|
25
|
+
.to_set
|
|
26
|
+
.freeze
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Authorization
|
|
5
|
+
SkipAuthorizationRule = Data.define(
|
|
6
|
+
:only,
|
|
7
|
+
:except,
|
|
8
|
+
) do
|
|
9
|
+
def matches?(action)
|
|
10
|
+
action = action.to_sym
|
|
11
|
+
|
|
12
|
+
return Array(only).map(&:to_sym).include?(action) if only.present?
|
|
13
|
+
return Array(except).map(&:to_sym).exclude?(action) if except.present?
|
|
14
|
+
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.routes.draw do
|
|
4
|
+
sign_in_path = RailsIam.configuration.authentication.sign_in_path
|
|
5
|
+
sign_out_path = RailsIam.configuration.authentication.sign_out_path
|
|
6
|
+
refresh_token_path = RailsIam.configuration.authentication.refresh_token_path
|
|
7
|
+
|
|
8
|
+
if sign_in_path.present?
|
|
9
|
+
post sign_in_path,
|
|
10
|
+
to: "rails_iam/auth#sign_in",
|
|
11
|
+
as: :rails_iam_sign_in
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if sign_out_path.present?
|
|
15
|
+
post sign_out_path,
|
|
16
|
+
to: "rails_iam/auth#sign_out",
|
|
17
|
+
as: :rails_iam_sign_out
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if refresh_token_path.present?
|
|
21
|
+
post refresh_token_path,
|
|
22
|
+
to: "rails_iam/auth#refresh",
|
|
23
|
+
as: :rails_iam_refresh
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module RailsIam
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
include ActiveRecord::Generators::Migration
|
|
9
|
+
|
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
|
11
|
+
|
|
12
|
+
def copy_migrations
|
|
13
|
+
say "Copying migration file", :yellow
|
|
14
|
+
migration_template "create_rails_iam_tables.rb.erb", "db/migrate/create_rails_iam_tables.rb"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create_initializer
|
|
18
|
+
say "Copying initializer file", :yellow
|
|
19
|
+
template "initializer.rb", "config/initializers/rails_iam.rb"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def show_readme
|
|
23
|
+
say "\n=======================================================", :yellow
|
|
24
|
+
say " RailsIam installed successfully!", :green
|
|
25
|
+
say "=======================================================", :yellow
|
|
26
|
+
say "Next steps:", :yellow
|
|
27
|
+
say ""
|
|
28
|
+
say "1. Open: config/initializers/rails_iam.rb", :green
|
|
29
|
+
say ""
|
|
30
|
+
say "2. Configure your user model if you have an existing authentication model:", :green
|
|
31
|
+
say " • If you have existing authentication (Devise, Auth0, custom, etc.)", :green
|
|
32
|
+
say " - config.user_class = 'User' (or Account, Member, Admin...)", :green
|
|
33
|
+
say " - config.current_user_method = :current_user (or :current_account)", :green
|
|
34
|
+
say " - Add `acts_as_rails_iam_user` to your user (or Account, Member, Admin...) authentication model.", :green
|
|
35
|
+
say ""
|
|
36
|
+
say " • If you use RailsIam authentication", :green
|
|
37
|
+
say " - Keep the default configuration.", :green
|
|
38
|
+
say ""
|
|
39
|
+
say "3. Run: bin/rails db:migrate", :green
|
|
40
|
+
say ""
|
|
41
|
+
say "You're ready to start using RailsIam 🚀", :green
|
|
42
|
+
say "=======================================================\n", :yellow
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.next_migration_number(dirname)
|
|
46
|
+
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
migration_version = "#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CreateRailsIamTables < ActiveRecord::Migration[migration_version.to_f]
|
|
5
|
+
|
|
6
|
+
def change
|
|
7
|
+
|
|
8
|
+
primary_key_type = Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
|
|
9
|
+
user_class_name = RailsIam.configuration&.user_class || "RailsIam::User"
|
|
10
|
+
user_table_name = user_class_name.underscore.pluralize.tr('/', '_')
|
|
11
|
+
|
|
12
|
+
create_table :rails_iam_users, id: primary_key_type do |t|
|
|
13
|
+
t.string :email, index: { unique: true }
|
|
14
|
+
t.string :password_digest
|
|
15
|
+
|
|
16
|
+
# Custom Audit Tracking Columns
|
|
17
|
+
t.string :created_by
|
|
18
|
+
t.string :updated_by
|
|
19
|
+
t.timestamp :deleted_at
|
|
20
|
+
t.string :deleted_by
|
|
21
|
+
|
|
22
|
+
t.timestamps
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
create_table :rails_iam_roles, id: primary_key_type do |t|
|
|
26
|
+
t.string :name, index: { unique: true }
|
|
27
|
+
t.text :description
|
|
28
|
+
|
|
29
|
+
# Custom Audit Tracking Columns
|
|
30
|
+
t.string :created_by
|
|
31
|
+
t.string :updated_by
|
|
32
|
+
t.timestamp :deleted_at
|
|
33
|
+
t.string :deleted_by
|
|
34
|
+
|
|
35
|
+
t.timestamps
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
create_table :rails_iam_permissions, id: primary_key_type do |t|
|
|
39
|
+
t.string :code, index: { unique: true }
|
|
40
|
+
t.text :description
|
|
41
|
+
|
|
42
|
+
# Custom Audit Tracking Columns
|
|
43
|
+
t.string :created_by
|
|
44
|
+
t.string :updated_by
|
|
45
|
+
t.timestamp :deleted_at
|
|
46
|
+
t.string :deleted_by
|
|
47
|
+
|
|
48
|
+
t.timestamps
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
create_table :rails_iam_user_roles, id: primary_key_type do |t|
|
|
52
|
+
t.references :user, null: false, type: primary_key_type, foreign_key: { to_table: user_table_name }
|
|
53
|
+
t.references :role, null: false, type: primary_key_type, foreign_key: { to_table: :rails_iam_roles }
|
|
54
|
+
|
|
55
|
+
# Custom Audit Tracking Columns
|
|
56
|
+
t.string :created_by
|
|
57
|
+
t.string :updated_by
|
|
58
|
+
t.timestamp :deleted_at
|
|
59
|
+
t.string :deleted_by
|
|
60
|
+
|
|
61
|
+
t.timestamps
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
create_table :rails_iam_user_permissions, id: primary_key_type do |t|
|
|
66
|
+
t.references :permission, null: false, type: primary_key_type, foreign_key: { to_table: :rails_iam_permissions }
|
|
67
|
+
t.references :user, null: false, type: primary_key_type, foreign_key: { to_table: user_table_name }
|
|
68
|
+
|
|
69
|
+
# Custom Audit Tracking Columns
|
|
70
|
+
t.string :created_by
|
|
71
|
+
t.string :updated_by
|
|
72
|
+
t.timestamp :deleted_at
|
|
73
|
+
t.string :deleted_by
|
|
74
|
+
|
|
75
|
+
t.timestamps
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
create_table :rails_iam_role_permissions, id: primary_key_type do |t|
|
|
79
|
+
t.references :permission, null: false, type: primary_key_type, foreign_key: { to_table: :rails_iam_permissions }
|
|
80
|
+
t.references :role, null: false, type: primary_key_type, foreign_key: { to_table: :rails_iam_roles }
|
|
81
|
+
|
|
82
|
+
# Custom Audit Tracking Columns
|
|
83
|
+
t.string :created_by
|
|
84
|
+
t.string :updated_by
|
|
85
|
+
t.timestamp :deleted_at
|
|
86
|
+
t.string :deleted_by
|
|
87
|
+
|
|
88
|
+
t.timestamps
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
create_table :rails_iam_user_denied_permissions, id: primary_key_type do |t|
|
|
92
|
+
t.references :permission, null: false, type: primary_key_type, foreign_key: { to_table: :rails_iam_permissions }
|
|
93
|
+
t.references :user, null: false, type: primary_key_type, foreign_key: { to_table: user_table_name }
|
|
94
|
+
|
|
95
|
+
# Custom Audit Tracking Columns
|
|
96
|
+
t.string :created_by
|
|
97
|
+
t.string :updated_by
|
|
98
|
+
t.timestamp :deleted_at
|
|
99
|
+
t.string :deleted_by
|
|
100
|
+
|
|
101
|
+
t.timestamps
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
create_table :rails_iam_refresh_tokens, id: primary_key_type do |t|
|
|
105
|
+
t.references :user, null: false, type: primary_key_type, foreign_key: { to_table: user_table_name }
|
|
106
|
+
t.string :token_digest, index: { unique: true }
|
|
107
|
+
t.datetime :expires_at, null: false
|
|
108
|
+
# to invalidate the token
|
|
109
|
+
t.datetime :revoked_at
|
|
110
|
+
t.datetime :last_used_at
|
|
111
|
+
# Jti from access token
|
|
112
|
+
t.string :jti, index: { unique: true }
|
|
113
|
+
t.string :ip_address
|
|
114
|
+
t.string :user_agent
|
|
115
|
+
|
|
116
|
+
t.timestamps
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# RailsIam Configuration
|
|
5
|
+
#
|
|
6
|
+
# Use this file to configure how your application handles authentication,
|
|
7
|
+
# authorization scopes, JWT token properties, caching, and error handling.
|
|
8
|
+
#
|
|
9
|
+
RailsIam.configure do |config|
|
|
10
|
+
# =========================================================================
|
|
11
|
+
# Host Application Integration
|
|
12
|
+
# =========================================================================
|
|
13
|
+
|
|
14
|
+
# - The class name of your main User/Account model which will be used to login
|
|
15
|
+
# - It's been used throughout RailsIam for authentication, authorization, associations and user lookup.
|
|
16
|
+
# - Configure this only if you use a different model
|
|
17
|
+
# or you have your own authentication mechanism (devise or jwt/bcrypt)
|
|
18
|
+
# - Default: 'RailsIam::User'
|
|
19
|
+
# config.user_class = 'RailsIam::User'
|
|
20
|
+
|
|
21
|
+
# - The method name your controllers use to look up the currently active session user.
|
|
22
|
+
# - Default: :current_user
|
|
23
|
+
# config.current_user_method = :current_user
|
|
24
|
+
|
|
25
|
+
# - The performer method that rails_iam use to track who creates and assign role/permission
|
|
26
|
+
# - Support symbol and proc
|
|
27
|
+
# - Default: :email
|
|
28
|
+
# config.audit_performer_method = :email
|
|
29
|
+
|
|
30
|
+
# =========================================================================
|
|
31
|
+
# Authentication Settings (config.authentication)
|
|
32
|
+
# =========================================================================
|
|
33
|
+
|
|
34
|
+
# - The key used when storing or reading the access token from cookies or parameters.
|
|
35
|
+
# - Default: 'access_token'
|
|
36
|
+
# config.authentication.access_token_key = 'access_token'
|
|
37
|
+
|
|
38
|
+
# - The key used when storing or reading the long-lived refresh token.
|
|
39
|
+
# - Default: 'refresh_token'
|
|
40
|
+
# config.authentication.refresh_token_key = 'refresh_token'
|
|
41
|
+
|
|
42
|
+
# - Supported authentication delivery strategies.
|
|
43
|
+
# - Options: [:cookie], [:bearer], or combined [:cookie, :bearer]
|
|
44
|
+
# - For :bearer, the gem expects the token in authorization header with `Bearer your_token`
|
|
45
|
+
# - Default: [:cookie]
|
|
46
|
+
# config.authentication.authentication_strategies = [:cookie] # httponly
|
|
47
|
+
|
|
48
|
+
# - Cookie configuration (Web / Cookie-based Strategy)
|
|
49
|
+
# - Configure the security and behavior properties for session cookies issued by
|
|
50
|
+
# - the RailsIam engine. These settings strictly apply when using the :cookie authentication strategy.
|
|
51
|
+
# - Default: { httponly: true, secure: Rails.env.production?, same_site: :lax, }
|
|
52
|
+
# config.authentication.cookie_options = { httponly: true, secure: Rails.env.production?, same_site: :lax, }
|
|
53
|
+
|
|
54
|
+
# - Refresh Token Transport, only needed if authentication_strategies uses :bearer
|
|
55
|
+
# - Determines how RailsIam receives the refresh token for
|
|
56
|
+
# refresh and sign-out requests when using bearer authentication.
|
|
57
|
+
# - Will use the same @refresh_token_key to get the header
|
|
58
|
+
# i,e: request.get_header(refresh_token_key.upcase) -> HTTP_X_REFRESH_TOKEN
|
|
59
|
+
# - Options: :header, :request_body
|
|
60
|
+
# - Default: :header
|
|
61
|
+
# config.authentication.refresh_token_transport = :header
|
|
62
|
+
|
|
63
|
+
# - Session persistence limit strategy.
|
|
64
|
+
# [:single] - Forces one active device/session per user at any time (invalidates old tokens).
|
|
65
|
+
# [:multi] - Allows concurrent login sessions across multiple devices or browsers.
|
|
66
|
+
# - Default: :single
|
|
67
|
+
# config.authentication.session_strategy = :single
|
|
68
|
+
|
|
69
|
+
#
|
|
70
|
+
# Built-in Authentication Routes
|
|
71
|
+
#
|
|
72
|
+
# - Routes exposed by RailsIam when using its built-in authentication
|
|
73
|
+
# controllers. Put `nil` if you don't want RailsIam to provide
|
|
74
|
+
# the HTTP sign-in, sign-out and refresh token endpoints. You don't need to add
|
|
75
|
+
# anything to your host application. RailsIam will map this routes
|
|
76
|
+
# to your host application i,e: example.com/auth/sign_in
|
|
77
|
+
#
|
|
78
|
+
# You can also:
|
|
79
|
+
# - Use Devise (or another authentication library) and leave these default nil.
|
|
80
|
+
# - Build your own authentication controllers and use RailsIam's
|
|
81
|
+
# JWT encoder/decoder, refresh token services, or login/logout
|
|
82
|
+
# services without exposing the built-in routes.
|
|
83
|
+
# - Mix both approaches (e.g. use RailsIam's sign-in route but your
|
|
84
|
+
# own sign-out route).
|
|
85
|
+
# - Use nil if your application already have authentication routes
|
|
86
|
+
# config.authentication.sign_in_path = '/auth/sign_in'
|
|
87
|
+
# config.authentication.sign_out_path = '/auth/sign_out'
|
|
88
|
+
# config.authentication.refresh_token_path = '/auth/refresh
|
|
89
|
+
|
|
90
|
+
# - Lifespan for the Access Token used to set httponly cookie expiration
|
|
91
|
+
# - Keep the value sync jwt expiry time
|
|
92
|
+
# - Default: 15.minutes
|
|
93
|
+
# config.authentication.access_token_expires_in = 15.minutes
|
|
94
|
+
|
|
95
|
+
# - Lifespan for the Refresh Token used to request replacement access tokens.
|
|
96
|
+
# - Default: 30.days
|
|
97
|
+
# config.authentication.refresh_token_expires_in = 30.days
|
|
98
|
+
|
|
99
|
+
# - Custom system error messages emitted during authentication edge cases.
|
|
100
|
+
# - Configure the error messages if you need different messages
|
|
101
|
+
# config.authentication.token_error = 'The provided authentication token is invalid or malformed.'
|
|
102
|
+
# config.authentication.token_expired_error = 'The authentication token has expired. Please re-authenticate.'
|
|
103
|
+
# config.authentication.authentication_error = 'Invalid email or password.'
|
|
104
|
+
# config.authentication.authentication_strategy_error = 'Unknown authentication strategy. Please configure it from rails_iam initializer'
|
|
105
|
+
# config.authentication.session_strategy_error = 'Unknown session strategy. Please configure it from rails_iam initializer'
|
|
106
|
+
# config.authentication.refresh_token_transport_error = 'Unknown refresh token transport. Please configure it from rails_iam initializer'
|
|
107
|
+
|
|
108
|
+
# =========================================================================
|
|
109
|
+
# Authorization & Cache Settings (config.authorization)
|
|
110
|
+
# =========================================================================
|
|
111
|
+
|
|
112
|
+
# - Custom formatters for policy denial messaging.
|
|
113
|
+
# - Accepts plain Strings, dynamic blocks, or Lambdas with positional keywords.
|
|
114
|
+
#
|
|
115
|
+
# config.authorization.missing_role_error = lambda do |required:, actual:|
|
|
116
|
+
# "Missing roles: #{required}. You only have: #{actual}"
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# config.authorization.missing_permission_error = lambda do |required:|
|
|
120
|
+
# "Missing permission(s): #{required.join(', ')}"
|
|
121
|
+
# end
|
|
122
|
+
|
|
123
|
+
# - Error message if permission is not applied on endpoint
|
|
124
|
+
# - Example: No authorization rule for users#show
|
|
125
|
+
# config.authorization.authorization_rule_error = "No authorization rule for "
|
|
126
|
+
|
|
127
|
+
# - The store engine to cache resolved roles and permissions queries for low DB footprints.
|
|
128
|
+
# - Default: Rails.cache
|
|
129
|
+
# config.authorization.cache_store = Rails.cache
|
|
130
|
+
|
|
131
|
+
# - Lifespan duration for records cached within the authorization engine layer.
|
|
132
|
+
# - Default: 15.minutes
|
|
133
|
+
# config.authorization.cache_ttl = 15.minutes
|
|
134
|
+
|
|
135
|
+
# - Controller namespaces excluded from automatic authorization.
|
|
136
|
+
# - Use this when a third-party library or Rails engine (such as Devise, or another mounted engine) provides controllers
|
|
137
|
+
# - that inherit from your ApplicationController but should not be subject to RailsIam authorization.
|
|
138
|
+
# - Example: [/\ADevise::/, /\ARailsIam::/]
|
|
139
|
+
# - Default []
|
|
140
|
+
# config.authorization.excluded_controller_patterns = []
|
|
141
|
+
|
|
142
|
+
# =========================================================================
|
|
143
|
+
# JWT Cryptography Settings (config.jwt)
|
|
144
|
+
# =========================================================================
|
|
145
|
+
|
|
146
|
+
# - Secret verification string used to sign security tokens.
|
|
147
|
+
# - Default: ENV['JWT_SECRET'] or Rails.application.secret_key_base
|
|
148
|
+
# config.jwt.secret = ENV['JWT_SECRET']
|
|
149
|
+
|
|
150
|
+
# - Digital signature algorithm category mapping.
|
|
151
|
+
# - Default: 'HS256'
|
|
152
|
+
# config.jwt.algorithm = 'HS256'
|
|
153
|
+
|
|
154
|
+
# - Jwt expiry, default to 15 minutes, same as access token in cookie expiry
|
|
155
|
+
# config.jwt.jwt_expires_in = 15.minutes
|
|
156
|
+
|
|
157
|
+
# - Optional security claim validations (RFC 7519 standard targets).
|
|
158
|
+
# - Leave nil to disable strict validation checks.
|
|
159
|
+
# - You can also dynamically set them in `extra_claims` at `JwtEncoder`
|
|
160
|
+
# config.jwt.issuer = 'https://yourdomain.com'
|
|
161
|
+
# config.jwt.audience = 'your_application_client_id'
|
|
162
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Config
|
|
5
|
+
class Authentication
|
|
6
|
+
attr_accessor :access_token_key,
|
|
7
|
+
:refresh_token_key,
|
|
8
|
+
:authentication_strategies,
|
|
9
|
+
:session_strategy,
|
|
10
|
+
:sign_in_path,
|
|
11
|
+
:sign_out_path,
|
|
12
|
+
:refresh_token_path,
|
|
13
|
+
:refresh_token_transport,
|
|
14
|
+
:access_token_expires_in,
|
|
15
|
+
:refresh_token_expires_in,
|
|
16
|
+
:token_error,
|
|
17
|
+
:token_expired_error,
|
|
18
|
+
:authentication_error,
|
|
19
|
+
:authentication_strategy_error,
|
|
20
|
+
:session_strategy_error,
|
|
21
|
+
:refresh_token_transport_error,
|
|
22
|
+
:cookie_options
|
|
23
|
+
|
|
24
|
+
def initialize
|
|
25
|
+
# The key used when storing or reading the access token from cookies or parameters.
|
|
26
|
+
@access_token_key = "access_token"
|
|
27
|
+
|
|
28
|
+
# The key used when storing or reading the long-lived refresh token.
|
|
29
|
+
@refresh_token_key = "refresh_token"
|
|
30
|
+
|
|
31
|
+
# Authentication Strategy
|
|
32
|
+
# Supported: [:cookie], [:bearer], or combined [:cookie, :bearer]
|
|
33
|
+
@authentication_strategies = %i[cookie]
|
|
34
|
+
|
|
35
|
+
# Session Strategy, allowing user to login from multiple device at the same time
|
|
36
|
+
# Supported: :single || :multi
|
|
37
|
+
@session_strategy = :single
|
|
38
|
+
|
|
39
|
+
# Determines how RailsIam receives the refresh token for :bearer strategy
|
|
40
|
+
# Supported: :header || :request_body
|
|
41
|
+
@refresh_token_transport = :header
|
|
42
|
+
|
|
43
|
+
# Authentication Endpoints
|
|
44
|
+
@sign_in_path = "/auth/sign_in"
|
|
45
|
+
@sign_out_path = "/auth/sign_out"
|
|
46
|
+
@refresh_token_path = "/auth/refresh"
|
|
47
|
+
|
|
48
|
+
# expiry for access token
|
|
49
|
+
@access_token_expires_in = 15.minutes
|
|
50
|
+
@refresh_token_expires_in = 30.days
|
|
51
|
+
|
|
52
|
+
# cookie options
|
|
53
|
+
@cookie_options = { httponly: true, secure: Rails.env.production?, same_site: :lax }.freeze
|
|
54
|
+
|
|
55
|
+
# Authentication Messages
|
|
56
|
+
@token_error = "The provided authentication token is invalid or malformed."
|
|
57
|
+
@token_expired_error = "The authentication token has expired. Please re-authenticate."
|
|
58
|
+
@authentication_error = "Invalid email or password."
|
|
59
|
+
@authentication_strategy_error = "Unknown authentication strategy. Please configure it from rails_iam initializer"
|
|
60
|
+
@session_strategy_error = "Unknown session strategy. Please configure it from rails_iam initializer"
|
|
61
|
+
@refresh_token_transport_error = "Unknown refresh token transport. Please configure it from rails_iam initializer"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Config
|
|
5
|
+
class Authorization
|
|
6
|
+
attr_accessor :missing_role_error,
|
|
7
|
+
:missing_permission_error,
|
|
8
|
+
:authorization_rule_error,
|
|
9
|
+
:cache_store,
|
|
10
|
+
:cache_ttl,
|
|
11
|
+
:excluded_controller_patterns
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
# Accepts plain Strings, dynamic blocks, or Lambdas with positional keywords.
|
|
15
|
+
@missing_role_error = lambda do |required:, actual:|
|
|
16
|
+
"Missing roles: #{required.join(', ')}. You only have: #{actual.join(', ')}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@missing_permission_error = lambda do |required:|
|
|
20
|
+
"Missing permission(s): #{required.join(', ')}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
@authorization_rule_error = "No authorization rule applied for"
|
|
24
|
+
|
|
25
|
+
# The store engine to cache resolved roles and permissions queries for low DB footprints.
|
|
26
|
+
@cache_store = Rails.cache
|
|
27
|
+
@cache_ttl = 15.minutes
|
|
28
|
+
|
|
29
|
+
# Useful for third-party gems and mounted engines (e.g. Devise)
|
|
30
|
+
# whose controllers inherit from ApplicationController but
|
|
31
|
+
# should bypass RailsIam authorization.
|
|
32
|
+
@excluded_controller_patterns = []
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module Config
|
|
5
|
+
class Jwt
|
|
6
|
+
attr_accessor :secret,
|
|
7
|
+
:algorithm,
|
|
8
|
+
:issuer,
|
|
9
|
+
:audience,
|
|
10
|
+
:jwt_expires_in
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
# Secret verification string used to sign security tokens.
|
|
14
|
+
@secret = ENV["JWT_SECRET"].presence || Rails.application&.secret_key_base
|
|
15
|
+
|
|
16
|
+
# Digital signature algorithm category mapping.
|
|
17
|
+
@algorithm = "HS256"
|
|
18
|
+
@jwt_expires_in = 15.minutes
|
|
19
|
+
|
|
20
|
+
# Optional security claim validations (RFC 7519 standard targets).
|
|
21
|
+
# Leave nil to disable strict validation checks.
|
|
22
|
+
@issuer = nil
|
|
23
|
+
@audience = nil
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_reader :authorization, :jwt, :authentication
|
|
6
|
+
|
|
7
|
+
attr_accessor :user_class, :current_user_method, :audit_performer_method
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
# Each concern has its own configuration object to keep the
|
|
11
|
+
@authorization = RailsIam::Config::Authorization.new
|
|
12
|
+
@jwt = RailsIam::Config::Jwt.new
|
|
13
|
+
@authentication = RailsIam::Config::Authentication.new
|
|
14
|
+
|
|
15
|
+
# Host Application User Model, defaults to RailsIam::User
|
|
16
|
+
# Used throughout RailsIam for authentication, authorization associations, and user lookup.
|
|
17
|
+
@user_class = "RailsIam::User"
|
|
18
|
+
|
|
19
|
+
# Current User Resolver, defaults to current_user
|
|
20
|
+
# Method used by RailsIam to obtain the authenticated user when
|
|
21
|
+
# the host application already manages authentication (e.g. Devise,)
|
|
22
|
+
@current_user_method = :current_user
|
|
23
|
+
|
|
24
|
+
# Audit performer method
|
|
25
|
+
# Used to audit who created/assigned what permission
|
|
26
|
+
# support symbol both symbol and proc i,e ->(user) { "#{user.id}:#{user.username}" }
|
|
27
|
+
@audit_performer_method = :email
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsIam
|
|
4
|
+
module ControllerDsl
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def rails_iam(*features)
|
|
9
|
+
features.each do |feature|
|
|
10
|
+
case feature.to_sym
|
|
11
|
+
when :authentication
|
|
12
|
+
include RailsIam::Authentication::Authenticatable
|
|
13
|
+
|
|
14
|
+
install_authentication_rescue_handler
|
|
15
|
+
|
|
16
|
+
when :authorization
|
|
17
|
+
include RailsIam::Authorization::Authorizable
|
|
18
|
+
|
|
19
|
+
install_authorization_rescue_handler
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Unknown RailsIam feature: #{feature.inspect}, only support :authentication and :authorization"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def install_authentication_rescue_handler
|
|
29
|
+
rescue_from RailsIam::Authentication::Exceptions::AuthenticationError do |exception|
|
|
30
|
+
render_rails_iam_authentication_error(exception)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def install_authorization_rescue_handler
|
|
35
|
+
rescue_from RailsIam::Authorization::Exceptions::AuthorizationError do |exception|
|
|
36
|
+
render_rails_iam_authorization_error(exception)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|