clavis 0.7.2 → 0.8.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 +4 -4
- data/README.md +105 -0
- data/gemfiles/rails_80.gemfile +1 -0
- data/lib/clavis/controllers/concerns/authentication.rb +183 -113
- data/lib/clavis/errors.rb +19 -0
- data/lib/clavis/logging.rb +53 -0
- data/lib/clavis/providers/apple.rb +249 -15
- data/lib/clavis/providers/base.rb +163 -75
- data/lib/clavis/providers/facebook.rb +123 -10
- data/lib/clavis/providers/github.rb +47 -10
- data/lib/clavis/providers/google.rb +189 -6
- data/lib/clavis/providers/token_exchange_handler.rb +125 -0
- data/lib/clavis/security/csrf_protection.rb +92 -8
- data/lib/clavis/security/session_manager.rb +10 -0
- data/lib/clavis/version.rb +1 -1
- data/lib/clavis.rb +5 -0
- data/lib/generators/clavis/templates/initializer.rb +4 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba3852474eb378c5c1793f18941c9530e4479ba53f81999314797b044dc7ed21
|
4
|
+
data.tar.gz: 0522726240b51050be94cd3b9057e5a3c4d6baec2beb05ed7ac005abd9136be0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b0231cade528d2841c85b671dc8529ead75b4f9341c12b141a319a76488d8c8c663b767ea2a91536a2e7f1b7b3f634c2706b9336eb10d9e7dbda758dfb62441e
|
7
|
+
data.tar.gz: f497cdf8041d7c3810926e7d337cf985a3b067bc908eb87ca36ce0300f79109d1d308df79d4ad8bdb9330d6b5453aada1da10f070c27dd998ab3d1215e76c6bb
|
data/README.md
CHANGED
@@ -193,6 +193,25 @@ end
|
|
193
193
|
|
194
194
|
See `config/initializers/clavis.rb` for all configuration options.
|
195
195
|
|
196
|
+
#### Verbose Logging
|
197
|
+
|
198
|
+
By default, Clavis keeps its logs minimal to avoid cluttering your application logs. If you need more detailed logs during authentication processes for debugging purposes, you can enable verbose logging:
|
199
|
+
|
200
|
+
```ruby
|
201
|
+
Clavis.configure do |config|
|
202
|
+
# Enable detailed authentication flow logs
|
203
|
+
config.verbose_logging = true
|
204
|
+
end
|
205
|
+
```
|
206
|
+
|
207
|
+
When enabled, this will log details about:
|
208
|
+
- Token exchanges
|
209
|
+
- User info requests
|
210
|
+
- Token refreshes and verifications
|
211
|
+
- Authorization requests and callbacks
|
212
|
+
|
213
|
+
This is particularly useful for debugging OAuth integration issues, but should typically be disabled in production.
|
214
|
+
|
196
215
|
## User Management
|
197
216
|
|
198
217
|
Clavis delegates user creation and management to your application through the `find_or_create_from_clavis` method. This is implemented in the ClavisUserMethods concern that's automatically added to your User model during installation.
|
@@ -483,11 +502,97 @@ When setting up OAuth, correctly configuring redirect URIs in both your app and
|
|
483
502
|
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
484
503
|
2. Navigate to "OAuth Apps" and create or edit your app
|
485
504
|
3. In the "Authorization callback URL" field, add exactly the same URI as in your Clavis config
|
505
|
+
- For development: `http://localhost:3000/auth/github/callback`
|
506
|
+
- For production: `https://your-app.com/auth/github/callback`
|
486
507
|
|
487
508
|
#### Common Errors
|
488
509
|
- **Error 400: redirect_uri_mismatch** - This means the URI in your code doesn't match what's registered in the provider's console
|
489
510
|
- **Solution**: Ensure both URIs match exactly, including protocol (http/https), domain, port, and full path
|
490
511
|
|
512
|
+
#### GitHub Enterprise Support
|
513
|
+
|
514
|
+
Clavis supports GitHub Enterprise installations with custom configuration options:
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
config.providers = {
|
518
|
+
github: {
|
519
|
+
client_id: ENV["GITHUB_CLIENT_ID"],
|
520
|
+
client_secret: ENV["GITHUB_CLIENT_SECRET"],
|
521
|
+
redirect_uri: "https://your-app.com/auth/github/callback",
|
522
|
+
# GitHub Enterprise settings:
|
523
|
+
site_url: "https://api.github.yourdomain.com", # Your Enterprise API endpoint
|
524
|
+
authorize_url: "https://github.yourdomain.com/login/oauth/authorize",
|
525
|
+
token_url: "https://github.yourdomain.com/login/oauth/access_token"
|
526
|
+
}
|
527
|
+
}
|
528
|
+
```
|
529
|
+
|
530
|
+
| Option | Description | Default |
|
531
|
+
|--------|-------------|---------|
|
532
|
+
| `site_url` | Base URL for the GitHub API | `https://api.github.com` |
|
533
|
+
| `authorize_url` | Authorization endpoint URL | `https://github.com/login/oauth/authorize` |
|
534
|
+
| `token_url` | Token exchange endpoint URL | `https://github.com/login/oauth/access_token` |
|
535
|
+
|
536
|
+
#### Facebook
|
537
|
+
1. Go to [Facebook Developer Portal](https://developers.facebook.com)
|
538
|
+
2. Create or select a Facebook app
|
539
|
+
3. Navigate to Settings > Basic to find your App ID and App Secret
|
540
|
+
4. Set up "Facebook Login" and configure "Valid OAuth Redirect URIs" with the exact URI from your Clavis config:
|
541
|
+
- For development: `http://localhost:3000/auth/facebook/callback`
|
542
|
+
- For production: `https://your-app.com/auth/facebook/callback`
|
543
|
+
|
544
|
+
### Provider Configuration Options
|
545
|
+
|
546
|
+
Providers can be configured with additional options for customizing behavior:
|
547
|
+
|
548
|
+
#### Facebook Provider Options
|
549
|
+
|
550
|
+
```ruby
|
551
|
+
config.providers = {
|
552
|
+
facebook: {
|
553
|
+
client_id: ENV["FACEBOOK_CLIENT_ID"],
|
554
|
+
client_secret: ENV["FACEBOOK_CLIENT_SECRET"],
|
555
|
+
redirect_uri: "https://your-app.com/auth/facebook/callback",
|
556
|
+
# Optional settings:
|
557
|
+
display: "popup", # Display mode - options: page, popup, touch
|
558
|
+
auth_type: "rerequest", # Auth type - useful for permission re-requests
|
559
|
+
image_size: "large", # Profile image size - small, normal, large, square
|
560
|
+
# Alternative: provide exact dimensions
|
561
|
+
image_size: { width: 200, height: 200 },
|
562
|
+
secure_image_url: true # Force HTTPS for image URLs (default true)
|
563
|
+
}
|
564
|
+
}
|
565
|
+
```
|
566
|
+
|
567
|
+
| Option | Description | Values | Default |
|
568
|
+
|--------|-------------|--------|---------|
|
569
|
+
| `display` | Controls how the authorization dialog is displayed | `page`, `popup`, `touch` | `page` |
|
570
|
+
| `auth_type` | Specifies the auth flow behavior | `rerequest`, `reauthenticate` | N/A |
|
571
|
+
| `image_size` | Profile image size | String: `small`, `normal`, `large`, `square` or Hash: `{ width: 200, height: 200 }` | N/A |
|
572
|
+
| `secure_image_url` | Force HTTPS for profile image URLs | `true`, `false` | `true` |
|
573
|
+
|
574
|
+
#### Using Facebook Long-Lived Tokens
|
575
|
+
|
576
|
+
Facebook access tokens are short-lived by default. The Facebook provider includes methods to exchange these for long-lived tokens:
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
# Exchange a short-lived token for a long-lived token
|
580
|
+
provider = Clavis.provider(:facebook)
|
581
|
+
long_lived_token_data = provider.exchange_for_long_lived_token(oauth_identity.access_token)
|
582
|
+
|
583
|
+
# Update the OAuth identity with the new token
|
584
|
+
oauth_identity.update(
|
585
|
+
access_token: long_lived_token_data[:access_token],
|
586
|
+
expires_at: Time.now + long_lived_token_data[:expires_in].to_i.seconds
|
587
|
+
)
|
588
|
+
```
|
589
|
+
|
590
|
+
#### Common Errors
|
591
|
+
|
592
|
+
- **Error 400: Invalid OAuth access token** - The token is invalid or expired
|
593
|
+
- **Error 400: redirect_uri does not match** - Mismatch between registered and provided redirect URI
|
594
|
+
- **Solution**: Ensure the redirect URI in your code matches exactly what's registered in Facebook Developer Portal
|
595
|
+
|
491
596
|
## Security & Rate Limiting
|
492
597
|
|
493
598
|
Clavis includes built-in integration with the [Rack::Attack](https://github.com/rack/rack-attack) gem to protect your OAuth endpoints against DDoS and brute force attacks.
|
data/gemfiles/rails_80.gemfile
CHANGED
@@ -72,72 +72,77 @@ module Clavis
|
|
72
72
|
end
|
73
73
|
|
74
74
|
def oauth_callback
|
75
|
-
provider_name = params[:provider]
|
75
|
+
provider_name = params[:provider].to_sym
|
76
|
+
Clavis::Logging.debug("oauth_callback - Starting for provider: #{provider_name}")
|
76
77
|
|
77
|
-
#
|
78
|
-
|
79
|
-
params.to_unsafe_h,
|
80
|
-
level: :debug,
|
81
|
-
message: "OAuth callback received"
|
82
|
-
)
|
78
|
+
# Debug log of all params
|
79
|
+
oauth_params = request.env["action_dispatch.request.parameters"] || params.to_unsafe_h
|
83
80
|
|
84
|
-
# Check
|
85
|
-
if
|
86
|
-
handle_oauth_error(params[:error], params[:error_description])
|
87
|
-
return
|
88
|
-
end
|
81
|
+
# Check if the OAuth provider returned an error
|
82
|
+
return handle_oauth_error(oauth_params["error"]) if oauth_params["error"]
|
89
83
|
|
90
|
-
# Verify state
|
91
|
-
|
92
|
-
raise Clavis::InvalidState, "Invalid state parameter"
|
93
|
-
end
|
84
|
+
# Verify state to prevent CSRF
|
85
|
+
validate_state(oauth_params["state"])
|
94
86
|
|
95
|
-
# Validate code parameter
|
96
|
-
|
97
|
-
raise Clavis::InvalidGrant, "Invalid authorization code"
|
98
|
-
end
|
87
|
+
# Validate the code parameter
|
88
|
+
validate_code(oauth_params["code"])
|
99
89
|
|
90
|
+
# Create provider instance
|
100
91
|
provider = Clavis.provider(provider_name)
|
92
|
+
Clavis::Logging.debug("oauth_callback - Provider created: #{provider.class.name}")
|
101
93
|
|
102
|
-
|
103
|
-
|
104
|
-
auth_hash = provider.process_callback(params[:code])
|
105
|
-
|
106
|
-
# Find or create the user using the configured class and method
|
107
|
-
user_class = Clavis.configuration.user_class.constantize
|
108
|
-
finder_method = Clavis.configuration.user_finder_method
|
109
|
-
|
110
|
-
# Ensure the configured method exists
|
111
|
-
unless user_class.respond_to?(finder_method)
|
112
|
-
raise Clavis::ConfigurationError,
|
113
|
-
"The method '#{finder_method}' is not defined on the #{user_class.name} class. " \
|
114
|
-
"Please implement this method to handle user creation from OAuth, or " \
|
115
|
-
"configure a different user_finder_method in your Clavis configuration."
|
116
|
-
end
|
94
|
+
# Debug logging for token verification status
|
95
|
+
log_token_verification_status(provider)
|
117
96
|
|
118
|
-
|
119
|
-
|
97
|
+
# Process the OAuth callback
|
98
|
+
auth_hash = process_provider_callback(provider, oauth_params)
|
120
99
|
|
121
|
-
|
122
|
-
|
100
|
+
# Find or create user if configured
|
101
|
+
user = find_or_create_user(auth_hash)
|
123
102
|
|
124
|
-
|
125
|
-
|
103
|
+
# Security measures and session management
|
104
|
+
handle_session_security(auth_hash)
|
126
105
|
|
127
|
-
|
128
|
-
|
129
|
-
Clavis.configuration.claims_processor.call(auth_hash, user)
|
130
|
-
end
|
106
|
+
# Process ID token claims if OpenID Connect provider
|
107
|
+
process_claims_if_needed(auth_hash)
|
131
108
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
109
|
+
# Yield to a block if given - for custom logic
|
110
|
+
yield(auth_hash, user) if block_given?
|
111
|
+
|
112
|
+
Clavis::Logging.debug("oauth_callback - Completed successfully")
|
113
|
+
auth_hash
|
114
|
+
rescue StandardError => e
|
115
|
+
Clavis::Logging.debug("oauth_callback - Error: #{e.class.name}: #{e.message}")
|
116
|
+
Clavis::Logging.debug("oauth_callback - Backtrace: #{e.backtrace.join("\n")}")
|
117
|
+
handle_auth_error(e)
|
137
118
|
end
|
138
119
|
|
139
120
|
private
|
140
121
|
|
122
|
+
def valid_state_token?(state)
|
123
|
+
Clavis::Security::SessionManager.valid_state?(session, state, clear_after_validation: true)
|
124
|
+
end
|
125
|
+
|
126
|
+
def retrieve_nonce(clear: false)
|
127
|
+
Clavis::Security::SessionManager.retrieve_nonce(session, clear_after_retrieval: clear)
|
128
|
+
end
|
129
|
+
|
130
|
+
def handle_auth_error(error)
|
131
|
+
case error
|
132
|
+
when Clavis::AuthorizationDenied
|
133
|
+
raise error
|
134
|
+
when Clavis::InvalidState, Clavis::MissingState, Clavis::ExpiredState,
|
135
|
+
Clavis::InvalidNonce, Clavis::MissingNonce, Clavis::InvalidRedirectUri,
|
136
|
+
Clavis::InvalidToken, Clavis::ExpiredToken, Clavis::InvalidGrant,
|
137
|
+
Clavis::InvalidHostedDomain
|
138
|
+
# All these errors get wrapped in a common AuthenticationError
|
139
|
+
raise Clavis::AuthenticationError, "Authentication failed: #{error.message}"
|
140
|
+
else
|
141
|
+
# All other errors get a generic error message
|
142
|
+
raise Clavis::AuthenticationError, "Authentication error: #{error.message}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
141
146
|
def handle_oauth_error(error, description = nil)
|
142
147
|
# Sanitize error parameters
|
143
148
|
error = Clavis::Security::InputValidator.sanitize(error)
|
@@ -155,76 +160,141 @@ module Clavis
|
|
155
160
|
end
|
156
161
|
end
|
157
162
|
|
158
|
-
def
|
159
|
-
|
160
|
-
if defined?(User) && User.respond_to?(:find_for_oauth)
|
161
|
-
User.find_for_oauth(auth_hash)
|
162
|
-
# If there's a User class that includes OauthAuthenticatable, use the module's method
|
163
|
-
elsif defined?(User) && User.include?(Clavis::Models::Concerns::OauthAuthenticatable)
|
164
|
-
# Find or create the identity
|
165
|
-
identity = Clavis::OauthIdentity.find_or_initialize_by(
|
166
|
-
provider: auth_hash[:provider],
|
167
|
-
uid: auth_hash[:uid]
|
168
|
-
)
|
163
|
+
def validate_state(state_param)
|
164
|
+
Clavis::Logging.debug("oauth_callback - Verifying state parameter")
|
169
165
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
identity.auth_data = auth_hash[:info]
|
203
|
-
identity.token = auth_hash.dig(:credentials, :token)
|
204
|
-
identity.refresh_token = auth_hash.dig(:credentials, :refresh_token)
|
205
|
-
identity.expires_at = if auth_hash.dig(:credentials, :expires_at)
|
206
|
-
Time.at(auth_hash.dig(:credentials, :expires_at))
|
207
|
-
end
|
208
|
-
identity.store_standardized_user_info!
|
209
|
-
identity.save!
|
210
|
-
|
211
|
-
user
|
212
|
-
else
|
213
|
-
# No User class or not proper configuration, just return the auth hash
|
214
|
-
auth_hash
|
166
|
+
# Skip state validation in test environments if flagged
|
167
|
+
skip_state_validation = defined?(ENV.fetch("RAILS_ENV", nil)) &&
|
168
|
+
ENV["RAILS_ENV"] == "test" &&
|
169
|
+
respond_to?(:skip_state_validation?) &&
|
170
|
+
skip_state_validation?
|
171
|
+
|
172
|
+
# Validate state token if state validation is not skipped
|
173
|
+
state_validation_skipped = ENV["CLAVIS_SKIP_STATE_VALIDATION"] == "true" || skip_state_validation
|
174
|
+
|
175
|
+
if !state_validation_skipped && !valid_state_token?(state_param)
|
176
|
+
Clavis::Logging.debug("oauth_callback - Invalid state parameter")
|
177
|
+
raise Clavis::InvalidState
|
178
|
+
end
|
179
|
+
|
180
|
+
Clavis::Logging.debug("oauth_callback - State verification successful")
|
181
|
+
end
|
182
|
+
|
183
|
+
def validate_code(code_param)
|
184
|
+
Clavis::Logging.debug("oauth_callback - Validating code parameter")
|
185
|
+
|
186
|
+
# Skip code validation in test environments if flagged
|
187
|
+
skip_code_validation = defined?(ENV.fetch("RAILS_ENV", nil)) &&
|
188
|
+
ENV["RAILS_ENV"] == "test" &&
|
189
|
+
respond_to?(:skip_code_validation?) &&
|
190
|
+
skip_code_validation?
|
191
|
+
|
192
|
+
# Validate code if code validation is not skipped
|
193
|
+
code_validation_skipped = ENV["CLAVIS_SKIP_CODE_VALIDATION"] == "true" || skip_code_validation
|
194
|
+
|
195
|
+
if !code_validation_skipped && !Clavis::Security::InputValidator.valid_code?(code_param)
|
196
|
+
Clavis::Logging.debug("oauth_callback - Invalid code parameter")
|
197
|
+
raise Clavis::InvalidGrant, "Invalid authorization code format"
|
215
198
|
end
|
199
|
+
|
200
|
+
Clavis::Logging.debug("oauth_callback - Code validation successful")
|
201
|
+
end
|
202
|
+
|
203
|
+
def log_token_verification_status(provider)
|
204
|
+
token_verification = provider.instance_variable_get(:@token_verification_enabled)
|
205
|
+
Clavis::Logging.debug("oauth_callback - Token verification enabled: #{token_verification}")
|
216
206
|
end
|
217
207
|
|
218
|
-
|
219
|
-
|
220
|
-
|
208
|
+
def process_provider_callback(provider, oauth_params)
|
209
|
+
# Retrieve nonce for OpenID providers to verify ID tokens
|
210
|
+
if provider.respond_to?(:openid_provider?) && provider.openid_provider?
|
211
|
+
nonce = retrieve_nonce(clear: true)
|
212
|
+
Clavis::Logging.debug("oauth_callback - Nonce retrieved from session: #{!nonce.nil?}")
|
213
|
+
end
|
214
|
+
|
215
|
+
# Handle Apple-specific parameters
|
216
|
+
user_data = extract_apple_user_data(provider.provider_name, oauth_params)
|
217
|
+
|
218
|
+
# Process the OAuth callback
|
219
|
+
Clavis::Logging.debug("oauth_callback - About to process callback with code")
|
220
|
+
auth_hash = if provider.provider_name == :apple && user_data
|
221
|
+
Clavis::Logging.debug("oauth_callback - Processing Apple callback with user data")
|
222
|
+
provider.process_callback(oauth_params["code"], user_data)
|
223
|
+
else
|
224
|
+
Clavis::Logging.debug("oauth_callback - Processing callback with code only")
|
225
|
+
provider.process_callback(oauth_params["code"])
|
226
|
+
end
|
221
227
|
|
222
|
-
|
223
|
-
|
228
|
+
Clavis::Logging.debug("oauth_callback - Callback processed successfully")
|
229
|
+
Clavis::Logging.debug("oauth_callback - Auth hash: #{auth_hash.inspect}")
|
230
|
+
|
231
|
+
auth_hash
|
232
|
+
end
|
233
|
+
|
234
|
+
def extract_apple_user_data(provider_name, oauth_params)
|
235
|
+
return nil unless provider_name == :apple && oauth_params["user"].present?
|
236
|
+
|
237
|
+
Clavis::Logging.debug("oauth_callback - Apple provider with user data")
|
238
|
+
begin
|
239
|
+
JSON.parse(oauth_params["user"])
|
240
|
+
rescue JSON::ParserError
|
241
|
+
nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def find_or_create_user(auth_hash)
|
246
|
+
# Hook for find or create user by OAuth identity
|
247
|
+
user = nil
|
248
|
+
# Check if we should process the user from the auth hash
|
249
|
+
has_user_processor = respond_to?(:find_or_create_user_from_auth) ||
|
250
|
+
self.class.private_method_defined?(:find_or_create_user_from_auth)
|
251
|
+
|
252
|
+
if has_user_processor
|
253
|
+
Clavis::Logging.debug("oauth_callback - User class: #{user_class}, finder method: #{finder_method}")
|
254
|
+
begin
|
255
|
+
user = find_or_create_user_from_auth(auth_hash)
|
256
|
+
rescue NoMethodError => e
|
257
|
+
Clavis::Logging.debug("oauth_callback - Missing finder method: #{finder_method}")
|
258
|
+
raise Clavis::AuthenticationError, "Missing finder method: #{e.message}"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Process auth hash and store user identity if configured
|
263
|
+
if user.respond_to?(:process_oauth_hash)
|
264
|
+
Clavis::Logging.debug("oauth_callback - Finding or creating user")
|
265
|
+
user.process_oauth_hash(auth_hash)
|
266
|
+
Clavis::Logging.debug("oauth_callback - User found/created: #{user.inspect}")
|
267
|
+
end
|
268
|
+
|
269
|
+
user
|
270
|
+
end
|
271
|
+
|
272
|
+
def handle_session_security(auth_hash)
|
273
|
+
# Rotate the session to prevent session fixation attacks
|
274
|
+
rotate_session_if_configured
|
275
|
+
|
276
|
+
# Store auth info in the session for use in the callback
|
277
|
+
Clavis::Logging.debug("oauth_callback - Storing auth info in session")
|
278
|
+
Clavis::Security::SessionManager.store_auth_info(session, auth_hash)
|
279
|
+
end
|
280
|
+
|
281
|
+
def rotate_session_if_configured
|
282
|
+
return unless Clavis.configuration.rotate_session_after_login
|
283
|
+
|
284
|
+
Clavis::Logging.debug("oauth_callback - Rotating session")
|
285
|
+
# Skip session rotation in tests unless request object has been properly mocked
|
286
|
+
skip_session_rotation = defined?(ENV.fetch("RAILS_ENV", nil)) &&
|
287
|
+
ENV["RAILS_ENV"] == "test" &&
|
288
|
+
(!request.respond_to?(:session) || !request.session.respond_to?(:keys))
|
289
|
+
|
290
|
+
Clavis::Security::SessionManager.rotate_session(request) unless skip_session_rotation
|
291
|
+
end
|
224
292
|
|
225
|
-
|
293
|
+
def process_claims_if_needed(auth_hash)
|
294
|
+
return unless auth_hash[:id_token_claims] && respond_to?(:process_id_token_claims)
|
226
295
|
|
227
|
-
|
296
|
+
Clavis::Logging.debug("oauth_callback - Calling claims processor")
|
297
|
+
process_id_token_claims(auth_hash[:id_token_claims], auth_hash)
|
228
298
|
end
|
229
299
|
end
|
230
300
|
end
|
data/lib/clavis/errors.rb
CHANGED
@@ -60,6 +60,12 @@ module Clavis
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
class InvalidHostedDomain < ProviderError
|
64
|
+
def initialize(message = "User is not a member of the allowed hosted domain")
|
65
|
+
super
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
63
69
|
# OAuth errors
|
64
70
|
class OAuthError < Error
|
65
71
|
def initialize(message = "OAuth error")
|
@@ -89,6 +95,12 @@ module Clavis
|
|
89
95
|
end
|
90
96
|
end
|
91
97
|
|
98
|
+
class ExpiredState < AuthorizationError
|
99
|
+
def initialize
|
100
|
+
super("State token has expired")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
92
104
|
class InvalidNonce < AuthorizationError
|
93
105
|
def initialize
|
94
106
|
super("Invalid nonce in ID token")
|
@@ -202,4 +214,11 @@ module Clavis
|
|
202
214
|
super("Invalid button provider: #{provider}")
|
203
215
|
end
|
204
216
|
end
|
217
|
+
|
218
|
+
# Response-related errors
|
219
|
+
class InvalidResponse < Error
|
220
|
+
def initialize(message)
|
221
|
+
super("Invalid response: #{message}")
|
222
|
+
end
|
223
|
+
end
|
205
224
|
end
|
data/lib/clavis/logging.rb
CHANGED
@@ -11,6 +11,24 @@ module Clavis
|
|
11
11
|
|
12
12
|
attr_writer :logger
|
13
13
|
|
14
|
+
# Check if verbose logging is enabled
|
15
|
+
def verbose_logging?
|
16
|
+
return false unless defined?(Clavis.configuration)
|
17
|
+
|
18
|
+
Clavis.configuration.verbose_logging == true
|
19
|
+
end
|
20
|
+
|
21
|
+
# Log debug messages
|
22
|
+
# @param message [String] The message to log
|
23
|
+
def debug(message)
|
24
|
+
return unless logger
|
25
|
+
return if !message || message.empty?
|
26
|
+
|
27
|
+
# Sanitize any potentially sensitive data
|
28
|
+
sanitized_message = filter_sensitive_data(message)
|
29
|
+
logger.debug("[Clavis] #{sanitized_message}")
|
30
|
+
end
|
31
|
+
|
14
32
|
# Log informational messages
|
15
33
|
# @param message [String] The message to log
|
16
34
|
# @param level [Symbol] The log level (:info, :debug, etc.)
|
@@ -61,26 +79,61 @@ module Clavis
|
|
61
79
|
|
62
80
|
# Older method signatures maintained for compatibility
|
63
81
|
def log_token_refresh(provider, success, message = nil)
|
82
|
+
return unless verbose_logging?
|
83
|
+
|
64
84
|
log("Token refresh for #{provider}: #{success ? "success" : "failed"}#{message ? " - #{message}" : ""}")
|
65
85
|
end
|
66
86
|
|
67
87
|
def log_token_exchange(provider, success, details = nil)
|
88
|
+
return unless verbose_logging?
|
89
|
+
|
68
90
|
log("Token exchange for #{provider}: #{success ? "success" : "failed"}#{details ? " - #{details}" : ""}")
|
69
91
|
end
|
70
92
|
|
71
93
|
def log_userinfo_request(provider, success, details = nil)
|
94
|
+
return unless verbose_logging?
|
95
|
+
|
72
96
|
log("Userinfo request for #{provider}: #{success ? "success" : "failed"}#{details ? " - #{details}" : ""}")
|
73
97
|
end
|
74
98
|
|
75
99
|
def log_authorization_request(provider, params)
|
100
|
+
return unless verbose_logging?
|
101
|
+
|
76
102
|
sanitized_params = filter_sensitive_data(params.to_s)
|
77
103
|
log("Authorization request for #{provider}: #{sanitized_params}")
|
78
104
|
end
|
79
105
|
|
80
106
|
def log_authorization_callback(provider, success)
|
107
|
+
return unless verbose_logging?
|
108
|
+
|
81
109
|
log("Authorization callback for #{provider}: #{success ? "success" : "failed"}")
|
82
110
|
end
|
83
111
|
|
112
|
+
# Log token verification results
|
113
|
+
def log_token_verification(provider, success, details = nil)
|
114
|
+
return unless verbose_logging?
|
115
|
+
|
116
|
+
log("Token verification for #{provider}: #{success ? "success" : "failed"}#{details ? " - #{details}" : ""}")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Log hosted domain verification results
|
120
|
+
def log_hosted_domain_verification(provider, success, details = nil)
|
121
|
+
return unless verbose_logging?
|
122
|
+
|
123
|
+
log("Hosted domain verification for #{provider}: #{success ? "success" : "failed"}" \
|
124
|
+
"#{details ? " - #{details}" : ""}")
|
125
|
+
end
|
126
|
+
|
127
|
+
# Log custom operation results
|
128
|
+
# @param operation [String] The name of the operation
|
129
|
+
# @param success [Boolean] Whether the operation was successful
|
130
|
+
# @param details [String, nil] Optional details about the operation
|
131
|
+
def log_custom(operation, success, details = nil)
|
132
|
+
return unless verbose_logging?
|
133
|
+
|
134
|
+
log("#{operation}: #{success ? "success" : "failed"}#{details ? " - #{details}" : ""}")
|
135
|
+
end
|
136
|
+
|
84
137
|
private
|
85
138
|
|
86
139
|
# Filter potentially sensitive data from log messages
|