Skip to main content

canic_core/api/auth/
mod.rs

1//! Module: api::auth
2//!
3//! Responsibility: expose auth endpoint helpers and auth boundary adapters.
4//! Does not own: stable auth records, proof verification internals, or runtime policy.
5//! Boundary: endpoint layer maps public DTOs into ops/workflow auth calls.
6
7use crate::{
8    cdk::types::Principal,
9    dto::{auth::DelegatedToken, error::Error},
10    error::InternalErrorClass,
11    ops::{
12        auth::{AuthOps, VerifyDelegatedTokenRuntimeInput},
13        config::ConfigOps,
14        ic::IcOps,
15    },
16};
17
18// Internal auth pipeline:
19// - `attestation` owns role-attestation endpoint adapters.
20// - `root` owns root-only issuer policy, renewal, and chain-key proof adapters.
21// - `session` owns delegated-session ingress and replay/session state handling.
22// - `token` owns issuer-local delegated-token endpoint adapters.
23mod attestation;
24mod root;
25mod session;
26mod token;
27
28///
29/// AuthApi
30///
31/// Owns delegated-token helpers and root-signed role-attestation helpers.
32/// Owned by the API layer and called by generated endpoint wrappers.
33///
34
35pub struct AuthApi;
36
37impl AuthApi {
38    const DELEGATED_TOKENS_DISABLED: &str =
39        "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
40    const DELEGATED_TOKEN_ISSUER_DISABLED: &str = "delegated token issuer disabled for this canister; set subnets.<subnet>.canisters.<role>.auth.delegated_token_issuer=true in canic.toml";
41    const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
42    const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
43        b"canic-session-bootstrap-token-fingerprint";
44
45    // Map internal auth failures onto public endpoint errors.
46    fn map_auth_error(err: crate::InternalError) -> Error {
47        match err.class() {
48            InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
49                Error::internal(err.to_string())
50            }
51            _ => Error::from(err),
52        }
53    }
54
55    fn require_delegated_token_issuer_enabled() -> Result<(), Error> {
56        let delegated_tokens_cfg =
57            ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
58        if !delegated_tokens_cfg.enabled {
59            return Err(Error::invalid(Self::DELEGATED_TOKENS_DISABLED));
60        }
61
62        let canister_cfg = ConfigOps::current_canister().map_err(Self::map_auth_error)?;
63        if !canister_cfg.auth.delegated_token_issuer {
64            return Err(Error::forbidden(Self::DELEGATED_TOKEN_ISSUER_DISABLED));
65        }
66
67        Ok(())
68    }
69
70    // Verify delegated-token material and return the token subject.
71    //
72    // This is intentionally private: endpoint authorization must also bind the
73    // verified subject to the caller before dispatch.
74    fn verify_token_material(
75        token: &DelegatedToken,
76        max_cert_ttl_ns: u64,
77        max_token_ttl_ns: u64,
78        required_scopes: &[String],
79        now_ns: u64,
80    ) -> Result<Principal, Error> {
81        AuthOps::verify_token(VerifyDelegatedTokenRuntimeInput {
82            token,
83            caller: IcOps::msg_caller(),
84            max_cert_ttl_ns,
85            max_token_ttl_ns,
86            required_scopes,
87            now_ns,
88        })
89        .map(|verified| verified.subject)
90        .map_err(Self::map_auth_error)
91    }
92
93    // Resolve the delegated-token TTL ceiling for endpoint auth/session callers.
94    fn delegated_token_max_ttl_ns() -> Result<u64, Error> {
95        let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
96        if !cfg.enabled {
97            return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
98        }
99
100        let max_ttl_secs = cfg
101            .max_ttl_secs
102            .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS);
103        max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
104            Error::invalid("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
105        })
106    }
107}