feat(auth): add OpenID Connect (OIDC) authentication support
Implements OIDC Authorization Code Flow for external identity providers (Authentik, Keycloak, etc.) with JIT user provisioning. New features: - OidcService with OpenID Discovery, JWKS caching, RS256 ID token validation - Authorization Code Flow: /api/auth/oidc/authorize -> IdP -> /api/auth/oidc/callback - JIT user provisioning from OIDC claims (sub, email, name, groups) - OIDC group-to-role mapping (admin_groups config) - Provider info endpoint: GET /api/auth/oidc/providers - Option to disable password login entirely (OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN) - Auto-provision toggle (OXICLOUD_OIDC_AUTO_PROVISION) - Email collision detection (security: prevents account takeover) Configuration (env vars): - OXICLOUD_OIDC_ENABLED, OXICLOUD_OIDC_ISSUER_URL - OXICLOUD_OIDC_CLIENT_ID, OXICLOUD_OIDC_CLIENT_SECRET - OXICLOUD_OIDC_REDIRECT_URI, OXICLOUD_OIDC_SCOPES - OXICLOUD_OIDC_FRONTEND_URL, OXICLOUD_OIDC_PROVIDER_NAME - OXICLOUD_OIDC_AUTO_PROVISION, OXICLOUD_OIDC_ADMIN_GROUPS - OXICLOUD_OIDC_DISABLE_PASSWORD_LOGIN DB migration: - ALTER TABLE auth.users ADD oidc_provider, oidc_subject columns - UNIQUE index on (oidc_provider, oidc_subject) Files changed: 14 files, ~1400 lines added Dependencies: reqwest 0.12 (rustls-tls-webpki-roots), base64 0.22
This commit is contained in:
@@ -94,6 +94,50 @@ pub trait UserStoragePort: Send + Sync + 'static {
|
||||
|
||||
/// Cambia la contraseña de un usuario
|
||||
async fn change_password(&self, user_id: &str, password_hash: &str) -> Result<(), DomainError>;
|
||||
|
||||
/// Finds a user by OIDC provider + subject pair
|
||||
async fn get_user_by_oidc_subject(&self, provider: &str, subject: &str) -> Result<User, DomainError>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// OIDC Port
|
||||
// ============================================================================
|
||||
|
||||
/// Represents the token set returned by the OIDC provider after code exchange
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OidcTokenSet {
|
||||
pub access_token: String,
|
||||
pub id_token: String,
|
||||
pub refresh_token: Option<String>,
|
||||
}
|
||||
|
||||
/// Claims extracted from the validated OIDC ID token
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OidcIdClaims {
|
||||
pub sub: String,
|
||||
pub email: Option<String>,
|
||||
pub preferred_username: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub groups: Vec<String>,
|
||||
}
|
||||
|
||||
/// Port for OIDC operations — implemented in infrastructure layer
|
||||
#[async_trait]
|
||||
pub trait OidcServicePort: Send + Sync + 'static {
|
||||
/// Get the authorization URL for redirecting the user to the IdP
|
||||
fn get_authorize_url(&self, state: &str) -> Result<String, DomainError>;
|
||||
|
||||
/// Exchange an authorization code for tokens
|
||||
async fn exchange_code(&self, code: &str) -> Result<OidcTokenSet, DomainError>;
|
||||
|
||||
/// Validate an ID token and extract claims
|
||||
async fn validate_id_token(&self, id_token: &str) -> Result<OidcIdClaims, DomainError>;
|
||||
|
||||
/// Fetch user info from the UserInfo endpoint (fallback for missing ID token claims)
|
||||
async fn fetch_user_info(&self, access_token: &str) -> Result<OidcIdClaims, DomainError>;
|
||||
|
||||
/// Get the OIDC provider display name
|
||||
fn provider_name(&self) -> &str;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
Reference in New Issue
Block a user