canic_core/api/access/
auth.rs

1use crate::{
2    InternalError,
3    access::{self, AccessRuleFn},
4    cdk::types::Principal,
5    dto::{
6        auth::{DelegatedToken, DelegatedTokenClaims},
7        error::Error,
8    },
9    ids::CanisterRole,
10};
11
12///
13/// AuthAccessApi
14///
15/// WHY THIS FILE EXISTS
16/// ---------------------
17/// This module defines the **public authorization API** exposed to:
18///   - macro-expanded endpoints
19///   - DSL-generated auth guards
20///   - higher-level application code
21///
22/// It intentionally sits between:
23///   - `access::*` (internal authorization logic)
24///   - `dto::error::Error` (external error surface)
25///
26/// Responsibilities:
27///
28/// 1. **Error domain translation**
29///    Access-layer errors are internal and must never leak directly.
30///    This API converts them into stable, user-facing error types.
31///
32/// 2. **Signature normalization**
33///    This is the canonical place to adapt access-layer contracts
34///    (e.g. `&'static str` policy constants) to callers.
35///
36/// 3. **Stability during refactors**
37///    Access internals may change freely as long as this API remains stable.
38///    Callers MUST NOT depend directly on `access::*`.
39///
40/// If this file appears repetitive, that is intentional.
41/// DO NOT collapse it into the access layer.
42///
43
44pub struct AuthAccessApi;
45
46impl AuthAccessApi {
47    // --- Composition ---------------------------------------------------
48
49    /// Require that ALL access rules succeed.
50    ///
51    /// Intended for use by DSL-expanded authorization pipelines.
52    pub async fn require_all(rules: Vec<AccessRuleFn>) -> Result<(), Error> {
53        access::require_all(rules)
54            .await
55            .map_err(InternalError::from)
56            .map_err(Error::from)
57    }
58
59    /// Require that ANY access rule succeeds.
60    pub async fn require_any(rules: Vec<AccessRuleFn>) -> Result<(), Error> {
61        access::require_any(rules)
62            .await
63            .map_err(InternalError::from)
64            .map_err(Error::from)
65    }
66
67    // --- Topology / identity rules ------------------------------------
68
69    pub async fn is_app_directory_role(caller: Principal, role: CanisterRole) -> Result<(), Error> {
70        access::rule::is_app_directory_role(caller, role)
71            .await
72            .map_err(InternalError::from)
73            .map_err(Error::from)
74    }
75
76    pub async fn is_child(caller: Principal) -> Result<(), Error> {
77        access::topology::is_child(caller)
78            .await
79            .map_err(InternalError::from)
80            .map_err(Error::from)
81    }
82
83    pub async fn is_controller(caller: Principal) -> Result<(), Error> {
84        access::env::is_controller(caller)
85            .await
86            .map_err(InternalError::from)
87            .map_err(Error::from)
88    }
89
90    pub async fn is_parent(caller: Principal) -> Result<(), Error> {
91        access::topology::is_parent(caller)
92            .await
93            .map_err(InternalError::from)
94            .map_err(Error::from)
95    }
96
97    pub async fn is_principal(caller: Principal, expected: Principal) -> Result<(), Error> {
98        access::rule::is_principal(caller, expected)
99            .await
100            .map_err(InternalError::from)
101            .map_err(Error::from)
102    }
103
104    pub async fn is_registered_to_subnet(caller: Principal) -> Result<(), Error> {
105        access::rule::is_registered_to_subnet(caller)
106            .await
107            .map_err(InternalError::from)
108            .map_err(Error::from)
109    }
110
111    pub async fn caller_is_root(caller: Principal) -> Result<(), Error> {
112        access::topology::caller_is_root(caller)
113            .await
114            .map_err(InternalError::from)
115            .map_err(Error::from)
116    }
117
118    pub async fn is_same_canister(caller: Principal) -> Result<(), Error> {
119        access::topology::is_same_canister(caller)
120            .await
121            .map_err(InternalError::from)
122            .map_err(Error::from)
123    }
124
125    pub async fn is_subnet_directory_role(
126        caller: Principal,
127        role: CanisterRole,
128    ) -> Result<(), Error> {
129        access::rule::is_subnet_directory_role(caller, role)
130            .await
131            .map_err(InternalError::from)
132            .map_err(Error::from)
133    }
134
135    pub async fn is_whitelisted(caller: Principal) -> Result<(), Error> {
136        access::env::is_whitelisted(caller)
137            .await
138            .map_err(InternalError::from)
139            .map_err(Error::from)
140    }
141
142    // --- Delegated token auth -----------------------------------------
143
144    /// Verify a delegated token read from the ingress payload.
145    ///
146    /// Intended for DSL-generated auth guards only.
147    pub async fn verify_delegated_token() -> Result<(), Error> {
148        access::auth::verify_delegated_token()
149            .await
150            .map_err(InternalError::from)
151            .map_err(Error::from)
152    }
153
154    pub async fn verify_token(
155        token: DelegatedToken,
156        authority_pid: Principal,
157        now_secs: u64,
158    ) -> Result<(), Error> {
159        access::auth::verify_token(token, authority_pid, now_secs)
160            .await
161            .map_err(InternalError::from)
162            .map_err(Error::from)
163    }
164
165    /// Require that the delegated token includes a specific scope.
166    ///
167    /// `required_scope` MUST be a compile-time policy constant.
168    pub async fn require_scope(
169        claims: DelegatedTokenClaims,
170        required_scope: &'static str,
171    ) -> Result<(), Error> {
172        access::auth::require_scope(claims, required_scope)
173            .await
174            .map_err(InternalError::from)
175            .map_err(Error::from)
176    }
177
178    /// Require that the delegated token targets a specific audience.
179    ///
180    /// `required_audience` MUST be a compile-time policy constant.
181    pub async fn require_audience(
182        claims: DelegatedTokenClaims,
183        required_audience: &'static str,
184    ) -> Result<(), Error> {
185        access::auth::require_audience(claims, required_audience)
186            .await
187            .map_err(InternalError::from)
188            .map_err(Error::from)
189    }
190}