//! OIDC device-flow login.
//!
//! Wire flow (matching CONSOLE_API.md §3.5):
//!
//! 1. `POST /api/oidc/auth { op: <provider>, id, uuid, deviceInfo }` →
//!    `{ code: <opaque-poll-handle>, url: <browser auth URL> }`. The client
//!    opens `url` in the user's browser.
//! 2. The IdP redirects the browser back to our `/oidc/callback?code=...&state=...`.
//!    That handler exchanges the IdP code for a token, fetches userinfo,
//!    upserts a local user, mints our own access token, and marks the
//!    session `success`.
//! 3. The client polls `GET /api/oidc/auth-query?code=&id=&uuid=` until it
//!    sees a wrapped `AuthBody` envelope.
//!
//! Auth on the IdP side is handled by the provider's standard OAuth2
//! authorization-code flow. We keep the hbbs side minimal: discovery via
//! `<issuer>/.well-known/openid-configuration`, no JWT verification (we
//! trust the userinfo endpoint, authenticated via the access token).

pub mod auth;
pub mod callback;
pub mod discovery;
pub mod poll;
pub mod providers;

use crate::api::error::ApiError;
use crate::api::state::AppState;
use crate::database::OidcProviderRow;

pub(crate) const OIDC_SESSION_TTL_SECS: i64 = 600; // 10 minutes — the user has to sign in fast

/// Convenience: resolve a provider name to its row, or an ApiError if it
/// doesn't exist or is disabled.
pub(crate) async fn require_provider(
    state: &AppState,
    name: &str,
) -> Result<OidcProviderRow, ApiError> {
    state
        .db
        .oidc_provider_get(name)
        .await
        .map_err(|e| ApiError::Internal(e.to_string()))?
        .ok_or_else(|| ApiError::BadRequest(format!("unknown OIDC provider: {}", name)))
}

/// 24 random bytes, base64url-encoded → ~32 characters. Used for both the
/// poll-handle (`code`) and the CSRF state.
pub(crate) fn random_token() -> String {
    base64::encode_config(
        sodiumoxide::randombytes::randombytes(24),
        base64::URL_SAFE_NO_PAD,
    )
}
