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
@@ -0,0 +1,157 @@
1
+ module StandardId
2
+ module Events
3
+ module Definitions
4
+ AUTHENTICATION_ATTEMPT_STARTED = "authentication.attempt.started"
5
+ AUTHENTICATION_SUCCEEDED = "authentication.attempt.succeeded"
6
+ AUTHENTICATION_FAILED = "authentication.attempt.failed"
7
+ PASSWORD_VALIDATED = "authentication.password.validated"
8
+ PASSWORD_VALIDATION_FAILED = "authentication.password.failed"
9
+ OTP_VALIDATED = "authentication.otp.validated"
10
+ OTP_VALIDATION_FAILED = "authentication.otp.failed"
11
+
12
+ SESSION_CREATING = "session.creating"
13
+ SESSION_CREATED = "session.created"
14
+ SESSION_VALIDATING = "session.validating"
15
+ SESSION_VALIDATED = "session.validated"
16
+ SESSION_EXPIRED = "session.expired"
17
+ SESSION_REVOKED = "session.revoked"
18
+ SESSION_REFRESHED = "session.refreshed"
19
+
20
+ ACCOUNT_CREATING = "account.creating"
21
+ ACCOUNT_CREATED = "account.created"
22
+ ACCOUNT_VERIFIED = "account.verified"
23
+ ACCOUNT_STATUS_CHANGED = "account.status_changed"
24
+ ACCOUNT_ACTIVATED = "account.activated"
25
+ ACCOUNT_DEACTIVATED = "account.deactivated"
26
+ ACCOUNT_LOCKED = "account.locked"
27
+ ACCOUNT_UNLOCKED = "account.unlocked"
28
+
29
+ IDENTIFIER_CREATED = "identifier.created"
30
+ IDENTIFIER_VERIFICATION_STARTED = "identifier.verification.started"
31
+ IDENTIFIER_VERIFICATION_SUCCEEDED = "identifier.verification.succeeded"
32
+ IDENTIFIER_VERIFICATION_FAILED = "identifier.verification.failed"
33
+ IDENTIFIER_LINKED = "identifier.linked"
34
+
35
+ OAUTH_AUTHORIZATION_REQUESTED = "oauth.authorization.requested"
36
+ OAUTH_AUTHORIZATION_GRANTED = "oauth.authorization.granted"
37
+ OAUTH_AUTHORIZATION_DENIED = "oauth.authorization.denied"
38
+ OAUTH_TOKEN_ISSUING = "oauth.token.issuing"
39
+ OAUTH_TOKEN_ISSUED = "oauth.token.issued"
40
+ OAUTH_TOKEN_REFRESHED = "oauth.token.refreshed"
41
+ OAUTH_CODE_CONSUMED = "oauth.code.consumed"
42
+
43
+ PASSWORDLESS_CODE_REQUESTED = "passwordless.code.requested"
44
+ PASSWORDLESS_CODE_GENERATED = "passwordless.code.generated"
45
+ PASSWORDLESS_CODE_SENT = "passwordless.code.sent"
46
+ PASSWORDLESS_CODE_VERIFIED = "passwordless.code.verified"
47
+ PASSWORDLESS_CODE_FAILED = "passwordless.code.failed"
48
+ PASSWORDLESS_ACCOUNT_CREATED = "passwordless.account.created"
49
+
50
+ SOCIAL_AUTH_STARTED = "social.auth.started"
51
+ SOCIAL_CALLBACK_RECEIVED = "social.auth.callback_received"
52
+ SOCIAL_USER_INFO_FETCHED = "social.user_info.fetched"
53
+ SOCIAL_ACCOUNT_CREATED = "social.account.created"
54
+ SOCIAL_ACCOUNT_LINKED = "social.account.linked"
55
+ SOCIAL_AUTH_COMPLETED = "social.auth.completed"
56
+
57
+ CREDENTIAL_PASSWORD_CREATED = "credential.password.created"
58
+ CREDENTIAL_PASSWORD_RESET_INITIATED = "credential.password.reset_initiated"
59
+ CREDENTIAL_PASSWORD_RESET_COMPLETED = "credential.password.reset_completed"
60
+ CREDENTIAL_PASSWORD_CHANGED = "credential.password.changed"
61
+ CREDENTIAL_CLIENT_SECRET_CREATED = "credential.client_secret.created"
62
+ CREDENTIAL_CLIENT_SECRET_ROTATED = "credential.client_secret.rotated"
63
+ CREDENTIAL_CLIENT_SECRET_REVOKED = "credential.client_secret.revoked"
64
+
65
+ AUTHENTICATION_EVENTS = [
66
+ AUTHENTICATION_ATTEMPT_STARTED,
67
+ AUTHENTICATION_SUCCEEDED,
68
+ AUTHENTICATION_FAILED,
69
+ PASSWORD_VALIDATED,
70
+ PASSWORD_VALIDATION_FAILED,
71
+ OTP_VALIDATED,
72
+ OTP_VALIDATION_FAILED
73
+ ].freeze
74
+
75
+ SESSION_EVENTS = [
76
+ SESSION_CREATING,
77
+ SESSION_CREATED,
78
+ SESSION_VALIDATING,
79
+ SESSION_VALIDATED,
80
+ SESSION_EXPIRED,
81
+ SESSION_REVOKED,
82
+ SESSION_REFRESHED
83
+ ].freeze
84
+
85
+ ACCOUNT_EVENTS = [
86
+ ACCOUNT_CREATING,
87
+ ACCOUNT_CREATED,
88
+ ACCOUNT_VERIFIED,
89
+ ACCOUNT_STATUS_CHANGED,
90
+ ACCOUNT_ACTIVATED,
91
+ ACCOUNT_DEACTIVATED,
92
+ ACCOUNT_LOCKED,
93
+ ACCOUNT_UNLOCKED
94
+ ].freeze
95
+
96
+ IDENTIFIER_EVENTS = [
97
+ IDENTIFIER_CREATED,
98
+ IDENTIFIER_VERIFICATION_STARTED,
99
+ IDENTIFIER_VERIFICATION_SUCCEEDED,
100
+ IDENTIFIER_VERIFICATION_FAILED,
101
+ IDENTIFIER_LINKED
102
+ ].freeze
103
+
104
+ OAUTH_EVENTS = [
105
+ OAUTH_AUTHORIZATION_REQUESTED,
106
+ OAUTH_AUTHORIZATION_GRANTED,
107
+ OAUTH_AUTHORIZATION_DENIED,
108
+ OAUTH_TOKEN_ISSUING,
109
+ OAUTH_TOKEN_ISSUED,
110
+ OAUTH_TOKEN_REFRESHED,
111
+ OAUTH_CODE_CONSUMED
112
+ ].freeze
113
+
114
+ PASSWORDLESS_EVENTS = [
115
+ PASSWORDLESS_CODE_REQUESTED,
116
+ PASSWORDLESS_CODE_GENERATED,
117
+ PASSWORDLESS_CODE_SENT,
118
+ PASSWORDLESS_CODE_VERIFIED,
119
+ PASSWORDLESS_CODE_FAILED,
120
+ PASSWORDLESS_ACCOUNT_CREATED
121
+ ].freeze
122
+
123
+ SOCIAL_EVENTS = [
124
+ SOCIAL_AUTH_STARTED,
125
+ SOCIAL_CALLBACK_RECEIVED,
126
+ SOCIAL_USER_INFO_FETCHED,
127
+ SOCIAL_ACCOUNT_CREATED,
128
+ SOCIAL_ACCOUNT_LINKED,
129
+ SOCIAL_AUTH_COMPLETED
130
+ ].freeze
131
+
132
+ CREDENTIAL_EVENTS = [
133
+ CREDENTIAL_PASSWORD_CREATED,
134
+ CREDENTIAL_PASSWORD_RESET_INITIATED,
135
+ CREDENTIAL_PASSWORD_RESET_COMPLETED,
136
+ CREDENTIAL_PASSWORD_CHANGED,
137
+ CREDENTIAL_CLIENT_SECRET_CREATED,
138
+ CREDENTIAL_CLIENT_SECRET_ROTATED,
139
+ CREDENTIAL_CLIENT_SECRET_REVOKED
140
+ ].freeze
141
+
142
+ ALL_EVENTS = (
143
+ AUTHENTICATION_EVENTS +
144
+ SESSION_EVENTS +
145
+ ACCOUNT_EVENTS +
146
+ IDENTIFIER_EVENTS +
147
+ OAUTH_EVENTS +
148
+ PASSWORDLESS_EVENTS +
149
+ SOCIAL_EVENTS +
150
+ CREDENTIAL_EVENTS
151
+ ).freeze
152
+ end
153
+
154
+ # Include definitions at module level for convenience
155
+ include Definitions
156
+ end
157
+ end
@@ -0,0 +1,123 @@
1
+ module StandardId
2
+ module Events
3
+ # Event object that wraps ActiveSupport::Notifications event data
4
+ #
5
+ # Provides a clean interface for accessing event information in subscribers.
6
+ #
7
+ # @attr_reader [String] name The full namespaced event name
8
+ # @attr_reader [Hash] payload The event payload data
9
+ # @attr_reader [Time] started_at When the event started
10
+ # @attr_reader [Time] finished_at When the event finished
11
+ # @attr_reader [String] transaction_id Unique identifier for this event instance
12
+ #
13
+ class Event
14
+ attr_reader :name, :payload, :started_at, :finished_at, :transaction_id
15
+
16
+ # @param name [String] The full namespaced event name
17
+ # @param payload [Hash] The event payload
18
+ # @param started_at [Time] When the event started
19
+ # @param finished_at [Time] When the event finished
20
+ # @param transaction_id [String] Unique identifier for the event
21
+ def initialize(name:, payload:, started_at: nil, finished_at: nil, transaction_id: nil)
22
+ @name = name
23
+ @payload = payload.with_indifferent_access
24
+ @started_at = started_at
25
+ @finished_at = finished_at
26
+ @transaction_id = transaction_id
27
+ end
28
+
29
+ # Get the event name without the namespace prefix
30
+ #
31
+ # @return [String] The short event name
32
+ # @example
33
+ # event.short_name # => "authentication.attempt.succeeded"
34
+ #
35
+ def short_name
36
+ name.to_s.delete_prefix("#{Events::NAMESPACE}.")
37
+ end
38
+
39
+ # Get the event type from the payload
40
+ #
41
+ # @return [String, nil] The event type
42
+ #
43
+ def event_type
44
+ payload[:event_type]
45
+ end
46
+
47
+ # Get the unique event ID
48
+ #
49
+ # @return [String, nil] The event UUID
50
+ #
51
+ def event_id
52
+ payload[:event_id]
53
+ end
54
+
55
+ # Get the event timestamp
56
+ #
57
+ # @return [String, nil] ISO8601 formatted timestamp
58
+ #
59
+ def timestamp
60
+ payload[:timestamp]
61
+ end
62
+
63
+ # Calculate the duration of the event in milliseconds
64
+ #
65
+ # @return [Float, nil] Duration in milliseconds, or nil if timing not available
66
+ #
67
+ def duration_ms
68
+ return nil unless started_at && finished_at
69
+ (finished_at - started_at) * 1000
70
+ end
71
+
72
+ # Convenience method to access payload values
73
+ #
74
+ # @param key [Symbol, String] The payload key
75
+ # @return [Object] The value from the payload
76
+ #
77
+ def [](key)
78
+ payload[key]
79
+ end
80
+
81
+ # Check if the payload contains a key
82
+ #
83
+ # @param key [Symbol, String] The payload key
84
+ # @return [Boolean]
85
+ #
86
+ def key?(key)
87
+ payload.key?(key)
88
+ end
89
+
90
+ # Convert event to a hash representation
91
+ #
92
+ # @return [Hash] The event as a hash
93
+ #
94
+ def to_h
95
+ {
96
+ name: name,
97
+ short_name: short_name,
98
+ transaction_id: transaction_id,
99
+ started_at: started_at&.iso8601,
100
+ finished_at: finished_at&.iso8601,
101
+ duration_ms: duration_ms,
102
+ payload: payload.to_h
103
+ }
104
+ end
105
+
106
+ # Convert event to JSON
107
+ #
108
+ # @return [String] JSON representation
109
+ #
110
+ def to_json(*args)
111
+ to_h.to_json(*args)
112
+ end
113
+
114
+ # String representation for debugging
115
+ #
116
+ # @return [String]
117
+ #
118
+ def inspect
119
+ "#<#{self.class.name} name=#{name} event_id=#{event_id} duration_ms=#{duration_ms}>"
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,17 @@
1
+ module StandardId
2
+ module Events
3
+ module Subscribers
4
+ class AccountLockingSubscriber < Base
5
+ subscribe_to StandardId::Events::ACCOUNT_LOCKED
6
+
7
+ def call(event)
8
+ account = event[:account]
9
+ active_sessions = account.sessions.active
10
+ active_sessions.find_each do |session|
11
+ session.revoke!(reason: "account_locked")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module StandardId
2
+ module Events
3
+ module Subscribers
4
+ class AccountStatusSubscriber < Base
5
+ subscribe_to StandardId::Events::ACCOUNT_DEACTIVATED
6
+
7
+ def call(event)
8
+ account = event[:account]
9
+ active_sessions = account.sessions.active
10
+ active_sessions.find_each do |session|
11
+ session.revoke!(reason: "account_deactivated")
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,165 @@
1
+ module StandardId
2
+ module Events
3
+ module Subscribers
4
+ # Base class for event subscribers
5
+ #
6
+ # @example Single event subscription
7
+ # class AuditSubscriber < StandardId::Events::Subscribers::Base
8
+ # subscribe_to StandardId::Events::AUTHENTICATION_SUCCEEDED
9
+ #
10
+ # def call(event)
11
+ # AuditLog.create!(
12
+ # event_type: event.short_name,
13
+ # account_id: event[:account]&.id,
14
+ # ip_address: event[:ip_address],
15
+ # metadata: event.payload
16
+ # )
17
+ # end
18
+ # end
19
+ #
20
+ # @example Multiple event subscription
21
+ # class SecurityAlertSubscriber < StandardId::Events::Subscribers::Base
22
+ # subscribe_to StandardId::Events::AUTHENTICATION_FAILED,
23
+ # StandardId::Events::SESSION_REVOKED,
24
+ # StandardId::Events::ACCOUNT_LOCKED
25
+ #
26
+ # def call(event)
27
+ # SecurityMailer.alert(event.to_h).deliver_later
28
+ # end
29
+ # end
30
+ #
31
+ # @example Pattern subscription
32
+ # class MetricsSubscriber < StandardId::Events::Subscribers::Base
33
+ # subscribe_to_pattern(/authentication/)
34
+ #
35
+ # def call(event)
36
+ # StatsD.increment("standard_id.#{event.short_name}")
37
+ # end
38
+ # end
39
+ #
40
+ class Base
41
+ class << self
42
+ # Subscribe to specific event(s)
43
+ #
44
+ # @param event_names [Array<String>] Event name constants
45
+ # @return [void]
46
+ #
47
+ def subscribe_to(*event_names)
48
+ @subscribed_events = event_names.flatten
49
+ end
50
+
51
+ # Subscribe to events matching a pattern
52
+ #
53
+ # @param pattern [Regexp] Pattern to match event names
54
+ # @return [void]
55
+ #
56
+ def subscribe_to_pattern(pattern)
57
+ @subscription_pattern = pattern
58
+ end
59
+
60
+ # Get the subscribed events
61
+ #
62
+ # @return [Array<String>]
63
+ #
64
+ def subscribed_events
65
+ @subscribed_events || []
66
+ end
67
+
68
+ # Get the subscription pattern
69
+ #
70
+ # @return [Regexp, nil]
71
+ #
72
+ def subscription_pattern
73
+ @subscription_pattern
74
+ end
75
+
76
+ # Register this subscriber with the event system
77
+ #
78
+ # @return [Array<Object>] The subscription handles
79
+ #
80
+ def attach
81
+ instance = new
82
+ @subscriptions ||= []
83
+
84
+ if subscription_pattern
85
+ @subscriptions << Events.subscribe(subscription_pattern) do |event|
86
+ instance.handle(event)
87
+ end
88
+ end
89
+
90
+ subscribed_events.each do |event_name|
91
+ @subscriptions << Events.subscribe(event_name) do |event|
92
+ instance.handle(event)
93
+ end
94
+ end
95
+
96
+ @subscriptions
97
+ end
98
+
99
+ # Unregister this subscriber from the event system
100
+ #
101
+ # @return [void]
102
+ #
103
+ def detach
104
+ return unless @subscriptions
105
+
106
+ @subscriptions.each do |subscription|
107
+ Events.unsubscribe(subscription)
108
+ end
109
+ @subscriptions = []
110
+ end
111
+
112
+ # Check if the subscriber is currently attached
113
+ #
114
+ # @return [Boolean]
115
+ #
116
+ def attached?
117
+ @subscriptions&.any?
118
+ end
119
+ end
120
+
121
+ # Handle an event
122
+ #
123
+ # Override this method to add custom handling logic like
124
+ # async processing or error handling. By default, it calls
125
+ # the `call` method.
126
+ #
127
+ # @param event [StandardId::Events::Event] The event to handle
128
+ # @return [void]
129
+ #
130
+ def handle(event)
131
+ call(event)
132
+ rescue StandardError => e
133
+ handle_error(e, event)
134
+ end
135
+
136
+ # Process the event
137
+ #
138
+ # Subclasses must implement this method.
139
+ #
140
+ # @param event [StandardId::Events::Event] The event to process
141
+ # @raise [NotImplementedError] If not implemented by subclass
142
+ #
143
+ def call(event)
144
+ raise NotImplementedError, "#{self.class.name} must implement #call"
145
+ end
146
+
147
+ # Handle errors during event processing
148
+ #
149
+ # Override this method to customize error handling.
150
+ # By default, it logs the error and re-raises.
151
+ #
152
+ # @param error [StandardError] The error that occurred
153
+ # @param event [StandardId::Events::Event] The event being processed
154
+ # @raise [StandardError] Re-raises the error by default
155
+ #
156
+ def handle_error(error, event)
157
+ StandardId.logger.error(
158
+ "[StandardId::Events] Error in #{self.class.name} handling #{event.short_name}: #{error.message}"
159
+ )
160
+ raise error
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,122 @@
1
+ module StandardId
2
+ module Events
3
+ module Subscribers
4
+ class LoggingSubscriber < Base
5
+ subscribe_to_pattern(/\Astandard_id\./)
6
+
7
+ LOG_LEVELS = {
8
+ "authentication.attempt.started" => :debug,
9
+ "authentication.attempt.succeeded" => :info,
10
+ "authentication.attempt.failed" => :warn,
11
+ "authentication.password.validated" => :debug,
12
+ "authentication.password.failed" => :warn,
13
+ "authentication.otp.validated" => :debug,
14
+ "authentication.otp.failed" => :warn,
15
+ "session.creating" => :debug,
16
+ "session.created" => :info,
17
+ "session.validating" => :debug,
18
+ "session.validated" => :debug,
19
+ "session.expired" => :info,
20
+ "session.revoked" => :info,
21
+ "session.refreshed" => :debug,
22
+ "account.creating" => :debug,
23
+ "account.created" => :info,
24
+ "account.verified" => :info,
25
+ "account.status_changed" => :warn,
26
+ "account.activated" => :info,
27
+ "account.deactivated" => :warn,
28
+ "account.locked" => :warn,
29
+ "account.unlocked" => :info,
30
+ "identifier.created" => :debug,
31
+ "identifier.verification.started" => :debug,
32
+ "identifier.verification.succeeded" => :info,
33
+ "identifier.verification.failed" => :warn,
34
+ "identifier.linked" => :info,
35
+ "oauth.authorization.requested" => :debug,
36
+ "oauth.authorization.granted" => :info,
37
+ "oauth.authorization.denied" => :info,
38
+ "oauth.token.issuing" => :debug,
39
+ "oauth.token.issued" => :info,
40
+ "oauth.token.refreshed" => :debug,
41
+ "oauth.code.consumed" => :debug,
42
+ "passwordless.code.requested" => :debug,
43
+ "passwordless.code.generated" => :debug,
44
+ "passwordless.code.sent" => :info,
45
+ "passwordless.code.verified" => :info,
46
+ "passwordless.code.failed" => :warn,
47
+ "passwordless.account.created" => :info,
48
+ "social.auth.started" => :debug,
49
+ "social.auth.callback_received" => :debug,
50
+ "social.user_info.fetched" => :debug,
51
+ "social.account.created" => :info,
52
+ "social.account.linked" => :info,
53
+ "social.auth.completed" => :info,
54
+ "credential.password.created" => :info,
55
+ "credential.password.reset_initiated" => :info,
56
+ "credential.password.reset_completed" => :info,
57
+ "credential.password.changed" => :info,
58
+ "credential.client_secret.created" => :info,
59
+ "credential.client_secret.rotated" => :warn
60
+ }.freeze
61
+
62
+ DEFAULT_LOG_LEVEL = :debug
63
+
64
+ def call(event)
65
+ return unless logging_enabled?
66
+
67
+ log_level = LOG_LEVELS.fetch(event.short_name, DEFAULT_LOG_LEVEL)
68
+ payload = build_payload(event, log_level)
69
+
70
+ case log_level
71
+ when :debug then StandardId.logger.debug(payload)
72
+ when :info then StandardId.logger.info(payload)
73
+ when :warn then StandardId.logger.warn(payload)
74
+ when :error then StandardId.logger.error(payload)
75
+ end
76
+ end
77
+
78
+ def handle_error(error, event)
79
+ StandardId.logger.error({
80
+ subject: "standard_id.logging_subscriber.error",
81
+ event_type: event.short_name,
82
+ error: error.message
83
+ })
84
+ end
85
+
86
+ private
87
+
88
+ def logging_enabled?
89
+ config = StandardId.config
90
+ return true unless config.respond_to?(:events)
91
+
92
+ config.events.enable_logging
93
+ end
94
+
95
+ def build_payload(event, log_level)
96
+ payload = {
97
+ subject: "standard_id.#{event.short_name}",
98
+ severity: log_level.to_s
99
+ }
100
+
101
+ payload[:duration] = event.duration_ms.round(2) if event.duration_ms
102
+
103
+ if (account = event[:account])
104
+ payload[:account_id] = account.respond_to?(:id) ? account.id : account
105
+ elsif event[:account_id]
106
+ payload[:account_id] = event[:account_id]
107
+ end
108
+
109
+ payload[:login] = event[:account_lookup] || event[:login] || event[:username] if event[:account_lookup] || event[:login] || event[:username]
110
+ payload[:auth_method] = event[:auth_method] if event[:auth_method]
111
+ payload[:grant_type] = event[:grant_type] if event[:grant_type]
112
+ payload[:provider] = event[:provider] if event[:provider]
113
+ payload[:session_type] = event[:session_type] if event[:session_type]
114
+ payload[:ip_address] = event[:ip_address] if event[:ip_address]
115
+ payload[:error_code] = event[:error_code] if event[:error_code]
116
+
117
+ payload
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end