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::{api::is_controller as caller_is_controller, types::Principal},
9    dto::{
10        auth::{
11            ActiveDelegationProofStatusResponse, DelegatedToken, DelegatedTokenGetRequest,
12            DelegatedTokenPrepareRequest, DelegatedTokenPrepareResponse,
13            InstallActiveDelegationProofRequest, InstallActiveDelegationProofResponse,
14            RoleAttestationGetRequest, RoleAttestationPrepareResponse, RoleAttestationRequest,
15            RootDelegationProofBatchGetRequest, RootDelegationProofBatchGetResponse,
16            RootDelegationProofBatchInstallRequest, RootDelegationProofBatchInstallResponse,
17            RootDelegationProofBatchPrepareRequest, RootDelegationProofBatchPrepareResponse,
18            RootDelegationRenewalProofBatchGetRequest,
19            RootDelegationRenewalProvisionerListResponse, RootDelegationRenewalProvisionerResponse,
20            RootDelegationRenewalProvisionerUpsertRequest, RootDelegationRenewalWorkListResponse,
21            RootIssuerPolicyResponse, RootIssuerPolicyUpsertRequest,
22            RootIssuerRenewalStatusRequest, RootIssuerRenewalStatusResponse,
23            RootIssuerRenewalTemplateResponse, RootIssuerRenewalTemplateUpsertRequest,
24            SignedRoleAttestation,
25        },
26        error::Error,
27    },
28    error::InternalErrorClass,
29    ops::{
30        auth::{AuthOps, VerifyDelegatedTokenRuntimeInput},
31        config::ConfigOps,
32        ic::IcOps,
33        runtime::env::EnvOps,
34    },
35    workflow::runtime::auth::RuntimeAuthWorkflow,
36};
37
38// Internal auth pipeline:
39// - `session` owns delegated-session ingress and replay/session state handling.
40mod session;
41
42///
43/// AuthApi
44///
45/// Owns delegated-token helpers and root-signed role-attestation helpers.
46/// Owned by the API layer and called by generated endpoint wrappers.
47///
48
49pub struct AuthApi;
50
51impl AuthApi {
52    const DELEGATED_TOKENS_DISABLED: &str =
53        "delegated token auth disabled; set auth.delegated_tokens.enabled=true in canic.toml";
54    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";
55    const MAX_DELEGATED_SESSION_TTL_SECS: u64 = 24 * 60 * 60;
56    const SESSION_BOOTSTRAP_TOKEN_FINGERPRINT_DOMAIN: &[u8] =
57        b"canic-session-bootstrap-token-fingerprint";
58
59    // Map internal auth failures onto public endpoint errors.
60    fn map_auth_error(err: crate::InternalError) -> Error {
61        match err.class() {
62            InternalErrorClass::Infra | InternalErrorClass::Ops | InternalErrorClass::Workflow => {
63                Error::internal(err.to_string())
64            }
65            _ => Error::from(err),
66        }
67    }
68
69    fn require_delegated_token_issuer_enabled() -> Result<(), Error> {
70        let delegated_tokens_cfg =
71            ConfigOps::delegated_tokens_config().map_err(Self::map_auth_error)?;
72        if !delegated_tokens_cfg.enabled {
73            return Err(Error::invalid(Self::DELEGATED_TOKENS_DISABLED));
74        }
75
76        let canister_cfg = ConfigOps::current_canister().map_err(Self::map_auth_error)?;
77        if !canister_cfg.auth.delegated_token_issuer {
78            return Err(Error::forbidden(Self::DELEGATED_TOKEN_ISSUER_DISABLED));
79        }
80
81        Ok(())
82    }
83
84    fn require_delegation_renewal_provisioner_or_controller() -> Result<bool, Error> {
85        let caller = IcOps::msg_caller();
86        if caller_is_controller(&caller) {
87            return Ok(true);
88        }
89        if AuthOps::is_delegation_renewal_provisioner(caller) {
90            return Ok(false);
91        }
92
93        Err(Error::forbidden(
94            "caller is not a controller or enabled delegation renewal provisioner",
95        ))
96    }
97
98    // Verify delegated-token material and return the token subject.
99    //
100    // This is intentionally private: endpoint authorization must also bind the
101    // verified subject to the caller before dispatch.
102    fn verify_token_material(
103        token: &DelegatedToken,
104        max_cert_ttl_ns: u64,
105        max_token_ttl_ns: u64,
106        required_scopes: &[String],
107        now_ns: u64,
108    ) -> Result<Principal, Error> {
109        AuthOps::verify_token(VerifyDelegatedTokenRuntimeInput {
110            token,
111            caller: IcOps::msg_caller(),
112            max_cert_ttl_ns,
113            max_token_ttl_ns,
114            required_scopes,
115            now_ns,
116        })
117        .map(|verified| verified.subject)
118        .map_err(Self::map_auth_error)
119    }
120
121    /// Prepare a delegated token from the issuer-local active delegation proof.
122    pub fn prepare_delegated_token(
123        request: DelegatedTokenPrepareRequest,
124    ) -> Result<DelegatedTokenPrepareResponse, Error> {
125        Self::require_delegated_token_issuer_enabled()?;
126        RuntimeAuthWorkflow::prepare_delegated_token(request).map_err(Self::map_auth_error)
127    }
128
129    /// Retrieve a prepared delegated token with its issuer canister-signature proof.
130    pub fn get_delegated_token(request: DelegatedTokenGetRequest) -> Result<DelegatedToken, Error> {
131        Self::require_delegated_token_issuer_enabled()?;
132
133        AuthOps::get_delegated_token_issuer_proof(request.claims_hash, IcOps::msg_caller())
134            .map_err(Self::map_auth_error)
135    }
136
137    /// Install validated root-certified delegation material for issuer-local token issuance.
138    pub fn install_active_delegation_proof(
139        request: InstallActiveDelegationProofRequest,
140    ) -> Result<InstallActiveDelegationProofResponse, Error> {
141        Self::require_delegated_token_issuer_enabled()?;
142
143        let active_proof =
144            AuthOps::install_active_delegation_proof(request.proof, IcOps::msg_caller())
145                .map_err(Self::map_auth_error)?;
146
147        Ok(InstallActiveDelegationProofResponse { active_proof })
148    }
149
150    /// Report non-secret issuer-local active proof lifecycle status for provisioners.
151    pub fn active_delegation_proof_status() -> Result<ActiveDelegationProofStatusResponse, Error> {
152        Self::require_delegated_token_issuer_enabled()?;
153        Ok(AuthOps::active_delegation_proof_status(IcOps::now_nanos()))
154    }
155
156    /// Upsert root issuer policy from the local root controller path.
157    pub fn upsert_root_issuer_policy_root(
158        request: RootIssuerPolicyUpsertRequest,
159    ) -> Result<RootIssuerPolicyResponse, Error> {
160        EnvOps::require_root().map_err(Error::from)?;
161        AuthOps::upsert_root_issuer_policy(request).map_err(Self::map_auth_error)
162    }
163
164    /// Upsert root-managed renewal template from the local root controller path.
165    pub fn upsert_root_issuer_renewal_template_root(
166        request: RootIssuerRenewalTemplateUpsertRequest,
167    ) -> Result<RootIssuerRenewalTemplateResponse, Error> {
168        EnvOps::require_root().map_err(Error::from)?;
169        let response = AuthOps::upsert_root_issuer_renewal_template(request, IcOps::now_nanos())
170            .map_err(Self::map_auth_error)?;
171        if response.template.enabled {
172            RuntimeAuthWorkflow::start_root_delegation_renewal_timer_if_configured()
173                .map_err(Self::map_auth_error)?;
174        }
175        Ok(response)
176    }
177
178    /// Report root-managed renewal template/state for one issuer.
179    pub fn root_issuer_renewal_status_root(
180        request: RootIssuerRenewalStatusRequest,
181    ) -> Result<RootIssuerRenewalStatusResponse, Error> {
182        EnvOps::require_root().map_err(Error::from)?;
183        Ok(AuthOps::root_issuer_renewal_status(request))
184    }
185
186    /// Upsert a constrained root-managed renewal provisioner.
187    pub fn upsert_delegation_renewal_provisioner_root(
188        request: RootDelegationRenewalProvisionerUpsertRequest,
189    ) -> Result<RootDelegationRenewalProvisionerResponse, Error> {
190        EnvOps::require_root().map_err(Error::from)?;
191        Ok(AuthOps::upsert_delegation_renewal_provisioner(request))
192    }
193
194    /// List constrained root-managed renewal provisioners.
195    pub fn delegation_renewal_provisioners_root()
196    -> Result<RootDelegationRenewalProvisionerListResponse, Error> {
197        EnvOps::require_root().map_err(Error::from)?;
198        Ok(AuthOps::delegation_renewal_provisioners())
199    }
200
201    /// List root-scheduled renewal batches ready for a constrained provisioner.
202    pub fn delegation_renewal_work_root() -> Result<RootDelegationRenewalWorkListResponse, Error> {
203        EnvOps::require_root().map_err(Error::from)?;
204        Self::require_delegation_renewal_provisioner_or_controller()?;
205        Ok(AuthOps::delegation_renewal_work(IcOps::now_nanos()))
206    }
207
208    /// Prepare root delegation proof batch metadata from the local root update path.
209    pub fn prepare_delegation_proof_batch_root(
210        request: RootDelegationProofBatchPrepareRequest,
211    ) -> Result<RootDelegationProofBatchPrepareResponse, Error> {
212        EnvOps::require_root().map_err(Error::from)?;
213        let max_cert_ttl_ns = Self::delegated_token_max_ttl_ns()?;
214        AuthOps::prepare_delegation_proof_batch(request, max_cert_ttl_ns, IcOps::now_nanos())
215            .map_err(Self::map_auth_error)
216    }
217
218    /// Retrieve root delegation proofs from the local direct root query path.
219    pub fn get_delegation_proof_batch_root(
220        request: RootDelegationProofBatchGetRequest,
221    ) -> Result<RootDelegationProofBatchGetResponse, Error> {
222        EnvOps::require_root().map_err(Error::from)?;
223        AuthOps::get_delegation_proof_batch(request).map_err(Self::map_auth_error)
224    }
225
226    /// Retrieve root-scheduled renewal proofs from the local direct root query path.
227    pub fn get_delegation_renewal_proof_batch_root(
228        request: RootDelegationRenewalProofBatchGetRequest,
229    ) -> Result<RootDelegationProofBatchGetResponse, Error> {
230        EnvOps::require_root().map_err(Error::from)?;
231        Self::require_delegation_renewal_provisioner_or_controller()?;
232        AuthOps::get_delegation_renewal_proof_batch(request).map_err(Self::map_auth_error)
233    }
234
235    /// Install retrieved root delegation proof batches from the local root update path.
236    pub async fn install_delegation_proof_batch_root(
237        request: RootDelegationProofBatchInstallRequest,
238    ) -> Result<RootDelegationProofBatchInstallResponse, Error> {
239        EnvOps::require_root().map_err(Error::from)?;
240        let caller_is_controller = Self::require_delegation_renewal_provisioner_or_controller()?;
241        if !caller_is_controller {
242            AuthOps::ensure_delegation_renewal_batch_scheduled(
243                request.batch_id,
244                IcOps::now_nanos(),
245            )
246            .map_err(Self::map_auth_error)?;
247        }
248        RuntimeAuthWorkflow::install_delegation_proof_batch_root(request)
249            .await
250            .map_err(Self::map_auth_error)
251    }
252
253    /// Prepare a root-certified role attestation from the local root update path.
254    pub fn prepare_role_attestation_root(
255        request: RoleAttestationRequest,
256    ) -> Result<RoleAttestationPrepareResponse, Error> {
257        RuntimeAuthWorkflow::prepare_role_attestation_root(request).map_err(Self::map_auth_error)
258    }
259
260    /// Retrieve a prepared role attestation with its root canister-signature proof.
261    pub fn get_role_attestation_root(
262        request: RoleAttestationGetRequest,
263    ) -> Result<SignedRoleAttestation, Error> {
264        EnvOps::require_root().map_err(Error::from)?;
265        AuthOps::get_role_attestation(IcOps::msg_caller(), request.payload_hash)
266            .map_err(Self::map_auth_error)
267    }
268
269    /// Verify a role attestation locally from its embedded root proof.
270    pub async fn verify_role_attestation(
271        attestation: &SignedRoleAttestation,
272        min_accepted_epoch: u64,
273    ) -> Result<(), Error> {
274        crate::workflow::runtime::auth::RuntimeAuthWorkflow::verify_role_attestation(
275            attestation,
276            min_accepted_epoch,
277        )
278        .await
279        .map_err(Self::map_auth_error)
280    }
281
282    // Resolve the delegated-token TTL ceiling for endpoint auth/session callers.
283    fn delegated_token_max_ttl_ns() -> Result<u64, Error> {
284        let cfg = ConfigOps::delegated_tokens_config().map_err(Error::from)?;
285        if !cfg.enabled {
286            return Err(Error::forbidden(Self::DELEGATED_TOKENS_DISABLED));
287        }
288
289        let max_ttl_secs = cfg
290            .max_ttl_secs
291            .unwrap_or(Self::MAX_DELEGATED_SESSION_TTL_SECS);
292        max_ttl_secs.checked_mul(1_000_000_000).ok_or_else(|| {
293            Error::invalid("auth.delegated_tokens.max_ttl_secs overflows nanoseconds")
294        })
295    }
296}