use nodedb_types::id::DatabaseId;
use tracing::debug;
use crate::control::security::identity::database_set::DatabaseSet;
use crate::control::security::identity::{AuthMethod, AuthenticatedIdentity, Role};
use crate::control::security::jwt::JwtError;
use crate::control::security::util::base64_url_decode;
use crate::control::state::SharedState;
use crate::types::TenantId;
use super::claim_mapping::apply_claim_mapping;
pub async fn verify_bearer_token(
state: &SharedState,
token: &str,
) -> crate::Result<AuthenticatedIdentity> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err(jwt_error_to_crate_error(JwtError::MalformedToken));
}
let payload_bytes = base64_url_decode(parts[1])
.ok_or_else(|| jwt_error_to_crate_error(JwtError::DecodingError))?;
let claims: crate::control::security::jwt::JwtClaims = sonic_rs::from_slice(&payload_bytes)
.map_err(|_| jwt_error_to_crate_error(JwtError::InvalidClaims))?;
let iss = &claims.iss;
let catalog = state
.credentials
.catalog()
.as_ref()
.ok_or_else(|| jwt_error_to_crate_error(JwtError::InvalidIssuer))?;
let provider = catalog
.list_oidc_providers()
.map_err(|_| jwt_error_to_crate_error(JwtError::InvalidIssuer))?
.into_iter()
.find(|p| p.issuer == *iss)
.ok_or_else(|| crate::Error::OidcUnknownProvider { iss: iss.clone() })?;
let jwks = state
.jwks_registry
.as_ref()
.ok_or_else(|| jwt_error_to_crate_error(JwtError::UnsupportedAlgorithm))?;
let verified_claims = jwks
.validate_with_catalog_provider(&provider.provider_name, &provider.jwks_uri, token)
.await
.map_err(jwt_error_to_crate_error)?;
if let Some(ref expected_aud) = provider.audience
&& verified_claims.aud != *expected_aud
{
return Err(jwt_error_to_crate_error(JwtError::InvalidAudience));
}
let mapping = apply_claim_mapping(&verified_claims, &provider.claim_mapping);
let default_db = mapping
.default_database
.map(DatabaseId::new)
.ok_or_else(|| crate::Error::OidcNoDefaultDatabase {
sub: verified_claims.sub.clone(),
})?;
let mut accessible: smallvec::SmallVec<[DatabaseId; 4]> = smallvec::smallvec![default_db];
for &db_raw in &mapping.accessible_databases {
let db = DatabaseId::new(db_raw);
if !accessible.contains(&db) {
accessible.push(db);
}
}
let roles: Vec<Role> = mapping
.roles
.iter()
.map(|r| match r.parse::<Role>() {
Ok(role) => role,
Err(never) => match never {},
})
.collect();
let username = if verified_claims.sub.is_empty() {
format!("oidc_{}", verified_claims.user_id)
} else {
verified_claims.sub.clone()
};
debug!(
provider = %provider.provider_name,
sub = %verified_claims.sub,
iss = %verified_claims.iss,
default_db = %default_db.as_u64(),
"OIDC login succeeded"
);
Ok(AuthenticatedIdentity {
user_id: verified_claims.user_id,
username,
tenant_id: TenantId::new(verified_claims.tenant_id),
auth_method: AuthMethod::OidcBearer,
roles,
is_superuser: false,
default_database: Some(default_db),
accessible_databases: DatabaseSet::Some(accessible),
})
}
fn jwt_error_to_crate_error(e: JwtError) -> crate::Error {
match e {
JwtError::Expired => crate::Error::SessionTokenExpired,
JwtError::MalformedToken
| JwtError::InvalidClaims
| JwtError::DecodingError
| JwtError::InvalidSignature
| JwtError::NotYetValid
| JwtError::InvalidIssuer
| JwtError::InvalidAudience
| JwtError::UnsupportedAlgorithm => crate::Error::BadRequest {
detail: e.to_string(),
},
}
}