standard_id 0.1.5 → 0.1.7
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/README.md +529 -20
- data/app/controllers/concerns/standard_id/inertia_rendering.rb +49 -0
- data/app/controllers/concerns/standard_id/inertia_support.rb +31 -0
- data/app/controllers/concerns/standard_id/set_current_request_details.rb +19 -0
- data/app/controllers/concerns/standard_id/social_authentication.rb +86 -37
- data/app/controllers/concerns/standard_id/web_authentication.rb +50 -1
- data/app/controllers/standard_id/api/base_controller.rb +1 -0
- data/app/controllers/standard_id/api/oauth/callback/providers_controller.rb +7 -18
- data/app/controllers/standard_id/web/auth/callback/providers_controller.rb +33 -37
- data/app/controllers/standard_id/web/base_controller.rb +1 -0
- data/app/controllers/standard_id/web/login_controller.rb +12 -21
- data/app/controllers/standard_id/web/signup_controller.rb +11 -8
- data/app/forms/standard_id/web/signup_form.rb +32 -1
- data/app/models/standard_id/browser_session.rb +8 -0
- data/app/models/standard_id/client_secret_credential.rb +11 -0
- data/app/models/standard_id/device_session.rb +4 -0
- data/app/models/standard_id/identifier.rb +28 -0
- data/app/models/standard_id/service_session.rb +1 -1
- data/app/models/standard_id/session.rb +16 -2
- data/app/views/standard_id/web/auth/callback/providers/{apple_mobile.html.erb → mobile_callback.html.erb} +1 -1
- data/config/routes/api.rb +1 -2
- data/config/routes/web.rb +4 -3
- data/lib/generators/standard_id/install/templates/standard_id.rb +19 -8
- data/lib/standard_config/config.rb +13 -12
- data/lib/standard_config/config_provider.rb +6 -6
- data/lib/standard_config/schema.rb +2 -2
- data/lib/standard_id/account_locking.rb +86 -0
- data/lib/standard_id/account_status.rb +45 -0
- data/lib/standard_id/api/authentication_guard.rb +40 -1
- data/lib/standard_id/api/token_manager.rb +1 -1
- data/lib/standard_id/config/schema.rb +13 -9
- data/lib/standard_id/current_attributes.rb +9 -0
- data/lib/standard_id/engine.rb +9 -0
- data/lib/standard_id/errors.rb +12 -0
- data/lib/standard_id/events/definitions.rb +157 -0
- data/lib/standard_id/events/event.rb +123 -0
- data/lib/standard_id/events/subscribers/account_locking_subscriber.rb +17 -0
- data/lib/standard_id/events/subscribers/account_status_subscriber.rb +17 -0
- data/lib/standard_id/events/subscribers/base.rb +165 -0
- data/lib/standard_id/events/subscribers/logging_subscriber.rb +122 -0
- data/lib/standard_id/events.rb +137 -0
- data/lib/standard_id/oauth/authorization_code_flow.rb +10 -0
- data/lib/standard_id/oauth/client_credentials_flow.rb +31 -0
- data/lib/standard_id/oauth/password_flow.rb +36 -4
- data/lib/standard_id/oauth/passwordless_otp_flow.rb +38 -2
- data/lib/standard_id/oauth/subflows/social_login_grant.rb +11 -22
- data/lib/standard_id/oauth/token_grant_flow.rb +22 -1
- data/lib/standard_id/passwordless/base_strategy.rb +32 -0
- data/lib/standard_id/provider_registry.rb +73 -0
- data/lib/standard_id/{social_providers → providers}/apple.rb +46 -7
- data/lib/standard_id/providers/base.rb +242 -0
- data/lib/standard_id/{social_providers → providers}/google.rb +26 -7
- data/lib/standard_id/version.rb +1 -1
- data/lib/standard_id/web/authentication_guard.rb +29 -0
- data/lib/standard_id/web/session_manager.rb +39 -1
- data/lib/standard_id/web/token_manager.rb +2 -2
- data/lib/standard_id.rb +13 -2
- metadata +20 -6
- data/lib/standard_id/social_providers/response_builder.rb +0 -18
|
@@ -11,6 +11,7 @@ module StandardId
|
|
|
11
11
|
validates :value, presence: true, uniqueness: { scope: [:account_id, :type] }
|
|
12
12
|
|
|
13
13
|
after_commit :mark_account_verified!, on: :update, if: :just_verified?
|
|
14
|
+
after_commit :emit_identifier_created_event, on: :create
|
|
14
15
|
|
|
15
16
|
def verified?
|
|
16
17
|
verified_at.present?
|
|
@@ -18,6 +19,7 @@ module StandardId
|
|
|
18
19
|
|
|
19
20
|
def verify!
|
|
20
21
|
update!(verified_at: Time.current)
|
|
22
|
+
emit_verification_succeeded
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def unverify!
|
|
@@ -37,6 +39,32 @@ module StandardId
|
|
|
37
39
|
return unless account.has_attribute?(:verified_at)
|
|
38
40
|
|
|
39
41
|
account.update!(verified: true, verified_at: Time.current)
|
|
42
|
+
emit_account_verified
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def emit_identifier_created_event
|
|
46
|
+
StandardId::Events.publish(
|
|
47
|
+
StandardId::Events::IDENTIFIER_CREATED,
|
|
48
|
+
identifier: self,
|
|
49
|
+
account: account
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def emit_verification_succeeded
|
|
54
|
+
StandardId::Events.publish(
|
|
55
|
+
StandardId::Events::IDENTIFIER_VERIFICATION_SUCCEEDED,
|
|
56
|
+
identifier: self,
|
|
57
|
+
account: account,
|
|
58
|
+
verified_at: verified_at
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def emit_account_verified
|
|
63
|
+
StandardId::Events.publish(
|
|
64
|
+
StandardId::Events::ACCOUNT_VERIFIED,
|
|
65
|
+
account: account,
|
|
66
|
+
verified_via: type.demodulize.underscore.gsub("_identifier", "")
|
|
67
|
+
)
|
|
40
68
|
end
|
|
41
69
|
end
|
|
42
70
|
end
|
|
@@ -19,7 +19,7 @@ module StandardId
|
|
|
19
19
|
attr_reader :token
|
|
20
20
|
|
|
21
21
|
before_validation :generate_token, :generate_token_digest, :generate_lookup_hash, on: :create
|
|
22
|
-
|
|
22
|
+
after_commit :emit_session_revoked_event, on: :update, if: :just_revoked?
|
|
23
23
|
|
|
24
24
|
def active?
|
|
25
25
|
!revoked? && !expired?
|
|
@@ -33,7 +33,8 @@ module StandardId
|
|
|
33
33
|
revoked_at.present?
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def revoke!
|
|
36
|
+
def revoke!(reason: nil)
|
|
37
|
+
@reason = reason
|
|
37
38
|
update!(revoked_at: Time.current)
|
|
38
39
|
end
|
|
39
40
|
|
|
@@ -50,5 +51,18 @@ module StandardId
|
|
|
50
51
|
def generate_lookup_hash
|
|
51
52
|
self.lookup_hash = Digest::SHA256.hexdigest("#{token}:#{Rails.configuration.secret_key_base}")
|
|
52
53
|
end
|
|
54
|
+
|
|
55
|
+
def just_revoked?
|
|
56
|
+
saved_change_to_revoked_at? && revoked?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def emit_session_revoked_event
|
|
60
|
+
StandardId::Events.publish(
|
|
61
|
+
StandardId::Events::SESSION_REVOKED,
|
|
62
|
+
session: self,
|
|
63
|
+
account:,
|
|
64
|
+
reason: @reason
|
|
65
|
+
)
|
|
66
|
+
end
|
|
53
67
|
end
|
|
54
68
|
end
|
data/config/routes/api.rb
CHANGED
|
@@ -16,8 +16,7 @@ StandardId::ApiEngine.routes.draw do
|
|
|
16
16
|
resource :token, only: [:create]
|
|
17
17
|
|
|
18
18
|
namespace :callback do
|
|
19
|
-
post :
|
|
20
|
-
post :apple, to: "providers#apple"
|
|
19
|
+
post ":provider", to: "providers#callback", as: :provider
|
|
21
20
|
end
|
|
22
21
|
end
|
|
23
22
|
end
|
data/config/routes/web.rb
CHANGED
|
@@ -7,10 +7,11 @@ StandardId::WebEngine.routes.draw do
|
|
|
7
7
|
|
|
8
8
|
# Social authentication callbacks (web flow)
|
|
9
9
|
namespace :auth do
|
|
10
|
+
post "callback_mobile/:provider", to: "callback/providers#mobile_callback", as: :callback_mobile
|
|
11
|
+
|
|
10
12
|
namespace :callback do
|
|
11
|
-
get :
|
|
12
|
-
post :
|
|
13
|
-
post :apple_mobile, to: "providers#apple_mobile"
|
|
13
|
+
get ":provider", to: "providers#callback", as: :provider
|
|
14
|
+
post ":provider", to: "providers#callback"
|
|
14
15
|
end
|
|
15
16
|
end
|
|
16
17
|
|
|
@@ -7,6 +7,21 @@ StandardId.configure do |c|
|
|
|
7
7
|
# c.cache_store = Rails.cache
|
|
8
8
|
# c.logger = Rails.logger
|
|
9
9
|
# c.web_layout = "application"
|
|
10
|
+
|
|
11
|
+
# Inertia.js support (requires inertia_rails gem)
|
|
12
|
+
# When enabled, StandardId web controllers will render Inertia components
|
|
13
|
+
# instead of ERB views. You must create the corresponding components in your
|
|
14
|
+
# frontend (e.g., pages/auth/login/show.tsx)
|
|
15
|
+
# c.use_inertia = true
|
|
16
|
+
# c.inertia_component_namespace = "auth" # Component path prefix (e.g., "auth/login/show")
|
|
17
|
+
|
|
18
|
+
# Session lifetimes (in seconds)
|
|
19
|
+
# c.session.browser_session_lifetime = 86400 # 24 hours (web sessions)
|
|
20
|
+
# c.session.browser_session_remember_me_lifetime = 2_592_000 # 30 days (remember me cookies)
|
|
21
|
+
# c.session.device_session_lifetime = 2_592_000 # 30 days (API device sessions)
|
|
22
|
+
# c.session.service_session_lifetime = 7_776_000 # 90 days (service-to-service sessions)
|
|
23
|
+
|
|
24
|
+
# Passwordless authentication delivery (DEPRECATED - use event subscriptions instead)
|
|
10
25
|
# c.passwordless_email_sender = ->(email, code) { PasswordlessMailer.with(code: code, to: email).deliver_later }
|
|
11
26
|
# c.passwordless_sms_sender = ->(phone, code) { SmsProvider.send_code(phone: phone, code: code) }
|
|
12
27
|
|
|
@@ -32,6 +47,10 @@ StandardId.configure do |c|
|
|
|
32
47
|
# }
|
|
33
48
|
# }
|
|
34
49
|
|
|
50
|
+
# Events
|
|
51
|
+
# Enable or disable logging emitted via the internal event system
|
|
52
|
+
# c.events.enable_logging = false
|
|
53
|
+
|
|
35
54
|
# Social login credentials (if enabled in your app)
|
|
36
55
|
# c.social.google_client_id = ENV["GOOGLE_CLIENT_ID"]
|
|
37
56
|
# c.social.google_client_secret = ENV["GOOGLE_CLIENT_SECRET"]
|
|
@@ -47,14 +66,6 @@ StandardId.configure do |c|
|
|
|
47
66
|
# name: social_info[:name] || social_info[:given_name]
|
|
48
67
|
# }
|
|
49
68
|
# }
|
|
50
|
-
# c.social.social_callback = ->(social_info:, provider:, tokens:, account:) {
|
|
51
|
-
# Analytics.track_social_login(
|
|
52
|
-
# provider: provider,
|
|
53
|
-
# email: social_info[:email],
|
|
54
|
-
# tokens: tokens,
|
|
55
|
-
# account_id: account.id
|
|
56
|
-
# )
|
|
57
|
-
# }
|
|
58
69
|
|
|
59
70
|
# OIDC Logout allow list
|
|
60
71
|
# c.allowed_post_logout_redirect_uris = [
|
|
@@ -23,13 +23,10 @@ module StandardConfig
|
|
|
23
23
|
# If set, Authorization endpoints can redirect to this path with a redirect_uri param
|
|
24
24
|
attr_accessor :login_url
|
|
25
25
|
|
|
26
|
-
# Social login
|
|
27
|
-
attr_accessor :
|
|
28
|
-
attr_accessor :apple_client_id, :apple_client_secret, :apple_private_key, :apple_key_id, :apple_team_id
|
|
29
|
-
attr_accessor :social_account_attributes, :social_callback
|
|
26
|
+
# Social login hooks
|
|
27
|
+
attr_accessor :social_account_attributes
|
|
30
28
|
|
|
31
|
-
# Passwordless authentication callbacks
|
|
32
|
-
# These should be callable objects (procs/lambdas) that accept (recipient, code) parameters
|
|
29
|
+
# Passwordless authentication delivery callbacks (deprecated - use events instead)
|
|
33
30
|
attr_accessor :passwordless_email_sender, :passwordless_sms_sender
|
|
34
31
|
|
|
35
32
|
# Allowed post-logout redirect URIs for OIDC logout endpoint
|
|
@@ -42,25 +39,29 @@ module StandardConfig
|
|
|
42
39
|
# Examples: "application", "standard_id/web/application", "my_custom_layout"
|
|
43
40
|
attr_accessor :web_layout
|
|
44
41
|
|
|
42
|
+
# Enable Inertia.js rendering for StandardId Web controllers
|
|
43
|
+
# When true and inertia_rails gem is available, controllers will render Inertia components
|
|
44
|
+
attr_accessor :use_inertia
|
|
45
|
+
|
|
46
|
+
# Namespace prefix for Inertia component paths
|
|
47
|
+
# Example: "Auth" will generate component paths like "Auth/Login/show"
|
|
48
|
+
attr_accessor :inertia_component_namespace
|
|
49
|
+
|
|
45
50
|
def initialize
|
|
46
51
|
@account_class_name = nil
|
|
47
52
|
@cache_store = nil
|
|
48
53
|
@logger = nil
|
|
49
54
|
@issuer = nil
|
|
50
55
|
@login_url = nil
|
|
51
|
-
@google_client_id = nil
|
|
52
|
-
@google_client_secret = nil
|
|
53
|
-
@apple_client_id = nil
|
|
54
|
-
@apple_client_secret = nil
|
|
55
|
-
@apple_private_key = nil
|
|
56
56
|
@apple_key_id = nil
|
|
57
57
|
@apple_team_id = nil
|
|
58
58
|
@social_account_attributes = nil
|
|
59
|
-
@social_callback = nil
|
|
60
59
|
@passwordless_email_sender = nil
|
|
61
60
|
@passwordless_sms_sender = nil
|
|
62
61
|
@allowed_post_logout_redirect_uris = []
|
|
63
62
|
@web_layout = nil
|
|
63
|
+
@use_inertia = nil
|
|
64
|
+
@inertia_component_namespace = nil
|
|
64
65
|
end
|
|
65
66
|
|
|
66
67
|
def account_class
|
|
@@ -9,9 +9,9 @@ module StandardConfig
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def method_missing(method_name, *args)
|
|
12
|
-
if method_name.to_s.end_with?(
|
|
12
|
+
if method_name.to_s.end_with?("=")
|
|
13
13
|
# Setter - only works for static configs (OpenStruct objects)
|
|
14
|
-
field_name = method_name.to_s.chomp(
|
|
14
|
+
field_name = method_name.to_s.chomp("=").to_sym
|
|
15
15
|
validate_field!(field_name)
|
|
16
16
|
|
|
17
17
|
config_object = @resolver_proc.call
|
|
@@ -42,11 +42,11 @@ module StandardConfig
|
|
|
42
42
|
config_object = @resolver_proc.call
|
|
43
43
|
raw_value = if config_object.respond_to?(field_name)
|
|
44
44
|
config_object.send(field_name)
|
|
45
|
-
|
|
45
|
+
elsif config_object.respond_to?(:[])
|
|
46
46
|
config_object[field_name] || config_object[field_name.to_s]
|
|
47
|
-
|
|
47
|
+
else
|
|
48
48
|
nil
|
|
49
|
-
|
|
49
|
+
end
|
|
50
50
|
|
|
51
51
|
# Cast the value according to schema
|
|
52
52
|
field_def = @schema&.field_definition(@scope_name, field_name)
|
|
@@ -64,7 +64,7 @@ module StandardConfig
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def respond_to_missing?(method_name, include_private = false)
|
|
67
|
-
field_name = method_name.to_s.end_with?(
|
|
67
|
+
field_name = method_name.to_s.end_with?("=") ? method_name.to_s.chomp("=").to_sym : method_name.to_sym
|
|
68
68
|
@schema&.valid_field?(@scope_name, field_name) || super
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -55,8 +55,8 @@ module StandardConfig
|
|
|
55
55
|
when :boolean
|
|
56
56
|
case value
|
|
57
57
|
when true, false then value
|
|
58
|
-
when
|
|
59
|
-
when
|
|
58
|
+
when "true", "1", 1 then true
|
|
59
|
+
when "false", "0", 0 then false
|
|
60
60
|
else !!value
|
|
61
61
|
end
|
|
62
62
|
when :array
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module StandardId
|
|
2
|
+
module AccountLocking
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
belongs_to :locked_by, polymorphic: true, optional: true
|
|
7
|
+
belongs_to :unlocked_by, polymorphic: true, optional: true
|
|
8
|
+
|
|
9
|
+
scope :locked, -> { where(locked: true) }
|
|
10
|
+
scope :unlocked, -> { where(locked: false) }
|
|
11
|
+
|
|
12
|
+
after_commit :emit_account_locked_event, on: :update, if: :just_locked?
|
|
13
|
+
after_commit :emit_account_unlocked_event, on: :update, if: :just_unlocked?
|
|
14
|
+
|
|
15
|
+
# Subscribe to events to enforce lock status
|
|
16
|
+
# Lock check runs BEFORE status check (more restrictive first)
|
|
17
|
+
StandardId::Events.subscribe(
|
|
18
|
+
StandardId::Events::OAUTH_TOKEN_ISSUING,
|
|
19
|
+
StandardId::Events::SESSION_CREATING,
|
|
20
|
+
StandardId::Events::SESSION_VALIDATING
|
|
21
|
+
) do |event|
|
|
22
|
+
account = event[:account]
|
|
23
|
+
if account&.locked?
|
|
24
|
+
raise StandardId::AccountLockedError.new(account)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def locked?
|
|
30
|
+
locked == true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unlocked?
|
|
34
|
+
!locked?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def lock!(reason:, locked_by: nil)
|
|
38
|
+
return true if locked?
|
|
39
|
+
|
|
40
|
+
update!(
|
|
41
|
+
locked: true,
|
|
42
|
+
locked_at: Time.current,
|
|
43
|
+
lock_reason: reason,
|
|
44
|
+
locked_by: locked_by
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unlock!(unlocked_by: nil)
|
|
49
|
+
return true if unlocked?
|
|
50
|
+
|
|
51
|
+
update!(
|
|
52
|
+
locked: false,
|
|
53
|
+
unlocked_at: Time.current,
|
|
54
|
+
unlocked_by: unlocked_by,
|
|
55
|
+
lock_reason: nil
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def just_locked?
|
|
62
|
+
locked_previously_changed? && locked?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def just_unlocked?
|
|
66
|
+
locked_previously_changed? && unlocked?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def emit_account_locked_event
|
|
70
|
+
StandardId::Events.publish(
|
|
71
|
+
StandardId::Events::ACCOUNT_LOCKED,
|
|
72
|
+
account: self,
|
|
73
|
+
reason: lock_reason,
|
|
74
|
+
locked_by:
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def emit_account_unlocked_event
|
|
79
|
+
StandardId::Events.publish(
|
|
80
|
+
StandardId::Events::ACCOUNT_UNLOCKED,
|
|
81
|
+
account: self,
|
|
82
|
+
unlocked_by:
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module StandardId
|
|
2
|
+
module AccountStatus
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
enum :status, { active: "active", inactive: "inactive" }, default: :active
|
|
7
|
+
|
|
8
|
+
after_commit :emit_account_status_changed_event, on: :update, if: :status_previously_changed?
|
|
9
|
+
|
|
10
|
+
StandardId::Events.subscribe(
|
|
11
|
+
StandardId::Events::OAUTH_TOKEN_ISSUING,
|
|
12
|
+
StandardId::Events::SESSION_CREATING,
|
|
13
|
+
StandardId::Events::SESSION_VALIDATING
|
|
14
|
+
) do |event|
|
|
15
|
+
account = event[:account]
|
|
16
|
+
if account&.inactive?
|
|
17
|
+
raise StandardId::AccountDeactivatedError, "Account is deactivated"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def activate!
|
|
23
|
+
return true if active?
|
|
24
|
+
|
|
25
|
+
update!(status: :active, activated_at: Time.current)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def deactivate!
|
|
29
|
+
return true if inactive?
|
|
30
|
+
|
|
31
|
+
update!(status: :inactive, deactivated_at: Time.current)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def emit_account_status_changed_event
|
|
37
|
+
event = inactive? ? StandardId::Events::ACCOUNT_DEACTIVATED : StandardId::Events::ACCOUNT_ACTIVATED
|
|
38
|
+
StandardId::Events.publish(
|
|
39
|
+
event,
|
|
40
|
+
account: self,
|
|
41
|
+
previous_status: status_previously_was
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
module StandardId
|
|
2
2
|
module Api
|
|
3
3
|
class AuthenticationGuard
|
|
4
|
-
def require_session!(session_manager)
|
|
4
|
+
def require_session!(session_manager, request: nil)
|
|
5
5
|
api_session = session_manager.current_session
|
|
6
|
+
emit_session_validating(api_session, request)
|
|
6
7
|
|
|
7
8
|
if api_session.blank?
|
|
8
9
|
raise StandardId::NotAuthenticatedError, "Invalid or missing access token"
|
|
9
10
|
elsif api_session.respond_to?(:expired?) && api_session.expired?
|
|
11
|
+
emit_session_expired(api_session)
|
|
10
12
|
raise StandardId::ExpiredSessionError, "Session has expired"
|
|
11
13
|
elsif api_session.respond_to?(:revoked?) && api_session.revoked?
|
|
12
14
|
session_manager.clear_session!
|
|
13
15
|
raise StandardId::RevokedSessionError, "Session has been revoked"
|
|
14
16
|
end
|
|
15
17
|
|
|
18
|
+
emit_session_validated(api_session)
|
|
16
19
|
api_session
|
|
17
20
|
end
|
|
18
21
|
|
|
@@ -51,6 +54,42 @@ module StandardId
|
|
|
51
54
|
raise ArgumentError, "Scopes must be provided as a String, Symbol, or Array"
|
|
52
55
|
end
|
|
53
56
|
end
|
|
57
|
+
|
|
58
|
+
def emit_session_validating(api_session, request)
|
|
59
|
+
StandardId::Events.publish(
|
|
60
|
+
StandardId::Events::SESSION_VALIDATING,
|
|
61
|
+
session: api_session
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def emit_session_validated(api_session)
|
|
66
|
+
account = if api_session.respond_to?(:account)
|
|
67
|
+
api_session.account
|
|
68
|
+
elsif api_session.respond_to?(:account_id)
|
|
69
|
+
StandardId.account_class.find_by(id: api_session.account_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
StandardId::Events.publish(
|
|
73
|
+
StandardId::Events::SESSION_VALIDATED,
|
|
74
|
+
session: api_session,
|
|
75
|
+
account: account
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def emit_session_expired(api_session)
|
|
80
|
+
account = if api_session.respond_to?(:account)
|
|
81
|
+
api_session.account
|
|
82
|
+
elsif api_session.respond_to?(:account_id)
|
|
83
|
+
StandardId.account_class.find_by(id: api_session.account_id)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
StandardId::Events.publish(
|
|
87
|
+
StandardId::Events::SESSION_EXPIRED,
|
|
88
|
+
session: api_session,
|
|
89
|
+
account: account,
|
|
90
|
+
expired_at: api_session.respond_to?(:expires_at) ? api_session.expires_at : nil
|
|
91
|
+
)
|
|
92
|
+
end
|
|
54
93
|
end
|
|
55
94
|
end
|
|
56
95
|
end
|
|
@@ -13,7 +13,7 @@ module StandardId
|
|
|
13
13
|
ip_address: @request.remote_ip,
|
|
14
14
|
device_id: device_id || SecureRandom.uuid,
|
|
15
15
|
device_agent: device_agent || @request.user_agent,
|
|
16
|
-
expires_at:
|
|
16
|
+
expires_at: StandardId::DeviceSession.expiry
|
|
17
17
|
)
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -14,6 +14,12 @@ StandardConfig.schema.draw do
|
|
|
14
14
|
field :issuer, type: :string, default: nil
|
|
15
15
|
field :login_url, type: :string, default: nil
|
|
16
16
|
field :allowed_post_logout_redirect_uris, type: :array, default: []
|
|
17
|
+
field :use_inertia, type: :boolean, default: false
|
|
18
|
+
field :inertia_component_namespace, type: :string, default: "standard_id"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
scope :events do
|
|
22
|
+
field :enable_logging, type: :boolean, default: false
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
scope :passwordless do
|
|
@@ -29,6 +35,13 @@ StandardConfig.schema.draw do
|
|
|
29
35
|
field :require_numbers, type: :boolean, default: false
|
|
30
36
|
end
|
|
31
37
|
|
|
38
|
+
scope :session do
|
|
39
|
+
field :browser_session_lifetime, type: :integer, default: 86400 # 24 hours in seconds
|
|
40
|
+
field :browser_session_remember_me_lifetime, type: :integer, default: 2592000 # 30 days in seconds
|
|
41
|
+
field :device_session_lifetime, type: :integer, default: 2592000 # 30 days in seconds
|
|
42
|
+
field :service_session_lifetime, type: :integer, default: 7776000 # 90 days in seconds
|
|
43
|
+
end
|
|
44
|
+
|
|
32
45
|
scope :oauth do
|
|
33
46
|
field :default_token_lifetime, type: :integer, default: 3600 # 1 hour in seconds
|
|
34
47
|
field :refresh_token_lifetime, type: :integer, default: 2592000 # 30 days in seconds
|
|
@@ -40,16 +53,7 @@ StandardConfig.schema.draw do
|
|
|
40
53
|
end
|
|
41
54
|
|
|
42
55
|
scope :social do
|
|
43
|
-
field :google_client_id, type: :string, default: nil
|
|
44
|
-
field :google_client_secret, type: :string, default: nil
|
|
45
|
-
field :apple_client_id, type: :string, default: nil
|
|
46
|
-
field :apple_client_secret, type: :string, default: nil
|
|
47
|
-
field :apple_private_key, type: :string, default: nil
|
|
48
|
-
field :apple_key_id, type: :string, default: nil
|
|
49
|
-
field :apple_team_id, type: :string, default: nil
|
|
50
|
-
field :apple_mobile_client_id, type: :string, default: nil
|
|
51
56
|
field :social_account_attributes, type: :any, default: nil
|
|
52
57
|
field :allowed_redirect_url_prefixes, type: :array, default: []
|
|
53
|
-
field :social_callback, type: :any, default: nil
|
|
54
58
|
end
|
|
55
59
|
end
|
data/lib/standard_id/engine.rb
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
module StandardId
|
|
2
2
|
class Engine < ::Rails::Engine
|
|
3
3
|
isolate_namespace StandardId
|
|
4
|
+
|
|
5
|
+
config.after_initialize do
|
|
6
|
+
if StandardId.config.events.enable_logging
|
|
7
|
+
StandardId::Events::Subscribers::LoggingSubscriber.attach
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
StandardId::Events::Subscribers::AccountStatusSubscriber.attach
|
|
11
|
+
StandardId::Events::Subscribers::AccountLockingSubscriber.attach
|
|
12
|
+
end
|
|
4
13
|
end
|
|
5
14
|
end
|
data/lib/standard_id/errors.rb
CHANGED
|
@@ -4,6 +4,18 @@ module StandardId
|
|
|
4
4
|
class InvalidSessionError < StandardError; end
|
|
5
5
|
class ExpiredSessionError < InvalidSessionError; end
|
|
6
6
|
class RevokedSessionError < InvalidSessionError; end
|
|
7
|
+
class AccountDeactivatedError < StandardError; end
|
|
8
|
+
|
|
9
|
+
class AccountLockedError < StandardError
|
|
10
|
+
attr_reader :account, :lock_reason, :locked_at
|
|
11
|
+
|
|
12
|
+
def initialize(account)
|
|
13
|
+
@account = account
|
|
14
|
+
@lock_reason = account.lock_reason
|
|
15
|
+
@locked_at = account.locked_at
|
|
16
|
+
super("Account has been locked#{lock_reason ? ": #{lock_reason}" : ""}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
7
19
|
|
|
8
20
|
class OAuthError < StandardError
|
|
9
21
|
def oauth_error_code
|