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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +529 -20
  3. data/app/controllers/concerns/standard_id/inertia_rendering.rb +49 -0
  4. data/app/controllers/concerns/standard_id/inertia_support.rb +31 -0
  5. data/app/controllers/concerns/standard_id/set_current_request_details.rb +19 -0
  6. data/app/controllers/concerns/standard_id/social_authentication.rb +86 -37
  7. data/app/controllers/concerns/standard_id/web_authentication.rb +50 -1
  8. data/app/controllers/standard_id/api/base_controller.rb +1 -0
  9. data/app/controllers/standard_id/api/oauth/callback/providers_controller.rb +7 -18
  10. data/app/controllers/standard_id/web/auth/callback/providers_controller.rb +33 -37
  11. data/app/controllers/standard_id/web/base_controller.rb +1 -0
  12. data/app/controllers/standard_id/web/login_controller.rb +12 -21
  13. data/app/controllers/standard_id/web/signup_controller.rb +11 -8
  14. data/app/forms/standard_id/web/signup_form.rb +32 -1
  15. data/app/models/standard_id/browser_session.rb +8 -0
  16. data/app/models/standard_id/client_secret_credential.rb +11 -0
  17. data/app/models/standard_id/device_session.rb +4 -0
  18. data/app/models/standard_id/identifier.rb +28 -0
  19. data/app/models/standard_id/service_session.rb +1 -1
  20. data/app/models/standard_id/session.rb +16 -2
  21. data/app/views/standard_id/web/auth/callback/providers/{apple_mobile.html.erb → mobile_callback.html.erb} +1 -1
  22. data/config/routes/api.rb +1 -2
  23. data/config/routes/web.rb +4 -3
  24. data/lib/generators/standard_id/install/templates/standard_id.rb +19 -8
  25. data/lib/standard_config/config.rb +13 -12
  26. data/lib/standard_config/config_provider.rb +6 -6
  27. data/lib/standard_config/schema.rb +2 -2
  28. data/lib/standard_id/account_locking.rb +86 -0
  29. data/lib/standard_id/account_status.rb +45 -0
  30. data/lib/standard_id/api/authentication_guard.rb +40 -1
  31. data/lib/standard_id/api/token_manager.rb +1 -1
  32. data/lib/standard_id/config/schema.rb +13 -9
  33. data/lib/standard_id/current_attributes.rb +9 -0
  34. data/lib/standard_id/engine.rb +9 -0
  35. data/lib/standard_id/errors.rb +12 -0
  36. data/lib/standard_id/events/definitions.rb +157 -0
  37. data/lib/standard_id/events/event.rb +123 -0
  38. data/lib/standard_id/events/subscribers/account_locking_subscriber.rb +17 -0
  39. data/lib/standard_id/events/subscribers/account_status_subscriber.rb +17 -0
  40. data/lib/standard_id/events/subscribers/base.rb +165 -0
  41. data/lib/standard_id/events/subscribers/logging_subscriber.rb +122 -0
  42. data/lib/standard_id/events.rb +137 -0
  43. data/lib/standard_id/oauth/authorization_code_flow.rb +10 -0
  44. data/lib/standard_id/oauth/client_credentials_flow.rb +31 -0
  45. data/lib/standard_id/oauth/password_flow.rb +36 -4
  46. data/lib/standard_id/oauth/passwordless_otp_flow.rb +38 -2
  47. data/lib/standard_id/oauth/subflows/social_login_grant.rb +11 -22
  48. data/lib/standard_id/oauth/token_grant_flow.rb +22 -1
  49. data/lib/standard_id/passwordless/base_strategy.rb +32 -0
  50. data/lib/standard_id/provider_registry.rb +73 -0
  51. data/lib/standard_id/{social_providers → providers}/apple.rb +46 -7
  52. data/lib/standard_id/providers/base.rb +242 -0
  53. data/lib/standard_id/{social_providers → providers}/google.rb +26 -7
  54. data/lib/standard_id/version.rb +1 -1
  55. data/lib/standard_id/web/authentication_guard.rb +29 -0
  56. data/lib/standard_id/web/session_manager.rb +39 -1
  57. data/lib/standard_id/web/token_manager.rb +2 -2
  58. data/lib/standard_id.rb +13 -2
  59. metadata +20 -6
  60. 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
@@ -21,7 +21,7 @@ module StandardId
21
21
  end
22
22
 
23
23
  def self.default_expiry
24
- 90.days.from_now # TODO: make this configurable
24
+ StandardId.config.session.service_session_lifetime.seconds.from_now
25
25
  end
26
26
 
27
27
  def refresh!
@@ -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
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
- <title>Continuing Sign in with Apple…</title>
5
+ <title>Continuing Sign in with <% @provider.provider_name.humanize %> …</title>
6
6
  <style>
7
7
  body {
8
8
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
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 :google, to: "providers#google"
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 :google, to: "providers#google"
12
- post :apple, to: "providers#apple"
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 provider credentials and hooks
27
- attr_accessor :google_client_id, :google_client_secret
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('=').to_sym
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
- elsif config_object.respond_to?(:[])
45
+ elsif config_object.respond_to?(:[])
46
46
  config_object[field_name] || config_object[field_name.to_s]
47
- else
47
+ else
48
48
  nil
49
- end
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?('=') ? method_name.to_s.chomp('=').to_sym : method_name.to_sym
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 'true', '1', 1 then true
59
- when 'false', '0', 0 then false
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: 30.days.from_now # TODO: make this configurable
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
@@ -0,0 +1,9 @@
1
+ module StandardId
2
+ module CurrentAttributes
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attribute :session, :account, :request_id, :ip_address, :user_agent
7
+ end
8
+ end
9
+ end
@@ -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
@@ -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