Skip to content

Commit 7ab7024

Browse files
committed
feat: implement password hashing and verification utilities
1 parent 62ac6cd commit 7ab7024

File tree

3 files changed

+199
-106
lines changed

3 files changed

+199
-106
lines changed

src/visualization/jwt.rs

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -239,18 +239,12 @@ pub struct JwtTokenMap {
239239
/// The algorithm used to sign and verify JWT tokens.
240240
/// Default is HS256 (HMAC with SHA-256).
241241
algorithm: Algorithm,
242-
242+
243243
/// Additional claims to include in tokens
244244
///
245245
/// This allows adding extra claims to the JWT tokens being generated.
246246
/// Used for including user-specific information in tokens.
247247
claims: HashMap<String, Value>,
248-
249-
/// Token hook for extensibility
250-
///
251-
/// A callback function that allows modifying token responses during issuance.
252-
/// Used for enriching tokens with additional data.
253-
token_hook: Option<Box<dyn FnMut(&mut oxide_auth::code_grant::accesstoken::TokenResponse) + Send>>,
254248
}
255249

256250
impl JwtTokenMap {
@@ -267,7 +261,6 @@ impl JwtTokenMap {
267261
usage_counter: 0,
268262
algorithm: Algorithm::HS256, // Default to HMAC-SHA256
269263
claims: HashMap::new(),
270-
token_hook: None,
271264
}
272265
}
273266

@@ -288,7 +281,6 @@ impl JwtTokenMap {
288281
usage_counter: 0,
289282
algorithm,
290283
claims: HashMap::new(),
291-
token_hook: None,
292284
}
293285
}
294286

@@ -320,7 +312,6 @@ impl JwtTokenMap {
320312
usage_counter: 0,
321313
algorithm: Algorithm::RS256,
322314
claims: HashMap::new(),
323-
token_hook: None,
324315
})
325316
}
326317

@@ -342,9 +333,40 @@ impl JwtTokenMap {
342333
self
343334
}
344335

345-
/// Create JWT claims from a grant
336+
/// Add user information to token claims that will be included in the next issued token
337+
pub fn add_user_claims(&mut self, username: &str, permissions: &[String]) -> &mut Self {
338+
// Clear previous user claims to avoid accumulation
339+
self.claims.retain(|key, _| !key.starts_with("user_"));
340+
341+
// Add user information to claims that will be included in JWT
342+
self.claims.insert(
343+
"user_id".to_string(),
344+
Value::public(Some(username.to_string()))
345+
);
346+
347+
// Add permissions as a space-separated string
348+
let perms_str = permissions.join(" ");
349+
self.claims.insert(
350+
"user_permissions".to_string(),
351+
Value::public(Some(perms_str))
352+
);
353+
354+
// Common identity claims
355+
self.claims.insert(
356+
"preferred_username".to_string(),
357+
Value::public(Some(username.to_string())),
358+
);
359+
self.claims.insert(
360+
"user_name".to_string(),
361+
Value::public(Some(username.to_string())),
362+
);
363+
364+
self
365+
}
366+
367+
/// Create JWT claims from a grant, including any additional user claims
346368
fn create_claims(&self, grant: &Grant, now: DateTime<Utc>, expiry: DateTime<Utc>) -> JwtClaims {
347-
// Create a map for any public extensions
369+
// Create a map for any public extensions and additional claims
348370
let mut metadata = HashMap::new();
349371

350372
// Add grant extensions to metadata
@@ -356,12 +378,18 @@ impl JwtTokenMap {
356378
}
357379
}
358380

359-
// Add any additional claims
381+
// Add any additional claims (including user claims)
360382
for (key, value) in &self.claims {
361-
if let Some(val) = value {
362-
metadata.insert(key.to_string(), val.to_string());
363-
} else {
364-
metadata.insert(key.to_string(), "true".to_string());
383+
match value {
384+
Value::Public(Some(val)) => {
385+
metadata.insert(key.to_string(), val.to_string());
386+
}
387+
Value::Public(None) => {
388+
metadata.insert(key.to_string(), "true".to_string());
389+
}
390+
Value::Private(_) => {
391+
// Skip private values
392+
}
365393
}
366394
}
367395

@@ -387,27 +415,6 @@ impl JwtTokenMap {
387415
},
388416
}
389417
}
390-
391-
/// Add user information to token claims
392-
pub fn add_user_claims(&mut self, username: &str, permissions: &[String]) -> &mut Self {
393-
// Add user information to claims
394-
self.claims.insert("sub".to_string(), Value::public(Some(username.to_string())));
395-
396-
// Add permissions as a space-separated string
397-
let perms_str = permissions.join(" ");
398-
self.claims.insert("permissions".to_string(), Value::public(Some(perms_str)));
399-
400-
// Common identity claims
401-
self.claims.insert("preferred_username".to_string(), Value::public(Some(username.to_string())));
402-
self.claims.insert("name".to_string(), Value::public(Some(username.to_string())));
403-
404-
self
405-
}
406-
407-
/// Set a token hook function
408-
pub fn set_token_hook(&mut self, hook: Box<dyn FnMut(&mut oxide_auth::code_grant::accesstoken::TokenResponse) + Send>) {
409-
self.token_hook = Some(hook);
410-
}
411418
}
412419

413420
impl Issuer for JwtTokenMap {
@@ -418,7 +425,7 @@ impl Issuer for JwtTokenMap {
418425
grant.until = now + duration;
419426
}
420427

421-
// Generate claims
428+
// Generate claims (this now includes user claims automatically)
422429
let claims = self.create_claims(&grant, now, grant.until);
423430

424431
// Create JWT token with specific algorithm
@@ -446,25 +453,16 @@ impl Issuer for JwtTokenMap {
446453
}
447454

448455
// Create the token response
449-
let mut token = IssuedToken {
456+
let token = IssuedToken {
450457
token: access_token,
451458
refresh: refresh_token,
452459
until: grant.until,
453460
token_type: TokenType::Bearer,
454461
};
455462

456-
// Apply token hook if present
457-
if let Some(hook) = &mut self.token_hook {
458-
// Convert IssuedToken to TokenResponse for the hook
459-
let mut token_response = oxide_auth::code_grant::accesstoken::TokenResponse::from(token.clone());
460-
461-
// Call the hook with our token response
462-
hook(&mut token_response);
463-
464-
// No need to convert back as we already have our IssuedToken
465-
}
463+
// Clear user claims after use to prevent them from being included in subsequent tokens
464+
self.claims.retain(|key, _| !key.starts_with("user_"));
466465

467-
// Return the issued token
468466
Ok(token)
469467
}
470468

@@ -527,25 +525,16 @@ impl Issuer for JwtTokenMap {
527525
}
528526

529527
// Create the refreshed token
530-
let mut token = RefreshedToken {
528+
let token = RefreshedToken {
531529
token: new_access_token,
532530
refresh: new_refresh_token,
533531
until: grant.until,
534532
token_type: TokenType::Bearer,
535533
};
536534

537-
// Apply token hook if present
538-
if let Some(hook) = &mut self.token_hook {
539-
// Convert RefreshedToken to TokenResponse for the hook
540-
let mut token_response = oxide_auth::code_grant::accesstoken::TokenResponse::from(token.clone());
541-
542-
// Call the hook with our token response
543-
hook(&mut token_response);
544-
545-
// No need to convert back as we already have our RefreshedToken
546-
}
535+
// Clear user claims after use
536+
self.claims.retain(|key, _| !key.starts_with("user_"));
547537

548-
// Return the refreshed token
549538
Ok(token)
550539
}
551540

@@ -711,12 +700,6 @@ impl JwtIssuer {
711700
}
712701
self
713702
}
714-
715-
/// Set a token hook function
716-
pub fn set_token_hook(&mut self, hook: Box<dyn FnMut(&mut oxide_auth::code_grant::accesstoken::TokenResponse) + Send>) {
717-
let mut map = self.0.lock().unwrap();
718-
map.set_token_hook(hook);
719-
}
720703

721704
/// Print the decoded contents of a JWT token for debugging purposes
722705
/// Returns Ok if the token could be decoded, Err otherwise

src/visualization/oxide_auth.rs

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ pub struct AuthenticatedUser(pub UserSession);
9292
impl<'r> FromRequest<'r> for AuthenticatedUser {
9393
type Error = ();
9494

95+
/// Extracts an `AuthenticatedUser` from the request if a valid session cookie is present.
96+
///
97+
/// This function checks for a private cookie named "user_session". If the cookie
98+
/// exists and can be successfully parsed into a `UserSession` (containing username
99+
/// and permissions), it returns `Outcome::Success` with the `AuthenticatedUser`.
100+
/// If the cookie is missing or invalid, it returns `Outcome::Forward(())` to
101+
/// allow the request to continue without authentication.
95102
async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> {
96103
// Check for user session cookie
97104
let cookies = request.cookies();
@@ -110,7 +117,7 @@ impl<'r> FromRequest<'r> for AuthenticatedUser {
110117
}
111118
}
112119

113-
Outcome::Forward(())
120+
Outcome::Forward(Status::Unauthorized)
114121
}
115122
}
116123

@@ -380,8 +387,8 @@ pub fn authorize_consent(
380387
// Ensure user is authenticated
381388
if authenticated_user.is_none() {
382389
return Err(OAuthFailure::from(
383-
oxide_auth::endpoint::OAuthError::MissingParameter("User not authenticated".into()),
384-
));
390+
oxide_auth::endpoint::OAuthError::BadRequest),
391+
);
385392
}
386393

387394
let user = authenticated_user.unwrap();
@@ -424,52 +431,45 @@ pub async fn token<'r>(
424431
mut oauth: OAuthRequest<'r>,
425432
state: &State<OxideState>,
426433
) -> Result<OAuthResponse, OAuthFailure> {
427-
// Get a copy of the raw body content to inspect the grant_type
428434
let body = oauth.urlbody()?;
429435
let grant_type = body.unique_value("grant_type");
430-
debug!("grant_type: {:?}", body.unique_value("grant_type"));
431-
432-
// Before executing the token flow, we'll set up a hook to enrich tokens with user info
433-
let token_hook = |token: &mut oxide_auth::code_grant::accesstoken::TokenResponse| {
434-
// Get the user ID from the token grant
435-
if let Some(owner_id) = token.owner_id() {
436-
let username = owner_id.to_string();
437-
438-
// Find the user in our access config
439-
for user in &state.access_config.0 {
440-
if user.user == username {
441-
// Get a mutable reference to the issuer to add user claims
442-
if let Ok(mut issuer) = state.issuer.lock() {
443-
issuer.add_user_claims(&username, &user.permissions);
444-
}
445-
break;
436+
debug!("grant_type: {:?}", grant_type);
437+
438+
// Extract username from the OAuth request if available
439+
let username = body.unique_value("username")
440+
.or_else(|| {
441+
// Try to extract from other sources if needed
442+
// This might need adjustment based on your OAuth flow
443+
None
444+
});
445+
446+
// If we have a username, add user claims before token issuance
447+
if let Some(username_cow) = username {
448+
let username_str = username_cow.as_ref();
449+
450+
// Find the user in our access config and add claims
451+
for user in &state.access_config.0 {
452+
if user.user == username_str {
453+
if let Ok(mut issuer) = state.issuer.lock() {
454+
issuer.add_user_claims(&username_str, &user.permissions);
446455
}
456+
break;
447457
}
448458
}
449-
};
459+
}
450460

451461
if grant_type == Some(std::borrow::Cow::Borrowed("refresh_token")) {
452-
// Handle refresh token flow with hook
462+
// Handle refresh token flow
453463
let mut endpoint = state.endpoint().refresh_flow();
454-
455-
// Get a mutable reference to the issuer and set the token hook
456-
let mut issuer = endpoint.issuer_mut();
457-
issuer.set_token_hook(Box::new(token_hook));
458-
459-
return endpoint
464+
endpoint
460465
.execute(oauth)
461-
.map_err(|err| err.pack::<OAuthFailure>());
466+
.map_err(|err| err.pack::<OAuthFailure>())
462467
} else {
463-
// Handle authorization code flow with hook
468+
// Handle authorization code flow
464469
let mut endpoint = state.endpoint().access_token_flow();
465-
466-
// Get a mutable reference to the issuer and set the token hook
467-
let mut issuer = endpoint.issuer_mut();
468-
issuer.set_token_hook(Box::new(token_hook));
469-
470-
return endpoint
470+
endpoint
471471
.execute(oauth)
472-
.map_err(|err| err.pack::<OAuthFailure>());
472+
.map_err(|err| err.pack::<OAuthFailure>())
473473
}
474474
}
475475

0 commit comments

Comments
 (0)