From 175aaecff5988c505d92b4f55264037f1be1fbf1 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 25 Jan 2026 15:44:57 +0100 Subject: [PATCH 01/64] feat(auth): authenticate workload controller --- src/console/src/auth/delegation.rs | 4 +- src/console/src/auth/register.rs | 4 +- src/console/src/impls.rs | 14 ++- src/console/src/types.rs | 9 +- src/libs/auth/src/openid/impls.rs | 6 +- src/libs/auth/src/openid/jwt/verify.rs | 93 ++++++++++++------- src/libs/auth/src/openid/types.rs | 2 +- src/libs/auth/src/openid/verify/mod.rs | 5 + .../src/openid/{verify.rs => verify/user.rs} | 42 +++++++-- src/libs/auth/src/openid/verify/workload.rs | 69 ++++++++++++++ src/libs/satellite/src/api/controllers.rs | 10 ++ src/libs/satellite/src/auth/delegation.rs | 4 +- .../satellite/src/controllers/authenticate.rs | 24 +++++ src/libs/satellite/src/controllers/mod.rs | 4 + src/libs/satellite/src/controllers/types.rs | 22 +++++ src/libs/satellite/src/user/core/impls.rs | 4 +- src/observatory/src/openid/scheduler.rs | 4 +- 17 files changed, 258 insertions(+), 62 deletions(-) create mode 100644 src/libs/auth/src/openid/verify/mod.rs rename src/libs/auth/src/openid/{verify.rs => verify/user.rs} (61%) create mode 100644 src/libs/auth/src/openid/verify/workload.rs create mode 100644 src/libs/satellite/src/controllers/authenticate.rs create mode 100644 src/libs/satellite/src/controllers/types.rs diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index 78a9fc0a96..699a9b7b22 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -16,7 +16,7 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::verify_openid_credentials_with_jwks_renewal( + let (credential, provider) = match openid::verify_user_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -40,7 +40,7 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::verify_openid_credentials_with_cached_jwks( + let (credential, provider) = match openid::verify_user_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 5dec898413..730b455dc7 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -1,6 +1,6 @@ use crate::accounts::{get_optional_account, init_account, update_provider}; -use crate::types::state::OpenId; use crate::types::state::{Account, OpenIdData, Provider}; +use crate::types::state::{OpenId, OpenIdAuthProvider}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; use junobuild_auth::openid::types::interface::OpenIdCredential; @@ -42,7 +42,7 @@ pub async fn register_account( }; let provider = Provider::OpenId(OpenId { - provider: provider.clone(), + provider: OpenIdAuthProvider::from(provider), data: provider_data, }); diff --git a/src/console/src/impls.rs b/src/console/src/impls.rs index 3697eab5de..1ab673dfcb 100644 --- a/src/console/src/impls.rs +++ b/src/console/src/impls.rs @@ -1,11 +1,14 @@ use crate::memory::manager::init_stable_state; use crate::types::ledger::{Fee, IcpPayment, IcrcPayment, IcrcPaymentKey}; -use crate::types::state::{Account, HeapState, Segment, SegmentKey, State, StorableSegmentKind}; +use crate::types::state::{ + Account, HeapState, OpenIdAuthProvider, Segment, SegmentKey, State, StorableSegmentKind, +}; use candid::Principal; use ic_cdk::api::time; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; +use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_shared::ledger::types::cycles::CyclesTokens; use junobuild_shared::memory::serializers::{ deserialize_from_bytes, serialize_into_bytes, serialize_to_bytes, @@ -190,3 +193,12 @@ impl Fee { } } } + +impl From<&OpenIdProvider> for OpenIdAuthProvider { + fn from(provider: &OpenIdProvider) -> Self { + match provider { + OpenIdProvider::Google => OpenIdAuthProvider::Google, + OpenIdProvider::GitHubProxy => OpenIdAuthProvider::GitHub, + } + } +} diff --git a/src/console/src/types.rs b/src/console/src/types.rs index 7af84477c8..5ecc7ae98a 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -4,7 +4,6 @@ pub mod state { use candid::CandidType; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::StableBTreeMap; - use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::state::types::state::AuthenticationHeapState; use junobuild_cdn::proposals::{ProposalsStable, SegmentDeploymentVersion}; use junobuild_cdn::storage::{ProposalAssetsStable, ProposalContentChunksStable}; @@ -83,10 +82,16 @@ pub mod state { #[derive(CandidType, Serialize, Deserialize, Clone)] pub struct OpenId { - pub provider: OpenIdProvider, + pub provider: OpenIdAuthProvider, pub data: OpenIdData, } + #[derive(CandidType, Serialize, Deserialize, Clone)] + pub enum OpenIdAuthProvider { + Google, + GitHub, + } + #[derive(CandidType, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct OpenIdData { pub email: Option, diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 24002271f9..cb9378d775 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -39,14 +39,14 @@ impl OpenIdProvider { Self::Google => "https://www.googleapis.com/oauth2/v3/certs", // Swap for local development with the Juno API: // http://host.docker.internal:3000/v1/auth/certs - Self::GitHub => "https://api.juno.build/v1/auth/certs", + Self::GitHubProxy => "https://api.juno.build/v1/auth/certs", } } pub fn issuers(&self) -> &[&'static str] { match self { OpenIdProvider::Google => &["https://accounts.google.com", "accounts.google.com"], - OpenIdProvider::GitHub => &["https://api.juno.build/auth/github"], + OpenIdProvider::GitHubProxy => &["https://api.juno.build/auth/github"], } } } @@ -93,7 +93,7 @@ impl Display for OpenIdProvider { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { OpenIdProvider::Google => write!(f, "Google"), - OpenIdProvider::GitHub => write!(f, "GitHub"), + OpenIdProvider::GitHubProxy => write!(f, "GitHub"), } } } diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index 088482f394..37d3fdc55a 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -7,13 +7,17 @@ fn pick_key<'a>(kid: &str, jwks: &'a [Jwk]) -> Option<&'a Jwk> { jwks.iter().find(|j| j.kid.as_deref() == Some(kid)) } -pub fn verify_openid_jwt( +pub fn verify_openid_jwt( jwt: &str, issuers: &[&str], - client_id: &str, jwks: &[Jwk], - expected_nonce: &str, -) -> Result, JwtVerifyError> { + assert_audience: Aud, + assert_no_replay: Replay, +) -> Result, JwtVerifyError> +where + Aud: FnOnce(&Claims) -> Result<(), JwtVerifyError>, + Replay: FnOnce(&Claims) -> Result<(), JwtVerifyError>, +{ // 1) Read header to get `kid` let header = decode_jwt_header(jwt).map_err(JwtVerifyError::from)?; @@ -55,16 +59,13 @@ pub fn verify_openid_jwt( let token = decode::(jwt, &key, &val).map_err(|e| JwtVerifyError::BadSig(e.to_string()))?; - // 6) Manual checks audience let c = &token.claims; - if c.aud != client_id { - return Err(JwtVerifyError::BadClaim("aud".to_string())); - } - // 7) Assert it is the expected nonce - if c.nonce.as_deref() != Some(expected_nonce) { - return Err(JwtVerifyError::BadClaim("nonce".to_string())); - } + // 6) Manual checks audience + assert_audience(c)?; + + // 7) Prevent replace attack + assert_no_replay(c)?; // 8) Assert expiration let now_ns = now_ns(); @@ -179,6 +180,20 @@ mod verify_tests { } } + fn assert_audience(claims: &Claims) -> Result<(), JwtVerifyError> { + if claims.aud != AUD_OK { + return Err(JwtVerifyError::BadClaim("aud".to_string())); + } + Ok(()) + } + + fn assert_nonce(claims: &Claims) -> Result<(), JwtVerifyError> { + if claims.nonce.as_deref() != Some(NONCE_OK) { + return Err(JwtVerifyError::BadClaim("nonce".to_string())); + } + Ok(()) + } + #[test] fn verifies_ok() { let now = now_secs(); @@ -197,9 +212,9 @@ mod verify_tests { let out = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .expect("should verify"); @@ -226,9 +241,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::MissingKid)); @@ -252,9 +267,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::NoKeyForKid)); @@ -279,9 +294,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); @@ -305,9 +320,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "typ")); @@ -331,9 +346,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "aud")); @@ -357,9 +372,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "nonce")); @@ -384,9 +399,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "iat_future")); @@ -411,9 +426,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "iat_expired")); @@ -439,9 +454,9 @@ mod verify_tests { let err = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); @@ -478,8 +493,14 @@ mod verify_tests { }), }; - let err = - verify_openid_jwt(&token, &[ISS_GOOGLE], AUD_OK, &[bad_jwk], NONCE_OK).unwrap_err(); + let err = verify_openid_jwt( + &token, + &[ISS_GOOGLE], + &[bad_jwk], + |claims| assert_audience(claims), + |claims| assert_nonce(claims), + ) + .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); } @@ -509,9 +530,9 @@ mod verify_tests { let out = verify_openid_jwt( &token, &[ISS_GOOGLE], - AUD_OK, &[jwk_with_kid(KID_OK)], - NONCE_OK, + |claims| assert_audience(claims), + |claims| assert_nonce(claims), ) .expect("should verify"); diff --git a/src/libs/auth/src/openid/types.rs b/src/libs/auth/src/openid/types.rs index b5b51b003b..0e944d8365 100644 --- a/src/libs/auth/src/openid/types.rs +++ b/src/libs/auth/src/openid/types.rs @@ -44,7 +44,7 @@ pub mod provider { )] pub enum OpenIdProvider { Google, - GitHub, + GitHubProxy, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/libs/auth/src/openid/verify/mod.rs b/src/libs/auth/src/openid/verify/mod.rs new file mode 100644 index 0000000000..d325f64617 --- /dev/null +++ b/src/libs/auth/src/openid/verify/mod.rs @@ -0,0 +1,5 @@ +mod user; +mod workload; + +pub use user::*; +pub use workload::*; \ No newline at end of file diff --git a/src/libs/auth/src/openid/verify.rs b/src/libs/auth/src/openid/verify/user.rs similarity index 61% rename from src/libs/auth/src/openid/verify.rs rename to src/libs/auth/src/openid/verify/user.rs index ca3b90cc2b..558af934f0 100644 --- a/src/libs/auth/src/openid/verify.rs +++ b/src/libs/auth/src/openid/verify/user.rs @@ -1,5 +1,7 @@ use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; +use crate::openid::jwt::types::errors::JwtVerifyError; +use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::errors::VerifyOpenidCredentialsError; use crate::openid::types::interface::OpenIdCredential; @@ -9,15 +11,15 @@ use crate::state::types::config::{OpenIdProviderClientId, OpenIdProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdCredentialsResult = +type VerifyUserOpenIdCredentialsResult = Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; -pub async fn verify_openid_credentials_with_jwks_renewal( +pub async fn verify_user_openid_credentials_with_jwks_renewal( jwt: &str, salt: &Salt, providers: &OpenIdProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { +) -> VerifyUserOpenIdCredentialsResult { let (provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -28,12 +30,12 @@ pub async fn verify_openid_credentials_with_jwks_renewal( verify_openid_credentials(jwt, &jwks, &provider, &config.client_id, salt) } -pub fn verify_openid_credentials_with_cached_jwks( +pub fn verify_user_openid_credentials_with_cached_jwks( jwt: &str, salt: &Salt, providers: &OpenIdProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { +) -> VerifyUserOpenIdCredentialsResult { let (provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -48,11 +50,33 @@ fn verify_openid_credentials( provider: &OpenIdProvider, client_id: &OpenIdProviderClientId, salt: &Salt, -) -> VerifyOpenIdCredentialsResult { - let nonce = build_nonce(salt); +) -> VerifyUserOpenIdCredentialsResult { + let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { + if claims.aud != client_id.as_str() { + return Err(JwtVerifyError::BadClaim("aud".to_string())); + } - let token = verify_openid_jwt(jwt, provider.issuers(), client_id, &jwks.keys, &nonce) - .map_err(VerifyOpenidCredentialsError::JwtVerify)?; + Ok(()) + }; + + let assert_no_replay = |claims: &Claims| -> Result<(), JwtVerifyError> { + let nonce = build_nonce(salt); + + if claims.nonce.as_deref() != Some(nonce.as_str()) { + return Err(JwtVerifyError::BadClaim("nonce".to_string())); + } + + Ok(()) + }; + + let token = verify_openid_jwt( + jwt, + provider.issuers(), + &jwks.keys, + &assert_audience, + &assert_no_replay, + ) + .map_err(VerifyOpenidCredentialsError::JwtVerify)?; let credential = OpenIdCredential::from(token); diff --git a/src/libs/auth/src/openid/verify/workload.rs b/src/libs/auth/src/openid/verify/workload.rs new file mode 100644 index 0000000000..4733b4ee65 --- /dev/null +++ b/src/libs/auth/src/openid/verify/workload.rs @@ -0,0 +1,69 @@ +use crate::openid::jwkset::get_or_refresh_jwks; +use crate::openid::jwt::types::cert::Jwks; +use crate::openid::jwt::types::errors::JwtVerifyError; +use crate::openid::jwt::types::token::Claims; +use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; +use crate::openid::types::errors::VerifyOpenidCredentialsError; +use crate::openid::types::interface::OpenIdCredential; +use crate::openid::types::provider::OpenIdProvider; +use crate::state::types::config::OpenIdProviders; +use crate::strategies::AuthHeapStrategy; + +type VerifyWorkloadOpenIdCredentialsResult = + Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; + +pub async fn verify_workload_openid_credentials_with_jwks_renewal( + jwt: &str, + providers: &OpenIdProviders, + auth_heap: &impl AuthHeapStrategy, +) -> VerifyWorkloadOpenIdCredentialsResult { + let (provider, config) = unsafe_find_jwt_provider(providers, jwt) + .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; + + let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) + .await + .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; + + verify_openid_credentials(jwt, &jwks, &provider) +} + +fn verify_openid_credentials( + jwt: &str, + jwks: &Jwks, + provider: &OpenIdProvider, +) -> VerifyWorkloadOpenIdCredentialsResult { + let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { + // if claims.aud != client_id.as_str() { + // return Err(JwtVerifyError::BadClaim("aud".to_string())); + // } + + // TODO: asser github username and repo + + Ok(()) + }; + + let assert_no_replay = |claims: &Claims| -> Result<(), JwtVerifyError> { + // let nonce = build_nonce(salt); + // + // if claims.nonce.as_deref() != Some(nonce.as_str()) { + // return Err(JwtVerifyError::BadClaim("nonce".to_string())); + // } + + // TODO: assert jti + + Ok(()) + }; + + let token = verify_openid_jwt( + jwt, + provider.issuers(), + &jwks.keys, + &assert_audience, + &assert_no_replay, + ) + .map_err(VerifyOpenidCredentialsError::JwtVerify)?; + + let credential = OpenIdCredential::from(token); + + Ok((credential, provider.clone())) +} diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index d11d23cdfb..556ef1f9d4 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -1,4 +1,6 @@ +use crate::controllers::openid_authenticate_controller; use crate::controllers::store::{delete_controllers, set_controllers as set_controllers_store}; +use crate::controllers::types::AuthenticateControllerArgs; use crate::{get_admin_controllers, get_controllers}; use ic_cdk::trap; use junobuild_shared::constants::shared::MAX_NUMBER_OF_SATELLITE_CONTROLLERS; @@ -49,3 +51,11 @@ pub fn del_controllers( pub fn list_controllers() -> Controllers { get_controllers() } + +pub async fn authenticate_controller(args: AuthenticateControllerArgs) { + match args { + AuthenticateControllerArgs::OpenId(args) => { + openid_authenticate_controller(&args).await.unwrap_or_trap() + } + } +} diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index 78a9fc0a96..699a9b7b22 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -16,7 +16,7 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::verify_openid_credentials_with_jwks_renewal( + let (credential, provider) = match openid::verify_user_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -40,7 +40,7 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::verify_openid_credentials_with_cached_jwks( + let (credential, provider) = match openid::verify_user_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs new file mode 100644 index 0000000000..6053af8e62 --- /dev/null +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -0,0 +1,24 @@ +use junobuild_auth::state::get_providers; +use crate::auth::strategy_impls::AuthHeap; +use crate::controllers::types::OpenIdAuthenticateControllerArgs; +use junobuild_auth::{delegation, openid}; +use junobuild_auth::delegation::types::PrepareDelegationError; + +pub async fn openid_authenticate_controller( + args: &OpenIdAuthenticateControllerArgs, +) -> Result<(), String> { + let providers = get_providers(&AuthHeap)?; + + let (credential, provider) = match openid::verify_workload_openid_credentials_with_jwks_renewal( + &args.jwt, &providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareDelegationError::from(err)), + }; + + + + Ok(()) +} diff --git a/src/libs/satellite/src/controllers/mod.rs b/src/libs/satellite/src/controllers/mod.rs index 55c88cbf3d..99a8201cdf 100644 --- a/src/libs/satellite/src/controllers/mod.rs +++ b/src/libs/satellite/src/controllers/mod.rs @@ -1 +1,5 @@ +mod authenticate; pub mod store; +pub mod types; + +pub use authenticate::*; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs new file mode 100644 index 0000000000..7ad2f47de9 --- /dev/null +++ b/src/libs/satellite/src/controllers/types.rs @@ -0,0 +1,22 @@ +use candid::{CandidType, Deserialize}; +use junobuild_shared::types::state::Metadata; +use serde::Serialize; + +#[derive(CandidType, Serialize, Deserialize)] +pub enum AuthenticateControllerArgs { + OpenId(OpenIdAuthenticateControllerArgs), +} + +#[derive(CandidType, Serialize, Deserialize)] +pub struct OpenIdAuthenticateControllerArgs { + pub jwt: String, + pub metadata: Metadata, + pub max_time_to_live: Option, + pub scope: GrantableScope, +} + +#[derive(CandidType, Serialize, Deserialize, Clone)] +pub enum GrantableScope { + Write, + Submit, +} diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index c3bf7e3440..d300c24a23 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -164,7 +164,7 @@ impl From<&OpenIdProvider> for AuthProvider { fn from(provider: &OpenIdProvider) -> Self { match provider { OpenIdProvider::Google => AuthProvider::Google, - OpenIdProvider::GitHub => AuthProvider::GitHub, + OpenIdProvider::GitHubProxy => AuthProvider::GitHub, } } } @@ -344,7 +344,7 @@ mod tests { AuthProvider::Google )); assert!(matches!( - AuthProvider::from(&OpenIdProvider::GitHub), + AuthProvider::from(&OpenIdProvider::GitHubProxy), AuthProvider::GitHub )); } diff --git a/src/observatory/src/openid/scheduler.rs b/src/observatory/src/openid/scheduler.rs index be0a668c3a..3ffc530e82 100644 --- a/src/observatory/src/openid/scheduler.rs +++ b/src/observatory/src/openid/scheduler.rs @@ -9,7 +9,7 @@ use std::time::Duration; pub fn defer_restart_monitoring() { // Early spare one timer if no scheduler is enabled. - let enabled_count = [OpenIdProvider::Google, OpenIdProvider::GitHub] + let enabled_count = [OpenIdProvider::Google, OpenIdProvider::GitHubProxy] .into_iter() .filter(|provider| is_scheduler_enabled(provider)) .count(); @@ -24,7 +24,7 @@ pub fn defer_restart_monitoring() { } async fn restart_monitoring() { - for provider in [OpenIdProvider::Google, OpenIdProvider::GitHub] { + for provider in [OpenIdProvider::Google, OpenIdProvider::GitHubProxy] { schedule_certificate_update(provider, None); } } From 071fd43d2e74b332b2f8845fa70772680b4ccfbf Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 25 Jan 2026 18:37:09 +0100 Subject: [PATCH 02/64] refactor: split modules user and workload for jwt verification --- src/console/src/accounts/impls.rs | 2 +- src/console/src/auth/delegation.rs | 6 ++-- src/console/src/auth/register.rs | 2 +- src/libs/auth/src/delegation/get.rs | 2 +- src/libs/auth/src/delegation/impls.rs | 2 +- src/libs/auth/src/delegation/prepare.rs | 2 +- src/libs/auth/src/delegation/utils/seed.rs | 4 +-- src/libs/auth/src/openid/impls.rs | 28 --------------- src/libs/auth/src/openid/mod.rs | 5 ++- src/libs/auth/src/openid/types.rs | 35 ------------------- src/libs/auth/src/openid/user/impls.rs | 28 +++++++++++++++ src/libs/auth/src/openid/user/mod.rs | 5 +++ src/libs/auth/src/openid/user/types.rs | 34 ++++++++++++++++++ .../openid/{verify/user.rs => user/verify.rs} | 16 ++++----- src/libs/auth/src/openid/verify/mod.rs | 5 --- src/libs/auth/src/openid/workload/mod.rs | 4 +++ src/libs/auth/src/openid/workload/types.rs | 13 +++++++ .../workload.rs => workload/verify.rs} | 26 ++++++-------- src/libs/satellite/src/auth/delegation.rs | 6 ++-- src/libs/satellite/src/auth/register.rs | 2 +- .../satellite/src/controllers/authenticate.rs | 24 +++++++------ src/libs/satellite/src/user/core/impls.rs | 2 +- 22 files changed, 133 insertions(+), 120 deletions(-) create mode 100644 src/libs/auth/src/openid/user/impls.rs create mode 100644 src/libs/auth/src/openid/user/mod.rs create mode 100644 src/libs/auth/src/openid/user/types.rs rename src/libs/auth/src/openid/{verify/user.rs => user/verify.rs} (85%) delete mode 100644 src/libs/auth/src/openid/verify/mod.rs create mode 100644 src/libs/auth/src/openid/workload/mod.rs create mode 100644 src/libs/auth/src/openid/workload/types.rs rename src/libs/auth/src/openid/{verify/workload.rs => workload/verify.rs} (63%) diff --git a/src/console/src/accounts/impls.rs b/src/console/src/accounts/impls.rs index 9a5e527a4c..b006f6da2e 100644 --- a/src/console/src/accounts/impls.rs +++ b/src/console/src/accounts/impls.rs @@ -1,7 +1,7 @@ use crate::constants::E8S_PER_ICP; use crate::types::state::{Account, OpenIdData, Provider}; use ic_cdk::api::time; -use junobuild_auth::openid::types::interface::OpenIdCredential; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_auth::profile::types::OpenIdProfile; use junobuild_shared::types::state::{MissionControlId, UserId}; diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index 699a9b7b22..c9ef797e3c 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -4,8 +4,8 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_auth::state::types::config::OpenIdProviders; use junobuild_auth::{delegation, openid}; @@ -16,7 +16,7 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::verify_user_openid_credentials_with_jwks_renewal( + let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -40,7 +40,7 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::verify_user_openid_credentials_with_cached_jwks( + let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 730b455dc7..dcb55f6df9 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -3,8 +3,8 @@ use crate::types::state::{Account, OpenIdData, Provider}; use crate::types::state::{OpenId, OpenIdAuthProvider}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; pub async fn register_account( public_key: &UserKey, diff --git a/src/libs/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 29801c37e7..489bc8168e 100644 --- a/src/libs/auth/src/delegation/get.rs +++ b/src/libs/auth/src/delegation/get.rs @@ -4,8 +4,8 @@ use crate::delegation::types::{ use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::openid::types::provider::OpenIdProvider; +use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::state::get_salt; use crate::state::services::read_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/impls.rs b/src/libs/auth/src/delegation/impls.rs index 699cd97fb3..564bee5d9a 100644 --- a/src/libs/auth/src/delegation/impls.rs +++ b/src/libs/auth/src/delegation/impls.rs @@ -1,5 +1,5 @@ use crate::delegation::types::{GetDelegationError, PrepareDelegationError}; -use crate::openid::types::errors::VerifyOpenidCredentialsError; +use crate::openid::user::types::errors::VerifyOpenidCredentialsError; impl From for GetDelegationError { fn from(e: VerifyOpenidCredentialsError) -> Self { diff --git a/src/libs/auth/src/delegation/prepare.rs b/src/libs/auth/src/delegation/prepare.rs index 9a61d458bf..408d227256 100644 --- a/src/libs/auth/src/delegation/prepare.rs +++ b/src/libs/auth/src/delegation/prepare.rs @@ -6,8 +6,8 @@ use crate::delegation::utils::duration::build_expiration; use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::openid::types::provider::OpenIdProvider; +use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::state::get_salt; use crate::state::services::mutate_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/utils/seed.rs b/src/libs/auth/src/delegation/utils/seed.rs index 7ef1f44162..6efd35777b 100644 --- a/src/libs/auth/src/delegation/utils/seed.rs +++ b/src/libs/auth/src/delegation/utils/seed.rs @@ -1,4 +1,4 @@ -use crate::openid::types::interface::OpenIdCredentialKey; +use crate::openid::user::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; @@ -30,7 +30,7 @@ fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { #[cfg(test)] mod tests { use super::calculate_seed; - use crate::openid::types::interface::OpenIdCredentialKey; + use crate::openid::user::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index cb9378d775..064dd6e21e 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,38 +1,10 @@ use crate::openid::jwt::types::cert::Jwks; -use crate::openid::jwt::types::token::Claims; -use crate::openid::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::openid::types::provider::{OpenIdCertificate, OpenIdProvider}; use ic_cdk::api::time; -use jsonwebtoken::TokenData; use junobuild_shared::data::version::next_version; use junobuild_shared::types::state::{Version, Versioned}; use std::fmt::{Display, Formatter, Result as FmtResult}; -impl From> for OpenIdCredential { - fn from(token: TokenData) -> Self { - Self { - sub: token.claims.sub, - iss: token.claims.iss, - email: token.claims.email, - name: token.claims.name, - given_name: token.claims.given_name, - family_name: token.claims.family_name, - preferred_username: token.claims.preferred_username, - picture: token.claims.picture, - locale: token.claims.locale, - } - } -} - -impl<'a> From<&'a OpenIdCredential> for OpenIdCredentialKey<'a> { - fn from(credential: &'a OpenIdCredential) -> Self { - Self { - sub: &credential.sub, - iss: &credential.iss, - } - } -} - impl OpenIdProvider { pub fn jwks_url(&self) -> &'static str { match self { diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index bce9aa14e3..c31a2a0561 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -2,7 +2,6 @@ mod impls; pub mod jwkset; pub mod jwt; pub mod types; +pub mod user; mod utils; -mod verify; - -pub use verify::*; +pub mod workload; diff --git a/src/libs/auth/src/openid/types.rs b/src/libs/auth/src/openid/types.rs index 0e944d8365..b4cce5e646 100644 --- a/src/libs/auth/src/openid/types.rs +++ b/src/libs/auth/src/openid/types.rs @@ -1,38 +1,3 @@ -pub mod interface { - pub struct OpenIdCredentialKey<'a> { - pub iss: &'a String, - pub sub: &'a String, - } - - pub struct OpenIdCredential { - pub iss: String, - pub sub: String, - - pub email: Option, - pub name: Option, - pub given_name: Option, - pub family_name: Option, - pub preferred_username: Option, - pub picture: Option, - pub locale: Option, - } -} - -pub(crate) mod errors { - use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; - use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; - use candid::{CandidType, Deserialize}; - use serde::Serialize; - - #[derive(CandidType, Serialize, Deserialize, Debug)] - pub enum VerifyOpenidCredentialsError { - GetOrFetchJwks(GetOrRefreshJwksError), - GetCachedJwks, - JwtFindProvider(JwtFindProviderError), - JwtVerify(JwtVerifyError), - } -} - pub mod provider { use crate::openid::jwt::types::cert::Jwks; use candid::{CandidType, Deserialize}; diff --git a/src/libs/auth/src/openid/user/impls.rs b/src/libs/auth/src/openid/user/impls.rs new file mode 100644 index 0000000000..0477724ab3 --- /dev/null +++ b/src/libs/auth/src/openid/user/impls.rs @@ -0,0 +1,28 @@ +use crate::openid::jwt::types::token::Claims; +use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use jsonwebtoken::TokenData; + +impl From> for OpenIdCredential { + fn from(token: TokenData) -> Self { + Self { + sub: token.claims.sub, + iss: token.claims.iss, + email: token.claims.email, + name: token.claims.name, + given_name: token.claims.given_name, + family_name: token.claims.family_name, + preferred_username: token.claims.preferred_username, + picture: token.claims.picture, + locale: token.claims.locale, + } + } +} + +impl<'a> From<&'a OpenIdCredential> for OpenIdCredentialKey<'a> { + fn from(credential: &'a OpenIdCredential) -> Self { + Self { + sub: &credential.sub, + iss: &credential.iss, + } + } +} diff --git a/src/libs/auth/src/openid/user/mod.rs b/src/libs/auth/src/openid/user/mod.rs new file mode 100644 index 0000000000..35c6668fcf --- /dev/null +++ b/src/libs/auth/src/openid/user/mod.rs @@ -0,0 +1,5 @@ +mod impls; +pub mod types; +mod verify; + +pub use verify::*; diff --git a/src/libs/auth/src/openid/user/types.rs b/src/libs/auth/src/openid/user/types.rs new file mode 100644 index 0000000000..af0991b32c --- /dev/null +++ b/src/libs/auth/src/openid/user/types.rs @@ -0,0 +1,34 @@ +pub mod interface { + pub struct OpenIdCredentialKey<'a> { + pub iss: &'a String, + pub sub: &'a String, + } + + pub struct OpenIdCredential { + pub iss: String, + pub sub: String, + + pub email: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub picture: Option, + pub locale: Option, + } +} + +pub(crate) mod errors { + use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; + use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; + use candid::{CandidType, Deserialize}; + use serde::Serialize; + + #[derive(CandidType, Serialize, Deserialize, Debug)] + pub enum VerifyOpenidCredentialsError { + GetOrFetchJwks(GetOrRefreshJwksError), + GetCachedJwks, + JwtFindProvider(JwtFindProviderError), + JwtVerify(JwtVerifyError), + } +} diff --git a/src/libs/auth/src/openid/verify/user.rs b/src/libs/auth/src/openid/user/verify.rs similarity index 85% rename from src/libs/auth/src/openid/verify/user.rs rename to src/libs/auth/src/openid/user/verify.rs index 558af934f0..e3bfe85233 100644 --- a/src/libs/auth/src/openid/verify/user.rs +++ b/src/libs/auth/src/openid/user/verify.rs @@ -3,23 +3,23 @@ use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; -use crate::openid::types::errors::VerifyOpenidCredentialsError; -use crate::openid::types::interface::OpenIdCredential; use crate::openid::types::provider::OpenIdProvider; +use crate::openid::user::types::errors::VerifyOpenidCredentialsError; +use crate::openid::user::types::interface::OpenIdCredential; use crate::openid::utils::build_nonce; use crate::state::types::config::{OpenIdProviderClientId, OpenIdProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; -type VerifyUserOpenIdCredentialsResult = +type VerifyOpenIdCredentialsResult = Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; -pub async fn verify_user_openid_credentials_with_jwks_renewal( +pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, salt: &Salt, providers: &OpenIdProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyUserOpenIdCredentialsResult { +) -> VerifyOpenIdCredentialsResult { let (provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -30,12 +30,12 @@ pub async fn verify_user_openid_credentials_with_jwks_renewal( verify_openid_credentials(jwt, &jwks, &provider, &config.client_id, salt) } -pub fn verify_user_openid_credentials_with_cached_jwks( +pub fn verify_openid_credentials_with_cached_jwks( jwt: &str, salt: &Salt, providers: &OpenIdProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyUserOpenIdCredentialsResult { +) -> VerifyOpenIdCredentialsResult { let (provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -50,7 +50,7 @@ fn verify_openid_credentials( provider: &OpenIdProvider, client_id: &OpenIdProviderClientId, salt: &Salt, -) -> VerifyUserOpenIdCredentialsResult { +) -> VerifyOpenIdCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { if claims.aud != client_id.as_str() { return Err(JwtVerifyError::BadClaim("aud".to_string())); diff --git a/src/libs/auth/src/openid/verify/mod.rs b/src/libs/auth/src/openid/verify/mod.rs deleted file mode 100644 index d325f64617..0000000000 --- a/src/libs/auth/src/openid/verify/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod user; -mod workload; - -pub use user::*; -pub use workload::*; \ No newline at end of file diff --git a/src/libs/auth/src/openid/workload/mod.rs b/src/libs/auth/src/openid/workload/mod.rs new file mode 100644 index 0000000000..0e0c4c0cde --- /dev/null +++ b/src/libs/auth/src/openid/workload/mod.rs @@ -0,0 +1,4 @@ +mod types; +mod verify; + +pub use verify::*; diff --git a/src/libs/auth/src/openid/workload/types.rs b/src/libs/auth/src/openid/workload/types.rs new file mode 100644 index 0000000000..1f8d6b2f34 --- /dev/null +++ b/src/libs/auth/src/openid/workload/types.rs @@ -0,0 +1,13 @@ +pub(crate) mod errors { + use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; + use crate::openid::jwt::types::errors::JwtVerifyError; + use candid::{CandidType, Deserialize}; + use serde::Serialize; + + #[derive(CandidType, Serialize, Deserialize, Debug)] + pub enum VerifyOpenidWorkloadCredentialsError { + GetOrFetchJwks(GetOrRefreshJwksError), + GetCachedJwks, + JwtVerify(JwtVerifyError), + } +} diff --git a/src/libs/auth/src/openid/verify/workload.rs b/src/libs/auth/src/openid/workload/verify.rs similarity index 63% rename from src/libs/auth/src/openid/verify/workload.rs rename to src/libs/auth/src/openid/workload/verify.rs index 4733b4ee65..9727dc34ea 100644 --- a/src/libs/auth/src/openid/verify/workload.rs +++ b/src/libs/auth/src/openid/workload/verify.rs @@ -2,27 +2,23 @@ use crate::openid::jwkset::get_or_refresh_jwks; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; -use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; -use crate::openid::types::errors::VerifyOpenidCredentialsError; -use crate::openid::types::interface::OpenIdCredential; +use crate::openid::jwt::verify_openid_jwt; use crate::openid::types::provider::OpenIdProvider; -use crate::state::types::config::OpenIdProviders; +use crate::openid::user::types::interface::OpenIdCredential; +use crate::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; use crate::strategies::AuthHeapStrategy; -type VerifyWorkloadOpenIdCredentialsResult = - Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; +type VerifyOpenIdWorkloadCredentialsResult = + Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidWorkloadCredentialsError>; -pub async fn verify_workload_openid_credentials_with_jwks_renewal( +pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, - providers: &OpenIdProviders, + provider: &OpenIdProvider, auth_heap: &impl AuthHeapStrategy, -) -> VerifyWorkloadOpenIdCredentialsResult { - let (provider, config) = unsafe_find_jwt_provider(providers, jwt) - .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; - +) -> VerifyOpenIdWorkloadCredentialsResult { let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) .await - .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; + .map_err(VerifyOpenidWorkloadCredentialsError::GetOrFetchJwks)?; verify_openid_credentials(jwt, &jwks, &provider) } @@ -31,7 +27,7 @@ fn verify_openid_credentials( jwt: &str, jwks: &Jwks, provider: &OpenIdProvider, -) -> VerifyWorkloadOpenIdCredentialsResult { +) -> VerifyOpenIdWorkloadCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { // if claims.aud != client_id.as_str() { // return Err(JwtVerifyError::BadClaim("aud".to_string())); @@ -61,7 +57,7 @@ fn verify_openid_credentials( &assert_audience, &assert_no_replay, ) - .map_err(VerifyOpenidCredentialsError::JwtVerify)?; + .map_err(VerifyOpenidWorkloadCredentialsError::JwtVerify)?; let credential = OpenIdCredential::from(token); diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index 699a9b7b22..c9ef797e3c 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -4,8 +4,8 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_auth::state::types::config::OpenIdProviders; use junobuild_auth::{delegation, openid}; @@ -16,7 +16,7 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::verify_user_openid_credentials_with_jwks_renewal( + let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -40,7 +40,7 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::verify_user_openid_credentials_with_cached_jwks( + let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index 8e01852726..c9c0c5f023 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -7,8 +7,8 @@ use crate::user::core::types::state::{OpenIdData, ProviderData, UserData}; use crate::Doc; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_collections::constants::db::COLLECTION_USER_KEY; use junobuild_collections::msg::msg_db_collection_not_found; use junobuild_shared::ic::api::id; diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index 6053af8e62..f0fc7253ab 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -1,24 +1,26 @@ -use junobuild_auth::state::get_providers; use crate::auth::strategy_impls::AuthHeap; use crate::controllers::types::OpenIdAuthenticateControllerArgs; -use junobuild_auth::{delegation, openid}; use junobuild_auth::delegation::types::PrepareDelegationError; +use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::state::get_providers; +use junobuild_auth::{delegation, openid}; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, ) -> Result<(), String> { let providers = get_providers(&AuthHeap)?; - let (credential, provider) = match openid::verify_workload_openid_credentials_with_jwks_renewal( - &args.jwt, &providers, &AuthHeap, - ) + let (credential, provider) = + match openid::workload::verify_openid_credentials_with_jwks_renewal( + &args.jwt, + &OpenIdProvider::GitHubProxy, + &AuthHeap, + ) .await - { - Ok(value) => value, - Err(err) => return Err(PrepareDelegationError::from(err)), - }; - - + { + Ok(value) => value, + Err(err) => return Err(PrepareDelegationError::from(err)), + }; Ok(()) } diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index d300c24a23..f310229b40 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -9,8 +9,8 @@ use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; use crate::{Doc, SetDoc}; -use junobuild_auth::openid::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; use junobuild_utils::encode_doc_data; From 2baab4ae3aebfea3ee9ce7005cfb0e44d6ed0bb0 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sun, 25 Jan 2026 19:56:26 +0100 Subject: [PATCH 03/64] feat: bubble error --- src/libs/auth/src/openid/workload/mod.rs | 2 +- src/libs/auth/src/openid/workload/types.rs | 2 +- src/libs/auth/src/openid/workload/verify.rs | 10 ++---- .../satellite/src/controllers/authenticate.rs | 34 +++++++++---------- src/libs/satellite/src/controllers/types.rs | 7 ++++ 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/libs/auth/src/openid/workload/mod.rs b/src/libs/auth/src/openid/workload/mod.rs index 0e0c4c0cde..d9a483abcc 100644 --- a/src/libs/auth/src/openid/workload/mod.rs +++ b/src/libs/auth/src/openid/workload/mod.rs @@ -1,4 +1,4 @@ -mod types; +pub mod types; mod verify; pub use verify::*; diff --git a/src/libs/auth/src/openid/workload/types.rs b/src/libs/auth/src/openid/workload/types.rs index 1f8d6b2f34..1f7306ef36 100644 --- a/src/libs/auth/src/openid/workload/types.rs +++ b/src/libs/auth/src/openid/workload/types.rs @@ -1,4 +1,4 @@ -pub(crate) mod errors { +pub mod errors { use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; use crate::openid::jwt::types::errors::JwtVerifyError; use candid::{CandidType, Deserialize}; diff --git a/src/libs/auth/src/openid/workload/verify.rs b/src/libs/auth/src/openid/workload/verify.rs index 9727dc34ea..64eaeb86dc 100644 --- a/src/libs/auth/src/openid/workload/verify.rs +++ b/src/libs/auth/src/openid/workload/verify.rs @@ -4,12 +4,10 @@ use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::verify_openid_jwt; use crate::openid::types::provider::OpenIdProvider; -use crate::openid::user::types::interface::OpenIdCredential; use crate::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdWorkloadCredentialsResult = - Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidWorkloadCredentialsError>; +type VerifyOpenIdWorkloadCredentialsResult = Result<(), VerifyOpenidWorkloadCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, @@ -50,7 +48,7 @@ fn verify_openid_credentials( Ok(()) }; - let token = verify_openid_jwt( + verify_openid_jwt( jwt, provider.issuers(), &jwks.keys, @@ -59,7 +57,5 @@ fn verify_openid_credentials( ) .map_err(VerifyOpenidWorkloadCredentialsError::JwtVerify)?; - let credential = OpenIdCredential::from(token); - - Ok((credential, provider.clone())) + Ok(()) } diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index f0fc7253ab..b8111c515f 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -1,26 +1,24 @@ use crate::auth::strategy_impls::AuthHeap; -use crate::controllers::types::OpenIdAuthenticateControllerArgs; -use junobuild_auth::delegation::types::PrepareDelegationError; +use crate::controllers::types::{AuthenticateControllerError, OpenIdAuthenticateControllerArgs}; +use junobuild_auth::openid; use junobuild_auth::openid::types::provider::OpenIdProvider; -use junobuild_auth::state::get_providers; -use junobuild_auth::{delegation, openid}; + +pub type OpenIdAuthenticateControllerResult = Result<(), AuthenticateControllerError>; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, -) -> Result<(), String> { - let providers = get_providers(&AuthHeap)?; - - let (credential, provider) = - match openid::workload::verify_openid_credentials_with_jwks_renewal( - &args.jwt, - &OpenIdProvider::GitHubProxy, - &AuthHeap, - ) - .await - { - Ok(value) => value, - Err(err) => return Err(PrepareDelegationError::from(err)), - }; +) -> OpenIdAuthenticateControllerResult { + match openid::workload::verify_openid_credentials_with_jwks_renewal( + &args.jwt, + // TODO: GitHubActions + &OpenIdProvider::GitHubProxy, + &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), + }; Ok(()) } diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index 7ad2f47de9..fbc5671a3b 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,4 +1,6 @@ use candid::{CandidType, Deserialize}; +use junobuild_auth::delegation::types::PrepareDelegationError; +use junobuild_auth::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; use junobuild_shared::types::state::Metadata; use serde::Serialize; @@ -20,3 +22,8 @@ pub enum GrantableScope { Write, Submit, } + +#[derive(CandidType, Serialize, Deserialize)] +pub enum AuthenticateControllerError { + VerifyOpenIdCredentials(VerifyOpenidWorkloadCredentialsError), +} From 8e7b6efecbec878de73afe78034d0dd52512159f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 09:02:46 +0100 Subject: [PATCH 04/64] feat: actions --- src/console/src/impls.rs | 14 +++++++---- src/libs/auth/src/openid/impls.rs | 5 +++- src/libs/auth/src/openid/types.rs | 1 + src/libs/satellite/src/api/controllers.rs | 6 +++-- src/libs/satellite/src/auth/register.rs | 2 +- .../satellite/src/controllers/authenticate.rs | 20 ++++++++-------- src/libs/satellite/src/controllers/types.rs | 3 ++- src/libs/satellite/src/user/core/impls.rs | 23 ++++++++++++------- 8 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/console/src/impls.rs b/src/console/src/impls.rs index 1ab673dfcb..b46f85a3e8 100644 --- a/src/console/src/impls.rs +++ b/src/console/src/impls.rs @@ -194,11 +194,17 @@ impl Fee { } } -impl From<&OpenIdProvider> for OpenIdAuthProvider { - fn from(provider: &OpenIdProvider) -> Self { +impl TryFrom<&OpenIdProvider> for OpenIdAuthProvider { + type Error = String; + + fn try_from(provider: &OpenIdProvider) -> Result { match provider { - OpenIdProvider::Google => OpenIdAuthProvider::Google, - OpenIdProvider::GitHubProxy => OpenIdAuthProvider::GitHub, + OpenIdProvider::Google => Ok(OpenIdAuthProvider::Google), + OpenIdProvider::GitHubProxy => Ok(OpenIdAuthProvider::GitHub), + _ => Err(format!( + "{:?} is not supported for user authentication", + provider + )), } } } diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 064dd6e21e..c13a7a7c61 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -12,6 +12,7 @@ impl OpenIdProvider { // Swap for local development with the Juno API: // http://host.docker.internal:3000/v1/auth/certs Self::GitHubProxy => "https://api.juno.build/v1/auth/certs", + Self::GitHubActions => "https://token.actions.githubusercontent.com/.well-known/jwks", } } @@ -19,6 +20,7 @@ impl OpenIdProvider { match self { OpenIdProvider::Google => &["https://accounts.google.com", "accounts.google.com"], OpenIdProvider::GitHubProxy => &["https://api.juno.build/auth/github"], + OpenIdProvider::GitHubActions => &["https://token.actions.githubusercontent.com"], } } } @@ -65,7 +67,8 @@ impl Display for OpenIdProvider { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { OpenIdProvider::Google => write!(f, "Google"), - OpenIdProvider::GitHubProxy => write!(f, "GitHub"), + OpenIdProvider::GitHubProxy => write!(f, "GitHub Proxy"), + OpenIdProvider::GitHubActions => write!(f, "GitHub Actions"), } } } diff --git a/src/libs/auth/src/openid/types.rs b/src/libs/auth/src/openid/types.rs index b4cce5e646..cf6d15655d 100644 --- a/src/libs/auth/src/openid/types.rs +++ b/src/libs/auth/src/openid/types.rs @@ -10,6 +10,7 @@ pub mod provider { pub enum OpenIdProvider { Google, GitHubProxy, + GitHubActions, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index 556ef1f9d4..6df4f040d7 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -1,6 +1,6 @@ use crate::controllers::openid_authenticate_controller; use crate::controllers::store::{delete_controllers, set_controllers as set_controllers_store}; -use crate::controllers::types::AuthenticateControllerArgs; +use crate::controllers::types::{AuthenticateControllerArgs, OpenIdAuthenticateControllerResult}; use crate::{get_admin_controllers, get_controllers}; use ic_cdk::trap; use junobuild_shared::constants::shared::MAX_NUMBER_OF_SATELLITE_CONTROLLERS; @@ -52,7 +52,9 @@ pub fn list_controllers() -> Controllers { get_controllers() } -pub async fn authenticate_controller(args: AuthenticateControllerArgs) { +pub async fn authenticate_controller( + args: AuthenticateControllerArgs, +) -> OpenIdAuthenticateControllerResult { match args { AuthenticateControllerArgs::OpenId(args) => { openid_authenticate_controller(&args).await.unwrap_or_trap() diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index c9c0c5f023..2eb33b00c8 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -83,7 +83,7 @@ pub fn register_user( // Create or update the user. let user_data: UserData = UserData { banned, - provider: Some(provider.into()), + provider: Some(provider.try_into()?), provider_data: Some(ProviderData::OpenId(provider_data)), }; diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index b8111c515f..e5d41ccdd4 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -1,24 +1,24 @@ use crate::auth::strategy_impls::AuthHeap; -use crate::controllers::types::{AuthenticateControllerError, OpenIdAuthenticateControllerArgs}; +use crate::controllers::types::{ + AuthenticateControllerError, OpenIdAuthenticateControllerArgs, + OpenIdAuthenticateControllerResult, +}; use junobuild_auth::openid; use junobuild_auth::openid::types::provider::OpenIdProvider; -pub type OpenIdAuthenticateControllerResult = Result<(), AuthenticateControllerError>; - pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, -) -> OpenIdAuthenticateControllerResult { - match openid::workload::verify_openid_credentials_with_jwks_renewal( +) -> Result { + let result = match openid::workload::verify_openid_credentials_with_jwks_renewal( &args.jwt, - // TODO: GitHubActions - &OpenIdProvider::GitHubProxy, + &OpenIdProvider::GitHubActions, &AuthHeap, ) .await { - Ok(value) => value, - Err(err) => return Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), + Ok(_) => Ok(()), + Err(err) => Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), }; - Ok(()) + Ok(result) } diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index fbc5671a3b..058eb09cbd 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,5 +1,4 @@ use candid::{CandidType, Deserialize}; -use junobuild_auth::delegation::types::PrepareDelegationError; use junobuild_auth::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; use junobuild_shared::types::state::Metadata; use serde::Serialize; @@ -27,3 +26,5 @@ pub enum GrantableScope { pub enum AuthenticateControllerError { VerifyOpenIdCredentials(VerifyOpenidWorkloadCredentialsError), } + +pub type OpenIdAuthenticateControllerResult = Result<(), AuthenticateControllerError>; diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index f310229b40..c8b19cc7cf 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -160,11 +160,17 @@ impl From<&OpenIdCredential> for OpenIdData { } } -impl From<&OpenIdProvider> for AuthProvider { - fn from(provider: &OpenIdProvider) -> Self { +impl TryFrom<&OpenIdProvider> for AuthProvider { + type Error = String; + + fn try_from(provider: &OpenIdProvider) -> Result { match provider { - OpenIdProvider::Google => AuthProvider::Google, - OpenIdProvider::GitHubProxy => AuthProvider::GitHub, + OpenIdProvider::Google => Ok(AuthProvider::Google), + OpenIdProvider::GitHubProxy => Ok(AuthProvider::GitHub), + _ => Err(format!( + "{:?} is not supported for user authentication", + provider + )), } } } @@ -340,12 +346,13 @@ mod tests { #[test] fn test_openid_provider_to_auth_provider() { assert!(matches!( - AuthProvider::from(&OpenIdProvider::Google), - AuthProvider::Google + AuthProvider::try_from(&OpenIdProvider::Google), + Ok(AuthProvider::Google) )); assert!(matches!( - AuthProvider::from(&OpenIdProvider::GitHubProxy), - AuthProvider::GitHub + AuthProvider::try_from(&OpenIdProvider::GitHubProxy), + Ok(AuthProvider::GitHub) )); + assert!(AuthProvider::try_from(&OpenIdProvider::GitHubActions).is_err()); } } From e43ac030ba3ec42285961c618eda6b0ba174dd08 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 09:19:06 +0100 Subject: [PATCH 05/64] feat: set controller --- .../satellite/src/controllers/authenticate.rs | 36 ++++++++++++++++++- .../satellite/src/controllers/constants.rs | 7 ++++ src/libs/satellite/src/controllers/impls.rs | 11 ++++++ src/libs/satellite/src/controllers/mod.rs | 2 ++ src/libs/satellite/src/controllers/types.rs | 5 +-- 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/libs/satellite/src/controllers/constants.rs create mode 100644 src/libs/satellite/src/controllers/impls.rs diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index e5d41ccdd4..3d0242c0ac 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -1,10 +1,17 @@ use crate::auth::strategy_impls::AuthHeap; +use crate::controllers::constants::{DEFAULT_CONTROLLER_DURATION_NS, MAX_CONTROLLER_DURATION_NS}; +use crate::controllers::store::set_controllers; use crate::controllers::types::{ AuthenticateControllerError, OpenIdAuthenticateControllerArgs, OpenIdAuthenticateControllerResult, }; +use ic_cdk::api::time; use junobuild_auth::openid; use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_shared::segments::controllers::assert_controllers; +use junobuild_shared::types::interface::SetController; +use junobuild_shared::types::state::ControllerId; +use std::cmp::min; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, @@ -16,9 +23,36 @@ pub async fn openid_authenticate_controller( ) .await { - Ok(_) => Ok(()), + Ok(_) => { + authenticate_controller(args)?; + Ok(()) + } Err(err) => Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), }; Ok(result) } + +fn authenticate_controller(args: &OpenIdAuthenticateControllerArgs) -> Result<(), String> { + let controllers: [ControllerId; 1] = [args.controller_id.clone()]; + + assert_controllers(&controllers)?; + + // TODO: Assert do not exist + + let expires_at = min( + args.max_time_to_live + .unwrap_or(DEFAULT_CONTROLLER_DURATION_NS), + MAX_CONTROLLER_DURATION_NS, + ); + + let controller: SetController = SetController { + scope: args.scope.clone().into(), + metadata: args.metadata.clone(), + expires_at: Some(time().saturating_add(expires_at)), + }; + + set_controllers(&controllers, &controller); + + Ok(()) +} diff --git a/src/libs/satellite/src/controllers/constants.rs b/src/libs/satellite/src/controllers/constants.rs new file mode 100644 index 0000000000..5e2a474a40 --- /dev/null +++ b/src/libs/satellite/src/controllers/constants.rs @@ -0,0 +1,7 @@ +const MINUTE_NS: u64 = 60 * 1_000_000_000; + +// 10 minutes in nanoseconds +pub const DEFAULT_CONTROLLER_DURATION_NS: u64 = 10 * MINUTE_NS; + +// The maximum duration for a workload controller +pub const MAX_CONTROLLER_DURATION_NS: u64 = 60 * MINUTE_NS; diff --git a/src/libs/satellite/src/controllers/impls.rs b/src/libs/satellite/src/controllers/impls.rs new file mode 100644 index 0000000000..cc82bf1169 --- /dev/null +++ b/src/libs/satellite/src/controllers/impls.rs @@ -0,0 +1,11 @@ +use crate::controllers::types::GrantableScope; +use junobuild_shared::types::state::ControllerScope; + +impl From for ControllerScope { + fn from(scope: GrantableScope) -> Self { + match scope { + GrantableScope::Write => ControllerScope::Write, + GrantableScope::Submit => ControllerScope::Submit, + } + } +} diff --git a/src/libs/satellite/src/controllers/mod.rs b/src/libs/satellite/src/controllers/mod.rs index 99a8201cdf..a1406ae0cf 100644 --- a/src/libs/satellite/src/controllers/mod.rs +++ b/src/libs/satellite/src/controllers/mod.rs @@ -1,4 +1,6 @@ mod authenticate; +mod constants; +mod impls; pub mod store; pub mod types; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index 058eb09cbd..ca2908e855 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,6 +1,6 @@ use candid::{CandidType, Deserialize}; use junobuild_auth::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; -use junobuild_shared::types::state::Metadata; +use junobuild_shared::types::state::{ControllerId, Metadata}; use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] @@ -11,9 +11,10 @@ pub enum AuthenticateControllerArgs { #[derive(CandidType, Serialize, Deserialize)] pub struct OpenIdAuthenticateControllerArgs { pub jwt: String, + pub controller_id: ControllerId, + pub scope: GrantableScope, pub metadata: Metadata, pub max_time_to_live: Option, - pub scope: GrantableScope, } #[derive(CandidType, Serialize, Deserialize, Clone)] From 3164d87d8f2f870db8af8df0208078a13102f58d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 09:22:33 +0100 Subject: [PATCH 06/64] feat: return result --- src/libs/satellite/src/api/controllers.rs | 4 +--- .../satellite/src/controllers/authenticate.rs | 16 ++++++---------- src/libs/satellite/src/controllers/types.rs | 1 + 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index 6df4f040d7..916acb9bbe 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -56,8 +56,6 @@ pub async fn authenticate_controller( args: AuthenticateControllerArgs, ) -> OpenIdAuthenticateControllerResult { match args { - AuthenticateControllerArgs::OpenId(args) => { - openid_authenticate_controller(&args).await.unwrap_or_trap() - } + AuthenticateControllerArgs::OpenId(args) => openid_authenticate_controller(&args).await, } } diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index 3d0242c0ac..1c70614da9 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -15,25 +15,21 @@ use std::cmp::min; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, -) -> Result { - let result = match openid::workload::verify_openid_credentials_with_jwks_renewal( +) -> OpenIdAuthenticateControllerResult { + match openid::workload::verify_openid_credentials_with_jwks_renewal( &args.jwt, &OpenIdProvider::GitHubActions, &AuthHeap, ) .await { - Ok(_) => { - authenticate_controller(args)?; - Ok(()) - } + Ok(_) => register_controller(args) + .map_err(|err| AuthenticateControllerError::RegisterController(err.to_string())), Err(err) => Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), - }; - - Ok(result) + } } -fn authenticate_controller(args: &OpenIdAuthenticateControllerArgs) -> Result<(), String> { +fn register_controller(args: &OpenIdAuthenticateControllerArgs) -> Result<(), String> { let controllers: [ControllerId; 1] = [args.controller_id.clone()]; assert_controllers(&controllers)?; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index ca2908e855..a5877b710f 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -26,6 +26,7 @@ pub enum GrantableScope { #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticateControllerError { VerifyOpenIdCredentials(VerifyOpenidWorkloadCredentialsError), + RegisterController(String), } pub type OpenIdAuthenticateControllerResult = Result<(), AuthenticateControllerError>; From bde258c2fac6c64e49a3ba5b3f3fd6928f257c8d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 09:29:14 +0100 Subject: [PATCH 07/64] feat: try_from --- src/console/src/auth/register.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index dcb55f6df9..1552677139 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -42,7 +42,7 @@ pub async fn register_account( }; let provider = Provider::OpenId(OpenId { - provider: OpenIdAuthProvider::from(provider), + provider: OpenIdAuthProvider::try_from(provider)?, data: provider_data, }); From da25168661c1f24627010fc1c993507abda5f6d8 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 09:40:07 +0100 Subject: [PATCH 08/64] feat: did --- src/console/console.did | 5 +++-- src/declarations/console/console.did.d.ts | 5 +++-- .../console/console.factory.certified.did.js | 9 +++++++-- src/declarations/console/console.factory.did.js | 9 +++++++-- src/declarations/console/console.factory.did.mjs | 9 +++++++-- src/declarations/observatory/observatory.did.d.ts | 2 +- .../observatory/observatory.factory.certified.did.js | 5 +++-- src/declarations/observatory/observatory.factory.did.js | 5 +++-- src/declarations/observatory/observatory.factory.did.mjs | 5 +++-- src/declarations/satellite/satellite.did.d.ts | 2 +- .../satellite/satellite.factory.certified.did.js | 5 +++-- src/declarations/satellite/satellite.factory.did.js | 5 +++-- src/declarations/satellite/satellite.factory.did.mjs | 5 +++-- src/declarations/sputnik/sputnik.did.d.ts | 2 +- .../sputnik/sputnik.factory.certified.did.js | 5 +++-- src/declarations/sputnik/sputnik.factory.did.js | 5 +++-- src/libs/satellite/satellite.did | 2 +- src/observatory/observatory.did | 2 +- src/satellite/satellite.did | 2 +- src/sputnik/sputnik.did | 2 +- .../declarations/test_satellite/test_satellite.did.d.ts | 2 +- .../test_satellite.factory.certified.did.js | 5 +++-- .../test_satellite/test_satellite.factory.did.js | 5 +++-- src/tests/fixtures/test_satellite/test_satellite.did | 2 +- 24 files changed, 66 insertions(+), 39 deletions(-) diff --git a/src/console/console.did b/src/console/console.did index 152d748729..b43c903d61 100644 --- a/src/console/console.did +++ b/src/console/console.did @@ -223,7 +223,8 @@ type ListSegmentsArgs = record { segment_kind : opt StorableSegmentKind; }; type Memory = variant { Heap; Stable }; -type OpenId = record { provider : OpenIdProvider; data : OpenIdData }; +type OpenId = record { provider : OpenIdAuthProvider; data : OpenIdData }; +type OpenIdAuthProvider = variant { GitHub; Google }; type OpenIdData = record { name : opt text; locale : opt text; @@ -244,7 +245,7 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type OpenIdProviderConfig = record { delegation : opt OpenIdProviderDelegationConfig; client_id : text; diff --git a/src/declarations/console/console.did.d.ts b/src/declarations/console/console.did.d.ts index c084e35f32..88cdde866c 100644 --- a/src/declarations/console/console.did.d.ts +++ b/src/declarations/console/console.did.d.ts @@ -277,9 +277,10 @@ export interface ListSegmentsArgs { } export type Memory = { Heap: null } | { Stable: null }; export interface OpenId { - provider: OpenIdProvider; + provider: OpenIdAuthProvider; data: OpenIdData; } +export type OpenIdAuthProvider = { GitHub: null } | { Google: null }; export interface OpenIdData { name: [] | [string]; locale: [] | [string]; @@ -300,7 +301,7 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHub: null } | { Google: null }; +export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; export interface OpenIdProviderConfig { delegation: [] | [OpenIdProviderDelegationConfig]; client_id: string; diff --git a/src/declarations/console/console.factory.certified.did.js b/src/declarations/console/console.factory.certified.did.js index ef34c851dc..3b28c5d410 100644 --- a/src/declarations/console/console.factory.certified.did.js +++ b/src/declarations/console/console.factory.certified.did.js @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdProvider = IDL.Variant({ + const OpenIdAuthProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdProvider, + provider: OpenIdAuthProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,6 +133,11 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); + const OpenIdProvider = IDL.Variant({ + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null + }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/console/console.factory.did.js b/src/declarations/console/console.factory.did.js index 250efe59ac..7c2917ef44 100644 --- a/src/declarations/console/console.factory.did.js +++ b/src/declarations/console/console.factory.did.js @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdProvider = IDL.Variant({ + const OpenIdAuthProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdProvider, + provider: OpenIdAuthProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,6 +133,11 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); + const OpenIdProvider = IDL.Variant({ + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null + }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/console/console.factory.did.mjs b/src/declarations/console/console.factory.did.mjs index 250efe59ac..7c2917ef44 100644 --- a/src/declarations/console/console.factory.did.mjs +++ b/src/declarations/console/console.factory.did.mjs @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdProvider = IDL.Variant({ + const OpenIdAuthProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdProvider, + provider: OpenIdAuthProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,6 +133,11 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); + const OpenIdProvider = IDL.Variant({ + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null + }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/observatory/observatory.did.d.ts b/src/declarations/observatory/observatory.did.d.ts index 4884af3d3d..f63c9b80bf 100644 --- a/src/declarations/observatory/observatory.did.d.ts +++ b/src/declarations/observatory/observatory.did.d.ts @@ -106,7 +106,7 @@ export interface OpenIdCertificate { created_at: bigint; version: [] | [bigint]; } -export type OpenIdProvider = { GitHub: null } | { Google: null }; +export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; export interface RateConfig { max_tokens: bigint; time_per_token_ns: bigint; diff --git a/src/declarations/observatory/observatory.factory.certified.did.js b/src/declarations/observatory/observatory.factory.certified.did.js index c414d71f0e..3272635431 100644 --- a/src/declarations/observatory/observatory.factory.certified.did.js +++ b/src/declarations/observatory/observatory.factory.certified.did.js @@ -21,8 +21,9 @@ export const idlFactory = ({ IDL }) => { failed: IDL.Nat64 }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const GetOpenIdCertificateArgs = IDL.Record({ provider: OpenIdProvider }); const JwkType = IDL.Variant({ diff --git a/src/declarations/observatory/observatory.factory.did.js b/src/declarations/observatory/observatory.factory.did.js index 2984066a23..bee4e67af4 100644 --- a/src/declarations/observatory/observatory.factory.did.js +++ b/src/declarations/observatory/observatory.factory.did.js @@ -21,8 +21,9 @@ export const idlFactory = ({ IDL }) => { failed: IDL.Nat64 }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const GetOpenIdCertificateArgs = IDL.Record({ provider: OpenIdProvider }); const JwkType = IDL.Variant({ diff --git a/src/declarations/observatory/observatory.factory.did.mjs b/src/declarations/observatory/observatory.factory.did.mjs index 2984066a23..bee4e67af4 100644 --- a/src/declarations/observatory/observatory.factory.did.mjs +++ b/src/declarations/observatory/observatory.factory.did.mjs @@ -21,8 +21,9 @@ export const idlFactory = ({ IDL }) => { failed: IDL.Nat64 }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const GetOpenIdCertificateArgs = IDL.Record({ provider: OpenIdProvider }); const JwkType = IDL.Variant({ diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index dde06ba223..a51b94415b 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -270,7 +270,7 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHub: null } | { Google: null }; +export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; export interface OpenIdProviderConfig { delegation: [] | [OpenIdProviderDelegationConfig]; client_id: string; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index b5c665b978..00743eabe6 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index ba851264b2..0161a96157 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index ba851264b2..0161a96157 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index dde06ba223..a51b94415b 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -270,7 +270,7 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHub: null } | { Google: null }; +export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; export interface OpenIdProviderConfig { delegation: [] | [OpenIdProviderDelegationConfig]; client_id: string; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index b5c665b978..00743eabe6 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index ba851264b2..0161a96157 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 7814ee0c99..363df45340 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -221,7 +221,7 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type OpenIdProviderConfig = record { delegation : opt OpenIdProviderDelegationConfig; client_id : text; diff --git a/src/observatory/observatory.did b/src/observatory/observatory.did index 8d9d593ffb..a7621c5b5c 100644 --- a/src/observatory/observatory.did +++ b/src/observatory/observatory.did @@ -68,7 +68,7 @@ type OpenIdCertificate = record { created_at : nat64; version : opt nat64; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type RateConfig = record { max_tokens : nat64; time_per_token_ns : nat64 }; type RateKind = variant { OpenIdCertificateRequests }; type Segment = record { diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 8d8d97afff..9e4fec0266 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -223,7 +223,7 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type OpenIdProviderConfig = record { delegation : opt OpenIdProviderDelegationConfig; client_id : text; diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 06751195a6..4eb0e68586 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -223,7 +223,7 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type OpenIdProviderConfig = record { delegation : opt OpenIdProviderDelegationConfig; client_id : text; diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 9ee13bd161..cc7fbe2464 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -270,7 +270,7 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHub: null } | { Google: null }; +export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; export interface OpenIdProviderConfig { delegation: [] | [OpenIdProviderDelegationConfig]; client_id: string; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index a6e62ab11a..8fa34b8bfa 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index 13c363c35e..c91ec13f54 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -159,8 +159,9 @@ export const idlFactory = ({ IDL }) => { version: IDL.Opt(IDL.Nat64) }); const OpenIdProvider = IDL.Variant({ - GitHub: IDL.Null, - Google: IDL.Null + GitHubActions: IDL.Null, + Google: IDL.Null, + GitHubProxy: IDL.Null }); const OpenIdProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index 5990623871..61bee19770 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -223,7 +223,7 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; +type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; type OpenIdProviderConfig = record { delegation : opt OpenIdProviderDelegationConfig; client_id : text; From 0d069ca551e41dadaf31a189dc5d1f68fa9a4dd9 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 10:48:57 +0100 Subject: [PATCH 09/64] feat: auth id provider --- src/console/src/auth/delegation.rs | 9 ++-- src/console/src/auth/register.rs | 8 ++-- src/console/src/impls.rs | 18 +------- src/console/src/types.rs | 7 +-- src/libs/auth/src/delegation/get.rs | 6 +-- src/libs/auth/src/delegation/prepare.rs | 8 ++-- .../auth/src/delegation/utils/duration.rs | 4 +- src/libs/auth/src/delegation/utils/targets.rs | 4 +- src/libs/auth/src/openid/jwt/provider.rs | 41 +++++++++-------- src/libs/auth/src/openid/user/types.rs | 4 ++ src/libs/auth/src/openid/user/verify.rs | 26 ++++++----- src/libs/auth/src/state/impls.rs | 45 ++++++++++++++++++- src/libs/auth/src/state/store.rs | 4 +- src/libs/auth/src/state/types.rs | 23 ++++++---- src/libs/satellite/src/auth/delegation.rs | 8 ++-- src/libs/satellite/src/auth/register.rs | 5 ++- src/libs/satellite/src/user/core/impls.rs | 17 +++---- 17 files changed, 133 insertions(+), 104 deletions(-) diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index c9ef797e3c..1e6647c23d 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -4,17 +4,16 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::OpenIdProviders; +use junobuild_auth::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviders}; use junobuild_auth::{delegation, openid}; pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdProvider, OpenIdCredential), PrepareDelegationError>; + Result<(PreparedDelegation, OpenIdAuthProvider, OpenIdCredential), PrepareDelegationError>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, @@ -38,7 +37,7 @@ pub async fn openid_prepare_delegation( pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> GetDelegationResult { let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 1552677139..339cc272ec 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -1,14 +1,14 @@ use crate::accounts::{get_optional_account, init_account, update_provider}; use crate::types::state::{Account, OpenIdData, Provider}; -use crate::types::state::{OpenId, OpenIdAuthProvider}; +use crate::types::state::{OpenId}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; +use junobuild_auth::state::types::config::OpenIdAuthProvider; pub async fn register_account( public_key: &UserKey, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, credential: &OpenIdCredential, ) -> Result { let user_id = Principal::self_authenticating(public_key); @@ -42,7 +42,7 @@ pub async fn register_account( }; let provider = Provider::OpenId(OpenId { - provider: OpenIdAuthProvider::try_from(provider)?, + provider: provider.clone(), data: provider_data, }); diff --git a/src/console/src/impls.rs b/src/console/src/impls.rs index b46f85a3e8..5be57ff4b4 100644 --- a/src/console/src/impls.rs +++ b/src/console/src/impls.rs @@ -1,14 +1,13 @@ use crate::memory::manager::init_stable_state; use crate::types::ledger::{Fee, IcpPayment, IcrcPayment, IcrcPaymentKey}; use crate::types::state::{ - Account, HeapState, OpenIdAuthProvider, Segment, SegmentKey, State, StorableSegmentKind, + Account, HeapState, Segment, SegmentKey, State, StorableSegmentKind, }; use candid::Principal; use ic_cdk::api::time; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::storable::Bound; use ic_stable_structures::Storable; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_shared::ledger::types::cycles::CyclesTokens; use junobuild_shared::memory::serializers::{ deserialize_from_bytes, serialize_into_bytes, serialize_to_bytes, @@ -193,18 +192,3 @@ impl Fee { } } } - -impl TryFrom<&OpenIdProvider> for OpenIdAuthProvider { - type Error = String; - - fn try_from(provider: &OpenIdProvider) -> Result { - match provider { - OpenIdProvider::Google => Ok(OpenIdAuthProvider::Google), - OpenIdProvider::GitHubProxy => Ok(OpenIdAuthProvider::GitHub), - _ => Err(format!( - "{:?} is not supported for user authentication", - provider - )), - } - } -} diff --git a/src/console/src/types.rs b/src/console/src/types.rs index 5ecc7ae98a..566879a2db 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -17,6 +17,7 @@ pub mod state { use junobuild_storage::types::state::StorageHeapState; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; + use junobuild_auth::state::types::config::OpenIdAuthProvider; pub type Accounts = HashMap; pub type IcpPayments = HashMap; @@ -86,12 +87,6 @@ pub mod state { pub data: OpenIdData, } - #[derive(CandidType, Serialize, Deserialize, Clone)] - pub enum OpenIdAuthProvider { - Google, - GitHub, - } - #[derive(CandidType, Serialize, Deserialize, Clone, Eq, PartialEq)] pub struct OpenIdData { pub email: Option, diff --git a/src/libs/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 489bc8168e..3e97864fba 100644 --- a/src/libs/auth/src/delegation/get.rs +++ b/src/libs/auth/src/delegation/get.rs @@ -4,18 +4,18 @@ use crate::delegation::types::{ use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::types::provider::OpenIdProvider; use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::state::get_salt; use crate::state::services::read_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; use serde_bytes::ByteBuf; +use crate::state::types::config::OpenIdAuthProvider; pub fn openid_get_delegation( session_key: &SessionKey, expiration: Timestamp, credential: &OpenIdCredential, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> GetDelegationResult { @@ -33,7 +33,7 @@ pub fn get_delegation( session_key: &SessionKey, expiration: Timestamp, key: &OpenIdCredentialKey, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> GetDelegationResult { diff --git a/src/libs/auth/src/delegation/prepare.rs b/src/libs/auth/src/delegation/prepare.rs index 408d227256..d7007d064b 100644 --- a/src/libs/auth/src/delegation/prepare.rs +++ b/src/libs/auth/src/delegation/prepare.rs @@ -6,7 +6,7 @@ use crate::delegation::utils::duration::build_expiration; use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::types::provider::OpenIdProvider; +use crate::state::types::config::OpenIdAuthProvider; use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; use crate::state::get_salt; use crate::state::services::mutate_state; @@ -18,7 +18,7 @@ use serde_bytes::ByteBuf; pub fn openid_prepare_delegation( session_key: &SessionKey, credential: &OpenIdCredential, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> PrepareDelegationResult { @@ -36,7 +36,7 @@ pub fn openid_prepare_delegation( fn prepare_delegation( session_key: &SessionKey, key: &OpenIdCredentialKey, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> PrepareDelegationResult { @@ -60,7 +60,7 @@ fn prepare_delegation( fn add_delegation_signature( session_key: &PublicKey, expiration: Timestamp, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, seed: &[u8], auth_heap: &impl AuthHeapStrategy, ) { diff --git a/src/libs/auth/src/delegation/utils/duration.rs b/src/libs/auth/src/delegation/utils/duration.rs index 587f842741..74a4fbb9a2 100644 --- a/src/libs/auth/src/delegation/utils/duration.rs +++ b/src/libs/auth/src/delegation/utils/duration.rs @@ -1,11 +1,11 @@ use crate::delegation::constants::{DEFAULT_EXPIRATION_PERIOD_NS, MAX_EXPIRATION_PERIOD_NS}; -use crate::openid::types::provider::OpenIdProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use ic_cdk::api::time; use std::cmp::min; +use crate::state::types::config::OpenIdAuthProvider; -pub fn build_expiration(provider: &OpenIdProvider, auth_heap: &impl AuthHeapStrategy) -> u64 { +pub fn build_expiration(provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy) -> u64 { let max_time_to_live = get_config(auth_heap) .as_ref() .and_then(|config| config.openid.as_ref()) diff --git a/src/libs/auth/src/delegation/utils/targets.rs b/src/libs/auth/src/delegation/utils/targets.rs index 2d837cc43d..7fca708b00 100644 --- a/src/libs/auth/src/delegation/utils/targets.rs +++ b/src/libs/auth/src/delegation/utils/targets.rs @@ -1,8 +1,8 @@ use crate::delegation::types::DelegationTargets; -use crate::openid::types::provider::OpenIdProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::id; +use crate::state::types::config::OpenIdAuthProvider; // By default, and for security reasons, we restrict delegation to the authentication module // that created it. Developers can opt out (allow any targets) or define their own @@ -19,7 +19,7 @@ use junobuild_shared::ic::api::id; // Moreover, there is unlikely to be a valid use case where a delegation generated by the Satellite // should target no canister at all. pub fn build_targets( - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy, ) -> Option { get_config(auth_heap) diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 42f5cbebde..2913a4b2ff 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,16 +1,15 @@ use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::jwt::types::token::UnsafeClaims; -use crate::openid::types::provider::OpenIdProvider; -use crate::state::types::config::{OpenIdProviderConfig, OpenIdProviders}; +use crate::state::types::config::{OpenIdProviderAuthConfig, OpenIdAuthProviders, OpenIdAuthProvider}; use jsonwebtoken::dangerous; /// ⚠️ **Warning:** This function decodes the JWT payload *without verifying its signature*. /// Use only to inspect claims (e.g., `iss`) before performing a verified decode. -pub fn unsafe_find_jwt_provider<'a>( - providers: &'a OpenIdProviders, +pub fn unsafe_find_jwt_auth_provider<'a>( + providers: &'a OpenIdAuthProviders, jwt: &str, -) -> Result<(OpenIdProvider, &'a OpenIdProviderConfig), JwtFindProviderError> { +) -> Result<(OpenIdAuthProvider, &'a OpenIdProviderAuthConfig), JwtFindProviderError> { // 1) Header sanity check decode_jwt_header(jwt).map_err(JwtFindProviderError::from)?; @@ -33,10 +32,10 @@ pub fn unsafe_find_jwt_provider<'a>( #[cfg(test)] mod tests { - use super::unsafe_find_jwt_provider; + use super::unsafe_find_jwt_auth_provider; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::types::provider::OpenIdProvider; - use crate::state::types::config::{OpenIdProviderConfig, OpenIdProviders}; + use crate::state::types::config::{OpenIdProviderAuthConfig, OpenIdAuthProviders, OpenIdAuthProvider}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use serde_json::json; @@ -51,11 +50,11 @@ mod tests { format!("{h}.{p}.{s}") } - fn providers_with_google() -> OpenIdProviders { + fn providers_with_google() -> OpenIdAuthProviders { let mut map = BTreeMap::new(); map.insert( - OpenIdProvider::Google, - OpenIdProviderConfig { + OpenIdAuthProvider::Google, + OpenIdProviderAuthConfig { client_id: "client-123".into(), delegation: None, }, @@ -71,9 +70,9 @@ mod tests { let provs = providers_with_google(); let (provider, cfg) = - unsafe_find_jwt_provider(&provs, &jwt).expect("should match provider"); + unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match provider"); - assert_eq!(provider, OpenIdProvider::Google); + assert_eq!(provider, OpenIdAuthProvider::Google); assert_eq!(cfg.client_id, "client-123"); } @@ -84,8 +83,8 @@ mod tests { let provs = providers_with_google(); let (provider, _) = - unsafe_find_jwt_provider(&provs, &jwt).expect("should match even without typ"); - assert_eq!(provider, OpenIdProvider::Google); + unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match even without typ"); + assert_eq!(provider, OpenIdAuthProvider::Google); } #[test] @@ -94,7 +93,7 @@ mod tests { let jwt = jwt_with(json!({"alg":"HS256","typ":"JWT"}), json!({"iss": iss})); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "alg"), @@ -111,7 +110,7 @@ mod tests { ); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "typ"), @@ -123,7 +122,7 @@ mod tests { fn returns_no_matching_provider_when_issuer_missing() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({})); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -134,7 +133,7 @@ mod tests { json!({"iss":"https://unknown.example.com"}), ); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -142,7 +141,7 @@ mod tests { fn malformed_token_is_badsig() { let jwt = "definitely-not-a-jwt"; let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -155,7 +154,7 @@ mod tests { let jwt = format!("{h}.{p}.{s}"); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -163,7 +162,7 @@ mod tests { fn empty_iss_is_no_match() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({"iss": ""})); let provs = providers_with_google(); - let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } } diff --git a/src/libs/auth/src/openid/user/types.rs b/src/libs/auth/src/openid/user/types.rs index af0991b32c..97f7cd6a2b 100644 --- a/src/libs/auth/src/openid/user/types.rs +++ b/src/libs/auth/src/openid/user/types.rs @@ -32,3 +32,7 @@ pub(crate) mod errors { JwtVerify(JwtVerifyError), } } + +pub mod provider { + +} \ No newline at end of file diff --git a/src/libs/auth/src/openid/user/verify.rs b/src/libs/auth/src/openid/user/verify.rs index e3bfe85233..381ab7bff1 100644 --- a/src/libs/auth/src/openid/user/verify.rs +++ b/src/libs/auth/src/openid/user/verify.rs @@ -2,53 +2,57 @@ use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; -use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; +use crate::openid::jwt::{unsafe_find_jwt_auth_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdProvider; use crate::openid::user::types::errors::VerifyOpenidCredentialsError; use crate::openid::user::types::interface::OpenIdCredential; use crate::openid::utils::build_nonce; -use crate::state::types::config::{OpenIdProviderClientId, OpenIdProviders}; +use crate::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviderClientId, OpenIdAuthProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; type VerifyOpenIdCredentialsResult = - Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; + Result<(OpenIdCredential, OpenIdAuthProvider), VerifyOpenidCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, salt: &Salt, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdCredentialsResult { - let (provider, config) = unsafe_find_jwt_provider(providers, jwt) + let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; + let provider: OpenIdProvider = (&auth_provider).into(); + let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &provider, &config.client_id, salt) + verify_openid_credentials(jwt, &jwks, &auth_provider, &config.client_id, salt) } pub fn verify_openid_credentials_with_cached_jwks( jwt: &str, salt: &Salt, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdCredentialsResult { - let (provider, config) = unsafe_find_jwt_provider(providers, jwt) + let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; + let provider: OpenIdProvider = (&auth_provider).into(); + let jwks = get_jwks(&provider, auth_heap).ok_or(VerifyOpenidCredentialsError::GetCachedJwks)?; - verify_openid_credentials(jwt, &jwks, &provider, &config.client_id, salt) + verify_openid_credentials(jwt, &jwks, &auth_provider, &config.client_id, salt) } fn verify_openid_credentials( jwt: &str, jwks: &Jwks, - provider: &OpenIdProvider, - client_id: &OpenIdProviderClientId, + provider: &OpenIdAuthProvider, + client_id: &OpenIdAuthProviderClientId, salt: &Salt, ) -> VerifyOpenIdCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { diff --git a/src/libs/auth/src/state/impls.rs b/src/libs/auth/src/state/impls.rs index bba2acce0f..b7f46bace6 100644 --- a/src/libs/auth/src/state/impls.rs +++ b/src/libs/auth/src/state/impls.rs @@ -1,5 +1,5 @@ -use crate::openid::types::provider::OpenIdCertificate; -use crate::state::types::config::AuthenticationConfig; +use crate::openid::types::provider::{OpenIdCertificate, OpenIdProvider}; +use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProvider}; use crate::state::types::interface::SetAuthenticationConfig; use crate::state::types::state::{OpenIdCachedCertificate, OpenIdLastFetchAttempt}; use ic_cdk::api::time; @@ -74,3 +74,44 @@ impl OpenIdCachedCertificate { self.last_fetch_attempt.streak_count = 0; } } + + +impl TryFrom<&OpenIdProvider> for OpenIdAuthProvider { + type Error = String; + + fn try_from(provider: &OpenIdProvider) -> Result { + match provider { + OpenIdProvider::Google => Ok(OpenIdAuthProvider::Google), + OpenIdProvider::GitHubProxy => Ok(OpenIdAuthProvider::GitHub), + _ => Err(format!( + "{:?} is not supported for user authentication", + provider + )), + } + } +} + +impl From<&OpenIdAuthProvider> for OpenIdProvider { + fn from(auth_provider: &OpenIdAuthProvider) -> Self { + match auth_provider { + OpenIdAuthProvider::Google => OpenIdProvider::Google, + OpenIdAuthProvider::GitHub => OpenIdProvider::GitHubProxy, + } + } +} + +impl OpenIdAuthProvider { + pub fn jwks_url(&self) -> &'static str { + match self { + Self::Google => OpenIdProvider::Google.jwks_url(), + Self::GitHub => OpenIdProvider::GitHubProxy.jwks_url(), + } + } + + pub fn issuers(&self) -> &[&'static str] { + match self { + Self::Google => OpenIdProvider::Google.issuers(), + Self::GitHub => OpenIdProvider::GitHubProxy.issuers(), + } + } +} \ No newline at end of file diff --git a/src/libs/auth/src/state/store.rs b/src/libs/auth/src/state/store.rs index 445a38d8a5..1f6ea6ca28 100644 --- a/src/libs/auth/src/state/store.rs +++ b/src/libs/auth/src/state/store.rs @@ -2,7 +2,7 @@ use crate::errors::{JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISAB use crate::state::assert::assert_set_config; use crate::state::heap::get_config; use crate::state::heap::insert_config; -use crate::state::types::config::{AuthenticationConfig, OpenIdProviders}; +use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProviders}; use crate::state::types::interface::SetAuthenticationConfig; use crate::state::{get_salt, insert_salt}; use crate::strategies::AuthHeapStrategy; @@ -46,7 +46,7 @@ pub async fn init_salt(auth_heap: &impl AuthHeapStrategy) -> Result<(), String> Ok(()) } -pub fn get_providers(auth_heap: &impl AuthHeapStrategy) -> Result { +pub fn get_providers(auth_heap: &impl AuthHeapStrategy) -> Result { let config = get_config(auth_heap).ok_or(JUNO_AUTH_ERROR_NOT_CONFIGURED.to_string())?; let openid = config .openid diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index abc4278382..84500f1337 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -53,7 +53,6 @@ pub(crate) mod runtime_state { pub mod config { use crate::delegation::types::DelegationTargets; - use crate::openid::types::provider::OpenIdProvider; use candid::{CandidType, Deserialize, Principal}; use junobuild_shared::types::core::DomainName; use junobuild_shared::types::state::{Timestamp, Version}; @@ -72,7 +71,7 @@ pub mod config { #[derive(Default, CandidType, Serialize, Deserialize, Clone)] pub struct AuthenticationConfigOpenId { - pub providers: OpenIdProviders, + pub providers: OpenIdAuthProviders, pub observatory_id: Option, } @@ -87,18 +86,26 @@ pub mod config { pub allowed_callers: Vec, } - pub type OpenIdProviders = BTreeMap; + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum OpenIdAuthProvider { + Google, + GitHub, + } + + pub type OpenIdAuthProviders = BTreeMap; - pub type OpenIdProviderClientId = String; + pub type OpenIdAuthProviderClientId = String; #[derive(Default, CandidType, Serialize, Deserialize, Clone, Debug)] - pub struct OpenIdProviderConfig { - pub client_id: OpenIdProviderClientId, - pub delegation: Option, + pub struct OpenIdProviderAuthConfig { + pub client_id: OpenIdAuthProviderClientId, + pub delegation: Option, } #[derive(Default, CandidType, Serialize, Deserialize, Clone, Debug)] - pub struct OpenIdProviderDelegationConfig { + pub struct OpenIdAuthProviderDelegationConfig { pub targets: Option, pub max_time_to_live: Option, } diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index c9ef797e3c..0c28126f6f 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -6,15 +6,15 @@ use junobuild_auth::delegation::types::{ }; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::OpenIdProviders; +use junobuild_auth::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviders}; use junobuild_auth::{delegation, openid}; pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdProvider, OpenIdCredential), PrepareDelegationError>; + Result<(PreparedDelegation, OpenIdAuthProvider, OpenIdCredential), PrepareDelegationError>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, @@ -38,7 +38,7 @@ pub async fn openid_prepare_delegation( pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> GetDelegationResult { let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index 2eb33b00c8..353ef27f33 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -9,6 +9,7 @@ use candid::Principal; use junobuild_auth::delegation::types::UserKey; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; +use junobuild_auth::state::types::config::OpenIdAuthProvider; use junobuild_collections::constants::db::COLLECTION_USER_KEY; use junobuild_collections::msg::msg_db_collection_not_found; use junobuild_shared::ic::api::id; @@ -16,7 +17,7 @@ use junobuild_utils::decode_doc_data; pub fn register_user( public_key: &UserKey, - provider: &OpenIdProvider, + provider: &OpenIdAuthProvider, credential: &OpenIdCredential, ) -> Result { let user_collection = COLLECTION_USER_KEY.to_string(); @@ -83,7 +84,7 @@ pub fn register_user( // Create or update the user. let user_data: UserData = UserData { banned, - provider: Some(provider.try_into()?), + provider: Some(provider.into()), provider_data: Some(ProviderData::OpenId(provider_data)), }; diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index c8b19cc7cf..2b6ab33a1e 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -12,6 +12,7 @@ use crate::{Doc, SetDoc}; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; +use junobuild_auth::state::types::config::OpenIdAuthProvider; use junobuild_utils::encode_doc_data; impl Validated for WebAuthnData { @@ -160,17 +161,11 @@ impl From<&OpenIdCredential> for OpenIdData { } } -impl TryFrom<&OpenIdProvider> for AuthProvider { - type Error = String; - - fn try_from(provider: &OpenIdProvider) -> Result { - match provider { - OpenIdProvider::Google => Ok(AuthProvider::Google), - OpenIdProvider::GitHubProxy => Ok(AuthProvider::GitHub), - _ => Err(format!( - "{:?} is not supported for user authentication", - provider - )), +impl From<&OpenIdAuthProvider> for AuthProvider { + fn from(auth_provider: &OpenIdAuthProvider) -> Self { + match auth_provider { + OpenIdAuthProvider::Google => AuthProvider::Google, + OpenIdAuthProvider::GitHub => AuthProvider::GitHub, } } } From 7eb171c8d264d89c242109178823e9ac776550d4 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 10:55:07 +0100 Subject: [PATCH 10/64] feat: more and rename type --- src/console/src/auth/delegation.rs | 13 ++++-- src/console/src/auth/register.rs | 6 +-- src/console/src/impls.rs | 4 +- src/console/src/types.rs | 4 +- src/libs/auth/src/delegation/get.rs | 6 +-- src/libs/auth/src/delegation/prepare.rs | 8 ++-- .../auth/src/delegation/utils/duration.rs | 7 ++- src/libs/auth/src/delegation/utils/targets.rs | 4 +- src/libs/auth/src/openid/jwt/provider.rs | 15 ++++--- src/libs/auth/src/openid/user/impls.rs | 42 +++++++++++++++++ src/libs/auth/src/openid/user/types.rs | 14 +++++- src/libs/auth/src/openid/user/verify.rs | 7 +-- src/libs/auth/src/state/impls.rs | 45 +------------------ src/libs/auth/src/state/types.rs | 11 +---- src/libs/satellite/src/auth/delegation.rs | 14 ++++-- src/libs/satellite/src/auth/register.rs | 5 +-- src/libs/satellite/src/user/core/impls.rs | 10 ++--- 17 files changed, 118 insertions(+), 97 deletions(-) diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index 1e6647c23d..5867de2d03 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -5,11 +5,18 @@ use junobuild_auth::delegation::types::{ PrepareDelegationError, PreparedDelegation, }; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviders}; +use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::state::types::config::OpenIdAuthProviders; use junobuild_auth::{delegation, openid}; -pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdAuthProvider, OpenIdCredential), PrepareDelegationError>; +pub type OpenIdPrepareDelegationResult = Result< + ( + PreparedDelegation, + OpenIdDelegationProvider, + OpenIdCredential, + ), + PrepareDelegationError, +>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 339cc272ec..6429ff7261 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -1,14 +1,14 @@ use crate::accounts::{get_optional_account, init_account, update_provider}; +use crate::types::state::OpenId; use crate::types::state::{Account, OpenIdData, Provider}; -use crate::types::state::{OpenId}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::OpenIdAuthProvider; +use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; pub async fn register_account( public_key: &UserKey, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, credential: &OpenIdCredential, ) -> Result { let user_id = Principal::self_authenticating(public_key); diff --git a/src/console/src/impls.rs b/src/console/src/impls.rs index 5be57ff4b4..3697eab5de 100644 --- a/src/console/src/impls.rs +++ b/src/console/src/impls.rs @@ -1,8 +1,6 @@ use crate::memory::manager::init_stable_state; use crate::types::ledger::{Fee, IcpPayment, IcrcPayment, IcrcPaymentKey}; -use crate::types::state::{ - Account, HeapState, Segment, SegmentKey, State, StorableSegmentKind, -}; +use crate::types::state::{Account, HeapState, Segment, SegmentKey, State, StorableSegmentKind}; use candid::Principal; use ic_cdk::api::time; use ic_ledger_types::{BlockIndex, Tokens}; diff --git a/src/console/src/types.rs b/src/console/src/types.rs index 566879a2db..5f53fb0d6d 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -4,6 +4,7 @@ pub mod state { use candid::CandidType; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::StableBTreeMap; + use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::state::AuthenticationHeapState; use junobuild_cdn::proposals::{ProposalsStable, SegmentDeploymentVersion}; use junobuild_cdn::storage::{ProposalAssetsStable, ProposalContentChunksStable}; @@ -17,7 +18,6 @@ pub mod state { use junobuild_storage::types::state::StorageHeapState; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; - use junobuild_auth::state::types::config::OpenIdAuthProvider; pub type Accounts = HashMap; pub type IcpPayments = HashMap; @@ -83,7 +83,7 @@ pub mod state { #[derive(CandidType, Serialize, Deserialize, Clone)] pub struct OpenId { - pub provider: OpenIdAuthProvider, + pub provider: OpenIdDelegationProvider, pub data: OpenIdData, } diff --git a/src/libs/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 3e97864fba..9a530b9c99 100644 --- a/src/libs/auth/src/delegation/get.rs +++ b/src/libs/auth/src/delegation/get.rs @@ -5,17 +5,17 @@ use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::read_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; use serde_bytes::ByteBuf; -use crate::state::types::config::OpenIdAuthProvider; pub fn openid_get_delegation( session_key: &SessionKey, expiration: Timestamp, credential: &OpenIdCredential, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> GetDelegationResult { @@ -33,7 +33,7 @@ pub fn get_delegation( session_key: &SessionKey, expiration: Timestamp, key: &OpenIdCredentialKey, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> GetDelegationResult { diff --git a/src/libs/auth/src/delegation/prepare.rs b/src/libs/auth/src/delegation/prepare.rs index d7007d064b..e277c3c989 100644 --- a/src/libs/auth/src/delegation/prepare.rs +++ b/src/libs/auth/src/delegation/prepare.rs @@ -6,8 +6,8 @@ use crate::delegation::utils::duration::build_expiration; use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::state::types::config::OpenIdAuthProvider; use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::mutate_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; @@ -18,7 +18,7 @@ use serde_bytes::ByteBuf; pub fn openid_prepare_delegation( session_key: &SessionKey, credential: &OpenIdCredential, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> PrepareDelegationResult { @@ -36,7 +36,7 @@ pub fn openid_prepare_delegation( fn prepare_delegation( session_key: &SessionKey, key: &OpenIdCredentialKey, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> PrepareDelegationResult { @@ -60,7 +60,7 @@ fn prepare_delegation( fn add_delegation_signature( session_key: &PublicKey, expiration: Timestamp, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, seed: &[u8], auth_heap: &impl AuthHeapStrategy, ) { diff --git a/src/libs/auth/src/delegation/utils/duration.rs b/src/libs/auth/src/delegation/utils/duration.rs index 74a4fbb9a2..f3a9ed56a3 100644 --- a/src/libs/auth/src/delegation/utils/duration.rs +++ b/src/libs/auth/src/delegation/utils/duration.rs @@ -1,11 +1,14 @@ use crate::delegation::constants::{DEFAULT_EXPIRATION_PERIOD_NS, MAX_EXPIRATION_PERIOD_NS}; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use ic_cdk::api::time; use std::cmp::min; -use crate::state::types::config::OpenIdAuthProvider; -pub fn build_expiration(provider: &OpenIdAuthProvider, auth_heap: &impl AuthHeapStrategy) -> u64 { +pub fn build_expiration( + provider: &OpenIdDelegationProvider, + auth_heap: &impl AuthHeapStrategy, +) -> u64 { let max_time_to_live = get_config(auth_heap) .as_ref() .and_then(|config| config.openid.as_ref()) diff --git a/src/libs/auth/src/delegation/utils/targets.rs b/src/libs/auth/src/delegation/utils/targets.rs index 7fca708b00..8eb7eee257 100644 --- a/src/libs/auth/src/delegation/utils/targets.rs +++ b/src/libs/auth/src/delegation/utils/targets.rs @@ -1,8 +1,8 @@ use crate::delegation::types::DelegationTargets; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::id; -use crate::state::types::config::OpenIdAuthProvider; // By default, and for security reasons, we restrict delegation to the authentication module // that created it. Developers can opt out (allow any targets) or define their own @@ -19,7 +19,7 @@ use crate::state::types::config::OpenIdAuthProvider; // Moreover, there is unlikely to be a valid use case where a delegation generated by the Satellite // should target no canister at all. pub fn build_targets( - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, ) -> Option { get_config(auth_heap) diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 2913a4b2ff..72a387d569 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,7 +1,8 @@ use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::jwt::types::token::UnsafeClaims; -use crate::state::types::config::{OpenIdProviderAuthConfig, OpenIdAuthProviders, OpenIdAuthProvider}; +use crate::openid::user::types::provider::OpenIdDelegationProvider; +use crate::state::types::config::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; use jsonwebtoken::dangerous; /// ⚠️ **Warning:** This function decodes the JWT payload *without verifying its signature*. @@ -9,7 +10,7 @@ use jsonwebtoken::dangerous; pub fn unsafe_find_jwt_auth_provider<'a>( providers: &'a OpenIdAuthProviders, jwt: &str, -) -> Result<(OpenIdAuthProvider, &'a OpenIdProviderAuthConfig), JwtFindProviderError> { +) -> Result<(OpenIdDelegationProvider, &'a OpenIdProviderAuthConfig), JwtFindProviderError> { // 1) Header sanity check decode_jwt_header(jwt).map_err(JwtFindProviderError::from)?; @@ -34,8 +35,8 @@ pub fn unsafe_find_jwt_auth_provider<'a>( mod tests { use super::unsafe_find_jwt_auth_provider; use crate::openid::jwt::types::errors::JwtFindProviderError; - use crate::openid::types::provider::OpenIdProvider; - use crate::state::types::config::{OpenIdProviderAuthConfig, OpenIdAuthProviders, OpenIdAuthProvider}; + use crate::openid::user::types::provider::OpenIdDelegationProvider; + use crate::state::types::config::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use serde_json::json; @@ -53,7 +54,7 @@ mod tests { fn providers_with_google() -> OpenIdAuthProviders { let mut map = BTreeMap::new(); map.insert( - OpenIdAuthProvider::Google, + OpenIdDelegationProvider::Google, OpenIdProviderAuthConfig { client_id: "client-123".into(), delegation: None, @@ -72,7 +73,7 @@ mod tests { let (provider, cfg) = unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match provider"); - assert_eq!(provider, OpenIdAuthProvider::Google); + assert_eq!(provider, OpenIdDelegationProvider::Google); assert_eq!(cfg.client_id, "client-123"); } @@ -84,7 +85,7 @@ mod tests { let provs = providers_with_google(); let (provider, _) = unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match even without typ"); - assert_eq!(provider, OpenIdAuthProvider::Google); + assert_eq!(provider, OpenIdDelegationProvider::Google); } #[test] diff --git a/src/libs/auth/src/openid/user/impls.rs b/src/libs/auth/src/openid/user/impls.rs index 0477724ab3..7196eff2e5 100644 --- a/src/libs/auth/src/openid/user/impls.rs +++ b/src/libs/auth/src/openid/user/impls.rs @@ -1,5 +1,7 @@ use crate::openid::jwt::types::token::Claims; +use crate::openid::types::provider::OpenIdProvider; use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use jsonwebtoken::TokenData; impl From> for OpenIdCredential { @@ -26,3 +28,43 @@ impl<'a> From<&'a OpenIdCredential> for OpenIdCredentialKey<'a> { } } } + +impl TryFrom<&OpenIdProvider> for OpenIdDelegationProvider { + type Error = String; + + fn try_from(provider: &OpenIdProvider) -> Result { + match provider { + OpenIdProvider::Google => Ok(OpenIdDelegationProvider::Google), + OpenIdProvider::GitHubProxy => Ok(OpenIdDelegationProvider::GitHub), + _ => Err(format!( + "{:?} is not supported for user authentication", + provider + )), + } + } +} + +impl From<&OpenIdDelegationProvider> for OpenIdProvider { + fn from(auth_provider: &OpenIdDelegationProvider) -> Self { + match auth_provider { + OpenIdDelegationProvider::Google => OpenIdProvider::Google, + OpenIdDelegationProvider::GitHub => OpenIdProvider::GitHubProxy, + } + } +} + +impl OpenIdDelegationProvider { + pub fn jwks_url(&self) -> &'static str { + match self { + Self::Google => OpenIdProvider::Google.jwks_url(), + Self::GitHub => OpenIdProvider::GitHubProxy.jwks_url(), + } + } + + pub fn issuers(&self) -> &[&'static str] { + match self { + Self::Google => OpenIdProvider::Google.issuers(), + Self::GitHub => OpenIdProvider::GitHubProxy.issuers(), + } + } +} diff --git a/src/libs/auth/src/openid/user/types.rs b/src/libs/auth/src/openid/user/types.rs index 97f7cd6a2b..5af20ee0a9 100644 --- a/src/libs/auth/src/openid/user/types.rs +++ b/src/libs/auth/src/openid/user/types.rs @@ -1,3 +1,6 @@ +use candid::{CandidType, Deserialize}; +use serde::Serialize; + pub mod interface { pub struct OpenIdCredentialKey<'a> { pub iss: &'a String, @@ -34,5 +37,14 @@ pub(crate) mod errors { } pub mod provider { + use candid::{CandidType, Deserialize}; + use serde::Serialize; -} \ No newline at end of file + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum OpenIdDelegationProvider { + Google, + GitHub, + } +} diff --git a/src/libs/auth/src/openid/user/verify.rs b/src/libs/auth/src/openid/user/verify.rs index 381ab7bff1..fa97648e22 100644 --- a/src/libs/auth/src/openid/user/verify.rs +++ b/src/libs/auth/src/openid/user/verify.rs @@ -6,13 +6,14 @@ use crate::openid::jwt::{unsafe_find_jwt_auth_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdProvider; use crate::openid::user::types::errors::VerifyOpenidCredentialsError; use crate::openid::user::types::interface::OpenIdCredential; +use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::openid::utils::build_nonce; -use crate::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviderClientId, OpenIdAuthProviders}; +use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; type VerifyOpenIdCredentialsResult = - Result<(OpenIdCredential, OpenIdAuthProvider), VerifyOpenidCredentialsError>; + Result<(OpenIdCredential, OpenIdDelegationProvider), VerifyOpenidCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, @@ -51,7 +52,7 @@ pub fn verify_openid_credentials_with_cached_jwks( fn verify_openid_credentials( jwt: &str, jwks: &Jwks, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, client_id: &OpenIdAuthProviderClientId, salt: &Salt, ) -> VerifyOpenIdCredentialsResult { diff --git a/src/libs/auth/src/state/impls.rs b/src/libs/auth/src/state/impls.rs index b7f46bace6..bba2acce0f 100644 --- a/src/libs/auth/src/state/impls.rs +++ b/src/libs/auth/src/state/impls.rs @@ -1,5 +1,5 @@ -use crate::openid::types::provider::{OpenIdCertificate, OpenIdProvider}; -use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProvider}; +use crate::openid::types::provider::OpenIdCertificate; +use crate::state::types::config::AuthenticationConfig; use crate::state::types::interface::SetAuthenticationConfig; use crate::state::types::state::{OpenIdCachedCertificate, OpenIdLastFetchAttempt}; use ic_cdk::api::time; @@ -74,44 +74,3 @@ impl OpenIdCachedCertificate { self.last_fetch_attempt.streak_count = 0; } } - - -impl TryFrom<&OpenIdProvider> for OpenIdAuthProvider { - type Error = String; - - fn try_from(provider: &OpenIdProvider) -> Result { - match provider { - OpenIdProvider::Google => Ok(OpenIdAuthProvider::Google), - OpenIdProvider::GitHubProxy => Ok(OpenIdAuthProvider::GitHub), - _ => Err(format!( - "{:?} is not supported for user authentication", - provider - )), - } - } -} - -impl From<&OpenIdAuthProvider> for OpenIdProvider { - fn from(auth_provider: &OpenIdAuthProvider) -> Self { - match auth_provider { - OpenIdAuthProvider::Google => OpenIdProvider::Google, - OpenIdAuthProvider::GitHub => OpenIdProvider::GitHubProxy, - } - } -} - -impl OpenIdAuthProvider { - pub fn jwks_url(&self) -> &'static str { - match self { - Self::Google => OpenIdProvider::Google.jwks_url(), - Self::GitHub => OpenIdProvider::GitHubProxy.jwks_url(), - } - } - - pub fn issuers(&self) -> &[&'static str] { - match self { - Self::Google => OpenIdProvider::Google.issuers(), - Self::GitHub => OpenIdProvider::GitHubProxy.issuers(), - } - } -} \ No newline at end of file diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index 84500f1337..f8db817b2b 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -53,6 +53,7 @@ pub(crate) mod runtime_state { pub mod config { use crate::delegation::types::DelegationTargets; + use crate::openid::user::types::provider::OpenIdDelegationProvider; use candid::{CandidType, Deserialize, Principal}; use junobuild_shared::types::core::DomainName; use junobuild_shared::types::state::{Timestamp, Version}; @@ -86,15 +87,7 @@ pub mod config { pub allowed_callers: Vec, } - #[derive( - CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, - )] - pub enum OpenIdAuthProvider { - Google, - GitHub, - } - - pub type OpenIdAuthProviders = BTreeMap; + pub type OpenIdAuthProviders = BTreeMap; pub type OpenIdAuthProviderClientId = String; diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index 0c28126f6f..5867de2d03 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -4,13 +4,19 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::{OpenIdAuthProvider, OpenIdAuthProviders}; +use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::state::types::config::OpenIdAuthProviders; use junobuild_auth::{delegation, openid}; -pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdAuthProvider, OpenIdCredential), PrepareDelegationError>; +pub type OpenIdPrepareDelegationResult = Result< + ( + PreparedDelegation, + OpenIdDelegationProvider, + OpenIdCredential, + ), + PrepareDelegationError, +>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index 353ef27f33..308d577780 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -7,9 +7,8 @@ use crate::user::core::types::state::{OpenIdData, ProviderData, UserData}; use crate::Doc; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::state::types::config::OpenIdAuthProvider; +use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; use junobuild_collections::constants::db::COLLECTION_USER_KEY; use junobuild_collections::msg::msg_db_collection_not_found; use junobuild_shared::ic::api::id; @@ -17,7 +16,7 @@ use junobuild_utils::decode_doc_data; pub fn register_user( public_key: &UserKey, - provider: &OpenIdAuthProvider, + provider: &OpenIdDelegationProvider, credential: &OpenIdCredential, ) -> Result { let user_collection = COLLECTION_USER_KEY.to_string(); diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index 2b6ab33a1e..4c9d374c56 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -11,8 +11,8 @@ use crate::user::core::types::state::{ use crate::{Doc, SetDoc}; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::openid::user::types::interface::OpenIdCredential; +use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; -use junobuild_auth::state::types::config::OpenIdAuthProvider; use junobuild_utils::encode_doc_data; impl Validated for WebAuthnData { @@ -161,11 +161,11 @@ impl From<&OpenIdCredential> for OpenIdData { } } -impl From<&OpenIdAuthProvider> for AuthProvider { - fn from(auth_provider: &OpenIdAuthProvider) -> Self { +impl From<&OpenIdDelegationProvider> for AuthProvider { + fn from(auth_provider: &OpenIdDelegationProvider) -> Self { match auth_provider { - OpenIdAuthProvider::Google => AuthProvider::Google, - OpenIdAuthProvider::GitHub => AuthProvider::GitHub, + OpenIdDelegationProvider::Google => AuthProvider::Google, + OpenIdDelegationProvider::GitHub => AuthProvider::GitHub, } } } From 9caae395c64fbd7a4e4b72f82cee35b455f548cc Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 10:58:15 +0100 Subject: [PATCH 11/64] fea: rename better meaning --- src/console/src/accounts/impls.rs | 2 +- src/console/src/auth/delegation.rs | 34 ++++++++++--------- src/console/src/auth/register.rs | 4 +-- src/console/src/types.rs | 2 +- src/libs/auth/src/delegation/get.rs | 4 +-- src/libs/auth/src/delegation/impls.rs | 2 +- src/libs/auth/src/delegation/prepare.rs | 4 +-- .../auth/src/delegation/utils/duration.rs | 2 +- src/libs/auth/src/delegation/utils/seed.rs | 4 +-- src/libs/auth/src/delegation/utils/targets.rs | 2 +- .../openid/{workload => automation}/mod.rs | 0 .../openid/{workload => automation}/types.rs | 2 +- .../openid/{workload => automation}/verify.rs | 12 +++---- .../src/openid/{user => delegation}/impls.rs | 4 +-- .../src/openid/{user => delegation}/mod.rs | 0 .../src/openid/{user => delegation}/types.rs | 0 .../src/openid/{user => delegation}/verify.rs | 14 ++++---- src/libs/auth/src/openid/jwt/provider.rs | 4 +-- src/libs/auth/src/openid/mod.rs | 4 +-- src/libs/auth/src/state/types.rs | 2 +- src/libs/satellite/src/auth/delegation.rs | 34 ++++++++++--------- src/libs/satellite/src/auth/register.rs | 4 +-- .../satellite/src/controllers/authenticate.rs | 2 +- .../satellite/src/controllers/constants.rs | 2 +- src/libs/satellite/src/controllers/types.rs | 4 +-- src/libs/satellite/src/user/core/impls.rs | 4 +-- 26 files changed, 78 insertions(+), 74 deletions(-) rename src/libs/auth/src/openid/{workload => automation}/mod.rs (100%) rename src/libs/auth/src/openid/{workload => automation}/types.rs (87%) rename src/libs/auth/src/openid/{workload => automation}/verify.rs (77%) rename src/libs/auth/src/openid/{user => delegation}/impls.rs (92%) rename src/libs/auth/src/openid/{user => delegation}/mod.rs (100%) rename src/libs/auth/src/openid/{user => delegation}/types.rs (100%) rename src/libs/auth/src/openid/{user => delegation}/verify.rs (87%) diff --git a/src/console/src/accounts/impls.rs b/src/console/src/accounts/impls.rs index b006f6da2e..516dac591f 100644 --- a/src/console/src/accounts/impls.rs +++ b/src/console/src/accounts/impls.rs @@ -1,7 +1,7 @@ use crate::constants::E8S_PER_ICP; use crate::types::state::{Account, OpenIdData, Provider}; use ic_cdk::api::time; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; use junobuild_auth::profile::types::OpenIdProfile; use junobuild_shared::types::state::{MissionControlId, UserId}; diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index 5867de2d03..b259366fce 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -4,8 +4,8 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::config::OpenIdAuthProviders; use junobuild_auth::{delegation, openid}; @@ -22,14 +22,15 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( - &args.jwt, &args.salt, providers, &AuthHeap, - ) - .await - { - Ok(value) => value, - Err(err) => return Err(PrepareDelegationError::from(err)), - }; + let (credential, provider) = + match openid::delegation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, &args.salt, providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareDelegationError::from(err)), + }; let result = delegation::openid_prepare_delegation( &args.session_key, @@ -46,12 +47,13 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdAuthProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( - &args.jwt, &args.salt, providers, &AuthHeap, - ) { - Ok(value) => value, - Err(err) => return Err(GetDelegationError::from(err)), - }; + let (credential, provider) = + match openid::delegation::verify_openid_credentials_with_cached_jwks( + &args.jwt, &args.salt, providers, &AuthHeap, + ) { + Ok(value) => value, + Err(err) => return Err(GetDelegationError::from(err)), + }; delegation::openid_get_delegation( &args.session_key, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 6429ff7261..4da81b00c6 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -3,8 +3,8 @@ use crate::types::state::OpenId; use crate::types::state::{Account, OpenIdData, Provider}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; pub async fn register_account( public_key: &UserKey, diff --git a/src/console/src/types.rs b/src/console/src/types.rs index 5f53fb0d6d..70eb514de2 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -4,7 +4,7 @@ pub mod state { use candid::CandidType; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::StableBTreeMap; - use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; + use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::state::AuthenticationHeapState; use junobuild_cdn::proposals::{ProposalsStable, SegmentDeploymentVersion}; use junobuild_cdn::storage::{ProposalAssetsStable, ProposalContentChunksStable}; diff --git a/src/libs/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 9a530b9c99..2117577734 100644 --- a/src/libs/auth/src/delegation/get.rs +++ b/src/libs/auth/src/delegation/get.rs @@ -4,8 +4,8 @@ use crate::delegation::types::{ use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::user::types::provider::OpenIdDelegationProvider; +use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::read_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/impls.rs b/src/libs/auth/src/delegation/impls.rs index 564bee5d9a..dbdfc2a9ec 100644 --- a/src/libs/auth/src/delegation/impls.rs +++ b/src/libs/auth/src/delegation/impls.rs @@ -1,5 +1,5 @@ use crate::delegation::types::{GetDelegationError, PrepareDelegationError}; -use crate::openid::user::types::errors::VerifyOpenidCredentialsError; +use crate::openid::delegation::types::errors::VerifyOpenidCredentialsError; impl From for GetDelegationError { fn from(e: VerifyOpenidCredentialsError) -> Self { diff --git a/src/libs/auth/src/delegation/prepare.rs b/src/libs/auth/src/delegation/prepare.rs index e277c3c989..6889ba555f 100644 --- a/src/libs/auth/src/delegation/prepare.rs +++ b/src/libs/auth/src/delegation/prepare.rs @@ -6,8 +6,8 @@ use crate::delegation::utils::duration::build_expiration; use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::user::types::provider::OpenIdDelegationProvider; +use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::mutate_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/utils/duration.rs b/src/libs/auth/src/delegation/utils/duration.rs index f3a9ed56a3..7a7ca03d3a 100644 --- a/src/libs/auth/src/delegation/utils/duration.rs +++ b/src/libs/auth/src/delegation/utils/duration.rs @@ -1,5 +1,5 @@ use crate::delegation::constants::{DEFAULT_EXPIRATION_PERIOD_NS, MAX_EXPIRATION_PERIOD_NS}; -use crate::openid::user::types::provider::OpenIdDelegationProvider; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use ic_cdk::api::time; diff --git a/src/libs/auth/src/delegation/utils/seed.rs b/src/libs/auth/src/delegation/utils/seed.rs index 6efd35777b..3911f4dbfc 100644 --- a/src/libs/auth/src/delegation/utils/seed.rs +++ b/src/libs/auth/src/delegation/utils/seed.rs @@ -1,4 +1,4 @@ -use crate::openid::user::types::interface::OpenIdCredentialKey; +use crate::openid::delegation::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; @@ -30,7 +30,7 @@ fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { #[cfg(test)] mod tests { use super::calculate_seed; - use crate::openid::user::types::interface::OpenIdCredentialKey; + use crate::openid::delegation::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; diff --git a/src/libs/auth/src/delegation/utils/targets.rs b/src/libs/auth/src/delegation/utils/targets.rs index 8eb7eee257..7704204e0f 100644 --- a/src/libs/auth/src/delegation/utils/targets.rs +++ b/src/libs/auth/src/delegation/utils/targets.rs @@ -1,5 +1,5 @@ use crate::delegation::types::DelegationTargets; -use crate::openid::user::types::provider::OpenIdDelegationProvider; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::id; diff --git a/src/libs/auth/src/openid/workload/mod.rs b/src/libs/auth/src/openid/automation/mod.rs similarity index 100% rename from src/libs/auth/src/openid/workload/mod.rs rename to src/libs/auth/src/openid/automation/mod.rs diff --git a/src/libs/auth/src/openid/workload/types.rs b/src/libs/auth/src/openid/automation/types.rs similarity index 87% rename from src/libs/auth/src/openid/workload/types.rs rename to src/libs/auth/src/openid/automation/types.rs index 1f7306ef36..9fd4d03ff1 100644 --- a/src/libs/auth/src/openid/workload/types.rs +++ b/src/libs/auth/src/openid/automation/types.rs @@ -5,7 +5,7 @@ pub mod errors { use serde::Serialize; #[derive(CandidType, Serialize, Deserialize, Debug)] - pub enum VerifyOpenidWorkloadCredentialsError { + pub enum VerifyOpenidAutomationCredentialsError { GetOrFetchJwks(GetOrRefreshJwksError), GetCachedJwks, JwtVerify(JwtVerifyError), diff --git a/src/libs/auth/src/openid/workload/verify.rs b/src/libs/auth/src/openid/automation/verify.rs similarity index 77% rename from src/libs/auth/src/openid/workload/verify.rs rename to src/libs/auth/src/openid/automation/verify.rs index 64eaeb86dc..20236f718c 100644 --- a/src/libs/auth/src/openid/workload/verify.rs +++ b/src/libs/auth/src/openid/automation/verify.rs @@ -1,22 +1,22 @@ +use crate::openid::automation::types::errors::VerifyOpenidAutomationCredentialsError; use crate::openid::jwkset::get_or_refresh_jwks; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::verify_openid_jwt; use crate::openid::types::provider::OpenIdProvider; -use crate::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdWorkloadCredentialsResult = Result<(), VerifyOpenidWorkloadCredentialsError>; +type VerifyOpenIdAutomationCredentialsResult = Result<(), VerifyOpenidAutomationCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, provider: &OpenIdProvider, auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdWorkloadCredentialsResult { +) -> VerifyOpenIdAutomationCredentialsResult { let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) .await - .map_err(VerifyOpenidWorkloadCredentialsError::GetOrFetchJwks)?; + .map_err(VerifyOpenidAutomationCredentialsError::GetOrFetchJwks)?; verify_openid_credentials(jwt, &jwks, &provider) } @@ -25,7 +25,7 @@ fn verify_openid_credentials( jwt: &str, jwks: &Jwks, provider: &OpenIdProvider, -) -> VerifyOpenIdWorkloadCredentialsResult { +) -> VerifyOpenIdAutomationCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { // if claims.aud != client_id.as_str() { // return Err(JwtVerifyError::BadClaim("aud".to_string())); @@ -55,7 +55,7 @@ fn verify_openid_credentials( &assert_audience, &assert_no_replay, ) - .map_err(VerifyOpenidWorkloadCredentialsError::JwtVerify)?; + .map_err(VerifyOpenidAutomationCredentialsError::JwtVerify)?; Ok(()) } diff --git a/src/libs/auth/src/openid/user/impls.rs b/src/libs/auth/src/openid/delegation/impls.rs similarity index 92% rename from src/libs/auth/src/openid/user/impls.rs rename to src/libs/auth/src/openid/delegation/impls.rs index 7196eff2e5..e42041d995 100644 --- a/src/libs/auth/src/openid/user/impls.rs +++ b/src/libs/auth/src/openid/delegation/impls.rs @@ -1,7 +1,7 @@ +use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::types::token::Claims; use crate::openid::types::provider::OpenIdProvider; -use crate::openid::user::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::user::types::provider::OpenIdDelegationProvider; use jsonwebtoken::TokenData; impl From> for OpenIdCredential { diff --git a/src/libs/auth/src/openid/user/mod.rs b/src/libs/auth/src/openid/delegation/mod.rs similarity index 100% rename from src/libs/auth/src/openid/user/mod.rs rename to src/libs/auth/src/openid/delegation/mod.rs diff --git a/src/libs/auth/src/openid/user/types.rs b/src/libs/auth/src/openid/delegation/types.rs similarity index 100% rename from src/libs/auth/src/openid/user/types.rs rename to src/libs/auth/src/openid/delegation/types.rs diff --git a/src/libs/auth/src/openid/user/verify.rs b/src/libs/auth/src/openid/delegation/verify.rs similarity index 87% rename from src/libs/auth/src/openid/user/verify.rs rename to src/libs/auth/src/openid/delegation/verify.rs index fa97648e22..1078079e4d 100644 --- a/src/libs/auth/src/openid/user/verify.rs +++ b/src/libs/auth/src/openid/delegation/verify.rs @@ -1,18 +1,18 @@ +use crate::openid::delegation::types::errors::VerifyOpenidCredentialsError; +use crate::openid::delegation::types::interface::OpenIdCredential; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_auth_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdProvider; -use crate::openid::user::types::errors::VerifyOpenidCredentialsError; -use crate::openid::user::types::interface::OpenIdCredential; -use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::openid::utils::build_nonce; use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdCredentialsResult = +type VerifyOpenIdDelegationCredentialsResult = Result<(OpenIdCredential, OpenIdDelegationProvider), VerifyOpenidCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( @@ -20,7 +20,7 @@ pub async fn verify_openid_credentials_with_jwks_renewal( salt: &Salt, providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { +) -> VerifyOpenIdDelegationCredentialsResult { let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -38,7 +38,7 @@ pub fn verify_openid_credentials_with_cached_jwks( salt: &Salt, providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { +) -> VerifyOpenIdDelegationCredentialsResult { let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; @@ -55,7 +55,7 @@ fn verify_openid_credentials( provider: &OpenIdDelegationProvider, client_id: &OpenIdAuthProviderClientId, salt: &Salt, -) -> VerifyOpenIdCredentialsResult { +) -> VerifyOpenIdDelegationCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { if claims.aud != client_id.as_str() { return Err(JwtVerifyError::BadClaim("aud".to_string())); diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 72a387d569..0890733fa0 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,7 +1,7 @@ +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::jwt::types::token::UnsafeClaims; -use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::types::config::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; use jsonwebtoken::dangerous; @@ -34,8 +34,8 @@ pub fn unsafe_find_jwt_auth_provider<'a>( #[cfg(test)] mod tests { use super::unsafe_find_jwt_auth_provider; + use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::types::errors::JwtFindProviderError; - use crate::openid::user::types::provider::OpenIdDelegationProvider; use crate::state::types::config::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index c31a2a0561..e962e11ab5 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -1,7 +1,7 @@ +pub mod automation; +pub mod delegation; mod impls; pub mod jwkset; pub mod jwt; pub mod types; -pub mod user; mod utils; -pub mod workload; diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index f8db817b2b..f2f1058669 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -53,7 +53,7 @@ pub(crate) mod runtime_state { pub mod config { use crate::delegation::types::DelegationTargets; - use crate::openid::user::types::provider::OpenIdDelegationProvider; + use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use candid::{CandidType, Deserialize, Principal}; use junobuild_shared::types::core::DomainName; use junobuild_shared::types::state::{Timestamp, Version}; diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index 5867de2d03..b259366fce 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -4,8 +4,8 @@ use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::config::OpenIdAuthProviders; use junobuild_auth::{delegation, openid}; @@ -22,14 +22,15 @@ pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::user::verify_openid_credentials_with_jwks_renewal( - &args.jwt, &args.salt, providers, &AuthHeap, - ) - .await - { - Ok(value) => value, - Err(err) => return Err(PrepareDelegationError::from(err)), - }; + let (credential, provider) = + match openid::delegation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, &args.salt, providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareDelegationError::from(err)), + }; let result = delegation::openid_prepare_delegation( &args.session_key, @@ -46,12 +47,13 @@ pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, providers: &OpenIdAuthProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::user::verify_openid_credentials_with_cached_jwks( - &args.jwt, &args.salt, providers, &AuthHeap, - ) { - Ok(value) => value, - Err(err) => return Err(GetDelegationError::from(err)), - }; + let (credential, provider) = + match openid::delegation::verify_openid_credentials_with_cached_jwks( + &args.jwt, &args.salt, providers, &AuthHeap, + ) { + Ok(value) => value, + Err(err) => return Err(GetDelegationError::from(err)), + }; delegation::openid_get_delegation( &args.session_key, diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index 308d577780..2e1742ec76 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -7,8 +7,8 @@ use crate::user::core::types::state::{OpenIdData, ProviderData, UserData}; use crate::Doc; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_collections::constants::db::COLLECTION_USER_KEY; use junobuild_collections::msg::msg_db_collection_not_found; use junobuild_shared::ic::api::id; diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index 1c70614da9..a182106a73 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -16,7 +16,7 @@ use std::cmp::min; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, ) -> OpenIdAuthenticateControllerResult { - match openid::workload::verify_openid_credentials_with_jwks_renewal( + match openid::automation::verify_openid_credentials_with_jwks_renewal( &args.jwt, &OpenIdProvider::GitHubActions, &AuthHeap, diff --git a/src/libs/satellite/src/controllers/constants.rs b/src/libs/satellite/src/controllers/constants.rs index 5e2a474a40..7db3c782bc 100644 --- a/src/libs/satellite/src/controllers/constants.rs +++ b/src/libs/satellite/src/controllers/constants.rs @@ -3,5 +3,5 @@ const MINUTE_NS: u64 = 60 * 1_000_000_000; // 10 minutes in nanoseconds pub const DEFAULT_CONTROLLER_DURATION_NS: u64 = 10 * MINUTE_NS; -// The maximum duration for a workload controller +// The maximum duration for a automation controller pub const MAX_CONTROLLER_DURATION_NS: u64 = 60 * MINUTE_NS; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index a5877b710f..59b069cb53 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,5 +1,5 @@ use candid::{CandidType, Deserialize}; -use junobuild_auth::openid::workload::types::errors::VerifyOpenidWorkloadCredentialsError; +use junobuild_auth::openid::automation::types::errors::VerifyOpenidAutomationCredentialsError; use junobuild_shared::types::state::{ControllerId, Metadata}; use serde::Serialize; @@ -25,7 +25,7 @@ pub enum GrantableScope { #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticateControllerError { - VerifyOpenIdCredentials(VerifyOpenidWorkloadCredentialsError), + VerifyOpenIdCredentials(VerifyOpenidAutomationCredentialsError), RegisterController(String), } diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index 4c9d374c56..e6c3b32c9f 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -9,9 +9,9 @@ use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; use crate::{Doc, SetDoc}; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::openid::types::provider::OpenIdProvider; -use junobuild_auth::openid::user::types::interface::OpenIdCredential; -use junobuild_auth::openid::user::types::provider::OpenIdDelegationProvider; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; use junobuild_utils::encode_doc_data; From 660c09dd2731c232fa499a36eeeeb35638dfc0e2 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 11:20:10 +0100 Subject: [PATCH 12/64] feat: expose --- src/libs/auth/src/openid/delegation/types.rs | 3 --- src/libs/satellite/src/api/controllers.rs | 4 ++-- .../satellite/src/controllers/authenticate.rs | 9 ++++----- src/libs/satellite/src/controllers/types.rs | 4 ++-- src/libs/satellite/src/impls.rs | 13 ++++++++++++- src/libs/satellite/src/lib.rs | 17 ++++++++++++----- src/libs/satellite/src/types.rs | 7 +++++++ 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/libs/auth/src/openid/delegation/types.rs b/src/libs/auth/src/openid/delegation/types.rs index 5af20ee0a9..8646d806e3 100644 --- a/src/libs/auth/src/openid/delegation/types.rs +++ b/src/libs/auth/src/openid/delegation/types.rs @@ -1,6 +1,3 @@ -use candid::{CandidType, Deserialize}; -use serde::Serialize; - pub mod interface { pub struct OpenIdCredentialKey<'a> { pub iss: &'a String, diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index 916acb9bbe..76a2e049c2 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -1,6 +1,6 @@ use crate::controllers::openid_authenticate_controller; use crate::controllers::store::{delete_controllers, set_controllers as set_controllers_store}; -use crate::controllers::types::{AuthenticateControllerArgs, OpenIdAuthenticateControllerResult}; +use crate::controllers::types::{AuthenticateControllerArgs, AuthenticateControllerResult}; use crate::{get_admin_controllers, get_controllers}; use ic_cdk::trap; use junobuild_shared::constants::shared::MAX_NUMBER_OF_SATELLITE_CONTROLLERS; @@ -54,7 +54,7 @@ pub fn list_controllers() -> Controllers { pub async fn authenticate_controller( args: AuthenticateControllerArgs, -) -> OpenIdAuthenticateControllerResult { +) -> AuthenticateControllerResult { match args { AuthenticateControllerArgs::OpenId(args) => openid_authenticate_controller(&args).await, } diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index a182106a73..0b4e8e8af1 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -2,8 +2,7 @@ use crate::auth::strategy_impls::AuthHeap; use crate::controllers::constants::{DEFAULT_CONTROLLER_DURATION_NS, MAX_CONTROLLER_DURATION_NS}; use crate::controllers::store::set_controllers; use crate::controllers::types::{ - AuthenticateControllerError, OpenIdAuthenticateControllerArgs, - OpenIdAuthenticateControllerResult, + AuthenticateControllerResult, AuthenticationControllerError, OpenIdAuthenticateControllerArgs, }; use ic_cdk::api::time; use junobuild_auth::openid; @@ -15,7 +14,7 @@ use std::cmp::min; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, -) -> OpenIdAuthenticateControllerResult { +) -> AuthenticateControllerResult { match openid::automation::verify_openid_credentials_with_jwks_renewal( &args.jwt, &OpenIdProvider::GitHubActions, @@ -24,8 +23,8 @@ pub async fn openid_authenticate_controller( .await { Ok(_) => register_controller(args) - .map_err(|err| AuthenticateControllerError::RegisterController(err.to_string())), - Err(err) => Err(AuthenticateControllerError::VerifyOpenIdCredentials(err)), + .map_err(|err| AuthenticationControllerError::RegisterController(err.to_string())), + Err(err) => Err(AuthenticationControllerError::VerifyOpenIdCredentials(err)), } } diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index 59b069cb53..65805bf8c8 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -24,9 +24,9 @@ pub enum GrantableScope { } #[derive(CandidType, Serialize, Deserialize)] -pub enum AuthenticateControllerError { +pub enum AuthenticationControllerError { VerifyOpenIdCredentials(VerifyOpenidAutomationCredentialsError), RegisterController(String), } -pub type OpenIdAuthenticateControllerResult = Result<(), AuthenticateControllerError>; +pub type AuthenticateControllerResult = Result<(), AuthenticationControllerError>; diff --git a/src/libs/satellite/src/impls.rs b/src/libs/satellite/src/impls.rs index 0b31f105fd..c66f4fd33a 100644 --- a/src/libs/satellite/src/impls.rs +++ b/src/libs/satellite/src/impls.rs @@ -1,6 +1,8 @@ +use crate::controllers::types::AuthenticateControllerResult; use crate::memory::internal::init_stable_state; use crate::types::interface::{ - AuthenticateResultResponse, AuthenticationResult, GetDelegationResultResponse, + AuthenticateControllerResultResponse, AuthenticateResultResponse, AuthenticationResult, + GetDelegationResultResponse, }; use crate::types::state::{CollectionType, HeapState, RuntimeState, State}; use junobuild_auth::delegation::types::{GetDelegationError, SignedDelegation}; @@ -46,3 +48,12 @@ impl From for AuthenticateResultResponse { } } } + +impl From for AuthenticateControllerResultResponse { + fn from(r: AuthenticateControllerResult) -> Self { + match r { + Ok(v) => Self::Ok(v), + Err(e) => Self::Err(e), + } + } +} diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index d4ecbc8d3a..5afd708209 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -23,8 +23,8 @@ use crate::guards::{ caller_is_admin_controller, caller_is_controller, caller_is_controller_with_write, }; use crate::types::interface::{ - AuthenticateResultResponse, AuthenticationArgs, Config, DeleteProposalAssets, - GetDelegationArgs, GetDelegationResultResponse, + AuthenticateControllerResultResponse, AuthenticateResultResponse, AuthenticationArgs, Config, + DeleteProposalAssets, GetDelegationArgs, GetDelegationResultResponse, }; use crate::types::state::CollectionType; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; @@ -63,11 +63,11 @@ use memory::lifecycle; // ============================================================================================ // These types are made available for use in Serverless Functions. // ============================================================================================ +use crate::controllers::types::AuthenticateControllerArgs; use crate::db::types::interface::SetDbConfig; use junobuild_auth::state::types::interface::SetAuthenticationConfig; pub use sdk::core::*; pub use sdk::internal; - // --------------------------------------------------------- // Init and Upgrade // --------------------------------------------------------- @@ -232,6 +232,13 @@ pub fn list_controllers() -> Controllers { api::controllers::list_controllers() } +#[doc(hidden)] +pub async fn authenticate_controller( + args: AuthenticateControllerArgs, +) -> AuthenticateControllerResultResponse { + api::controllers::authenticate_controller(args).await.into() +} + // --------------------------------------------------------- // Proposal // --------------------------------------------------------- @@ -555,7 +562,7 @@ macro_rules! include_satellite { set_storage_config, submit_proposal, switch_storage_system_memory, upload_asset_chunk, upload_proposal_asset_chunk, }; - - ic_cdk::export_candid!(); }; } + +ic_cdk::export_candid!(); diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index cfcc94b79e..edc724250b 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -56,6 +56,7 @@ pub mod state { } pub mod interface { + use crate::controllers::types::AuthenticationControllerError; use crate::db::types::config::DbConfig; use crate::Doc; use candid::CandidType; @@ -118,6 +119,12 @@ pub mod interface { Ok(SignedDelegation), Err(GetDelegationError), } + + #[derive(CandidType, Serialize, Deserialize)] + pub enum AuthenticateControllerResultResponse { + Ok(()), + Err(AuthenticationControllerError), + } } pub mod store { From 8b8a74bf1bbfb9d69c42e724a70d825ba8b1382e Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 11:21:29 +0100 Subject: [PATCH 13/64] chore: redo include --- src/libs/satellite/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 5afd708209..c57fcabe29 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -562,7 +562,7 @@ macro_rules! include_satellite { set_storage_config, submit_proposal, switch_storage_system_memory, upload_asset_chunk, upload_proposal_asset_chunk, }; + + ic_cdk::export_candid!(); }; } - -ic_cdk::export_candid!(); From 4706433ba7e98629eb7d5dd5deb4694033da4514 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 11:36:41 +0100 Subject: [PATCH 14/64] feat: generate did --- src/console/console.did | 19 ++++---- src/declarations/console/console.did.d.ts | 19 ++++---- .../console/console.factory.certified.did.js | 17 +++---- .../console/console.factory.did.js | 17 +++---- .../console/console.factory.did.mjs | 17 +++---- src/declarations/satellite/satellite.did.d.ts | 43 +++++++++++++---- .../satellite.factory.certified.did.js | 47 +++++++++++++++---- .../satellite/satellite.factory.did.js | 47 +++++++++++++++---- .../satellite/satellite.factory.did.mjs | 47 +++++++++++++++---- src/declarations/sputnik/sputnik.did.d.ts | 43 +++++++++++++---- .../sputnik/sputnik.factory.certified.did.js | 47 +++++++++++++++---- .../sputnik/sputnik.factory.did.js | 47 +++++++++++++++---- src/libs/satellite/satellite.did | 43 +++++++++++++---- src/libs/satellite/src/controllers/impls.rs | 10 ++-- src/libs/satellite/src/controllers/types.rs | 4 +- src/libs/satellite/src/lib.rs | 1 + src/libs/satellite/src/user/core/impls.rs | 2 +- src/satellite/satellite.did | 43 +++++++++++++---- src/sputnik/sputnik.did | 43 +++++++++++++---- .../test_satellite/test_satellite.did.d.ts | 43 +++++++++++++---- .../test_satellite.factory.certified.did.js | 47 +++++++++++++++---- .../test_satellite.factory.did.js | 47 +++++++++++++++---- .../test_satellite/test_satellite.did | 43 +++++++++++++---- 23 files changed, 563 insertions(+), 173 deletions(-) diff --git a/src/console/console.did b/src/console/console.did index b43c903d61..5ea54ba7b9 100644 --- a/src/console/console.did +++ b/src/console/console.did @@ -52,7 +52,7 @@ type AuthenticationConfigInternetIdentity = record { }; type AuthenticationConfigOpenId = record { observatory_id : opt principal; - providers : vec record { OpenIdProvider; OpenIdProviderConfig }; + providers : vec record { OpenIdDelegationProvider; OpenIdProviderAuthConfig }; }; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; @@ -223,8 +223,11 @@ type ListSegmentsArgs = record { segment_kind : opt StorableSegmentKind; }; type Memory = variant { Heap; Stable }; -type OpenId = record { provider : OpenIdAuthProvider; data : OpenIdData }; -type OpenIdAuthProvider = variant { GitHub; Google }; +type OpenId = record { provider : OpenIdDelegationProvider; data : OpenIdData }; +type OpenIdAuthProviderDelegationConfig = record { + targets : opt vec principal; + max_time_to_live : opt nat64; +}; type OpenIdData = record { name : opt text; locale : opt text; @@ -234,6 +237,7 @@ type OpenIdData = record { given_name : opt text; preferred_username : opt text; }; +type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; session_key : blob; @@ -245,15 +249,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; -type OpenIdProviderConfig = record { - delegation : opt OpenIdProviderDelegationConfig; +type OpenIdProviderAuthConfig = record { + delegation : opt OpenIdAuthProviderDelegationConfig; client_id : text; }; -type OpenIdProviderDelegationConfig = record { - targets : opt vec principal; - max_time_to_live : opt nat64; -}; type PaymentStatus = variant { Refunded; Acknowledged; Completed }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; diff --git a/src/declarations/console/console.did.d.ts b/src/declarations/console/console.did.d.ts index 88cdde866c..9a63b818c6 100644 --- a/src/declarations/console/console.did.d.ts +++ b/src/declarations/console/console.did.d.ts @@ -69,7 +69,7 @@ export interface AuthenticationConfigInternetIdentity { } export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; - providers: Array<[OpenIdProvider, OpenIdProviderConfig]>; + providers: Array<[OpenIdDelegationProvider, OpenIdProviderAuthConfig]>; } export type AuthenticationError = | { @@ -277,10 +277,13 @@ export interface ListSegmentsArgs { } export type Memory = { Heap: null } | { Stable: null }; export interface OpenId { - provider: OpenIdAuthProvider; + provider: OpenIdDelegationProvider; data: OpenIdData; } -export type OpenIdAuthProvider = { GitHub: null } | { Google: null }; +export interface OpenIdAuthProviderDelegationConfig { + targets: [] | [Array]; + max_time_to_live: [] | [bigint]; +} export interface OpenIdData { name: [] | [string]; locale: [] | [string]; @@ -290,6 +293,7 @@ export interface OpenIdData { given_name: [] | [string]; preferred_username: [] | [string]; } +export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; session_key: Uint8Array; @@ -301,15 +305,10 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; -export interface OpenIdProviderConfig { - delegation: [] | [OpenIdProviderDelegationConfig]; +export interface OpenIdProviderAuthConfig { + delegation: [] | [OpenIdAuthProviderDelegationConfig]; client_id: string; } -export interface OpenIdProviderDelegationConfig { - targets: [] | [Array]; - max_time_to_live: [] | [bigint]; -} export type PaymentStatus = { Refunded: null } | { Acknowledged: null } | { Completed: null }; export type PrepareDelegationError = | { diff --git a/src/declarations/console/console.factory.certified.did.js b/src/declarations/console/console.factory.certified.did.js index 3b28c5d410..f2f72f1182 100644 --- a/src/declarations/console/console.factory.certified.did.js +++ b/src/declarations/console/console.factory.certified.did.js @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdAuthProvider = IDL.Variant({ + const OpenIdDelegationProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdAuthProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,22 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null - }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), diff --git a/src/declarations/console/console.factory.did.js b/src/declarations/console/console.factory.did.js index 7c2917ef44..1fe980de44 100644 --- a/src/declarations/console/console.factory.did.js +++ b/src/declarations/console/console.factory.did.js @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdAuthProvider = IDL.Variant({ + const OpenIdDelegationProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdAuthProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,22 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null - }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), diff --git a/src/declarations/console/console.factory.did.mjs b/src/declarations/console/console.factory.did.mjs index 7c2917ef44..1fe980de44 100644 --- a/src/declarations/console/console.factory.did.mjs +++ b/src/declarations/console/console.factory.did.mjs @@ -24,7 +24,7 @@ export const idlFactory = ({ IDL }) => { user_key: IDL.Vec(IDL.Nat8), expiration: IDL.Nat64 }); - const OpenIdAuthProvider = IDL.Variant({ + const OpenIdDelegationProvider = IDL.Variant({ GitHub: IDL.Null, Google: IDL.Null }); @@ -38,7 +38,7 @@ export const idlFactory = ({ IDL }) => { preferred_username: IDL.Opt(IDL.Text) }); const OpenId = IDL.Record({ - provider: OpenIdAuthProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,22 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null - }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index a51b94415b..db58076314 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -34,6 +34,12 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } +export type AuthenticateControllerArgs = { + OpenId: OpenIdAuthenticateControllerArgs; +}; +export type AuthenticateControllerResultResponse = + | { Ok: null } + | { Err: AuthenticationControllerError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; @@ -54,8 +60,11 @@ export interface AuthenticationConfigInternetIdentity { } export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; - providers: Array<[OpenIdProvider, OpenIdProviderConfig]>; + providers: Array<[OpenIdDelegationProvider, OpenIdProviderAuthConfig]>; } +export type AuthenticationControllerError = + | { RegisterController: string } + | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -64,6 +73,7 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -259,6 +269,18 @@ export interface MemorySize { stable: bigint; heap: bigint; } +export interface OpenIdAuthProviderDelegationConfig { + targets: [] | [Array]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAuthenticateControllerArgs { + jwt: string; + metadata: Array<[string, string]>; + scope: AutomationScope; + max_time_to_live: [] | [bigint]; + controller_id: Principal; +} +export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; session_key: Uint8Array; @@ -270,15 +292,10 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; -export interface OpenIdProviderConfig { - delegation: [] | [OpenIdProviderDelegationConfig]; +export interface OpenIdProviderAuthConfig { + delegation: [] | [OpenIdAuthProviderDelegationConfig]; client_id: string; } -export interface OpenIdProviderDelegationConfig { - targets: [] | [Array]; - max_time_to_live: [] | [bigint]; -} export type Permission = | { Controllers: null } | { Private: null } @@ -438,8 +455,18 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } +export type VerifyOpenidAutomationCredentialsError = + | { + GetCachedJwks: null; + } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; + authenticate_controller: ActorMethod< + [AuthenticateControllerArgs], + AuthenticateControllerResultResponse + >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; commit_proposal_asset_upload: ActorMethod<[CommitBatch], undefined>; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 00743eabe6..c43568c316 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -447,6 +473,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 0161a96157..62bff55282 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -447,6 +473,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 0161a96157..62bff55282 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -447,6 +473,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index a51b94415b..db58076314 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -34,6 +34,12 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } +export type AuthenticateControllerArgs = { + OpenId: OpenIdAuthenticateControllerArgs; +}; +export type AuthenticateControllerResultResponse = + | { Ok: null } + | { Err: AuthenticationControllerError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; @@ -54,8 +60,11 @@ export interface AuthenticationConfigInternetIdentity { } export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; - providers: Array<[OpenIdProvider, OpenIdProviderConfig]>; + providers: Array<[OpenIdDelegationProvider, OpenIdProviderAuthConfig]>; } +export type AuthenticationControllerError = + | { RegisterController: string } + | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -64,6 +73,7 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -259,6 +269,18 @@ export interface MemorySize { stable: bigint; heap: bigint; } +export interface OpenIdAuthProviderDelegationConfig { + targets: [] | [Array]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAuthenticateControllerArgs { + jwt: string; + metadata: Array<[string, string]>; + scope: AutomationScope; + max_time_to_live: [] | [bigint]; + controller_id: Principal; +} +export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; session_key: Uint8Array; @@ -270,15 +292,10 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; -export interface OpenIdProviderConfig { - delegation: [] | [OpenIdProviderDelegationConfig]; +export interface OpenIdProviderAuthConfig { + delegation: [] | [OpenIdAuthProviderDelegationConfig]; client_id: string; } -export interface OpenIdProviderDelegationConfig { - targets: [] | [Array]; - max_time_to_live: [] | [bigint]; -} export type Permission = | { Controllers: null } | { Private: null } @@ -438,8 +455,18 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } +export type VerifyOpenidAutomationCredentialsError = + | { + GetCachedJwks: null; + } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; + authenticate_controller: ActorMethod< + [AuthenticateControllerArgs], + AuthenticateControllerResultResponse + >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; commit_proposal_asset_upload: ActorMethod<[CommitBatch], undefined>; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index 00743eabe6..c43568c316 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -447,6 +473,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index 0161a96157..62bff55282 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -447,6 +473,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 363df45340..4044aee380 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -20,6 +20,13 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; +type AuthenticateControllerArgs = variant { + OpenId : OpenIdAuthenticateControllerArgs; +}; +type AuthenticateControllerResultResponse = variant { + Ok; + Err : AuthenticationControllerError; +}; type AuthenticateResultResponse = variant { Ok : Authentication; Err : AuthenticationError; @@ -40,13 +47,18 @@ type AuthenticationConfigInternetIdentity = record { }; type AuthenticationConfigOpenId = record { observatory_id : opt principal; - providers : vec record { OpenIdProvider; OpenIdProviderConfig }; + providers : vec record { OpenIdDelegationProvider; OpenIdProviderAuthConfig }; +}; +type AuthenticationControllerError = variant { + RegisterController : text; + VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; }; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -210,6 +222,18 @@ type ListRulesResults = record { }; type Memory = variant { Heap; Stable }; type MemorySize = record { stable : nat64; heap : nat64 }; +type OpenIdAuthProviderDelegationConfig = record { + targets : opt vec principal; + max_time_to_live : opt nat64; +}; +type OpenIdAuthenticateControllerArgs = record { + jwt : text; + metadata : vec record { text; text }; + scope : AutomationScope; + max_time_to_live : opt nat64; + controller_id : principal; +}; +type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; session_key : blob; @@ -221,15 +245,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; -type OpenIdProviderConfig = record { - delegation : opt OpenIdProviderDelegationConfig; +type OpenIdProviderAuthConfig = record { + delegation : opt OpenIdAuthProviderDelegationConfig; client_id : text; }; -type OpenIdProviderDelegationConfig = record { - targets : opt vec principal; - max_time_to_live : opt nat64; -}; type Permission = variant { Controllers; Private; Public; Managed }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -371,8 +390,16 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type VerifyOpenidAutomationCredentialsError = variant { + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); + authenticate_controller : (AuthenticateControllerArgs) -> ( + AuthenticateControllerResultResponse, + ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); commit_proposal_asset_upload : (CommitBatch) -> (); diff --git a/src/libs/satellite/src/controllers/impls.rs b/src/libs/satellite/src/controllers/impls.rs index cc82bf1169..d588dca7bc 100644 --- a/src/libs/satellite/src/controllers/impls.rs +++ b/src/libs/satellite/src/controllers/impls.rs @@ -1,11 +1,11 @@ -use crate::controllers::types::GrantableScope; +use crate::controllers::types::AutomationScope; use junobuild_shared::types::state::ControllerScope; -impl From for ControllerScope { - fn from(scope: GrantableScope) -> Self { +impl From for ControllerScope { + fn from(scope: AutomationScope) -> Self { match scope { - GrantableScope::Write => ControllerScope::Write, - GrantableScope::Submit => ControllerScope::Submit, + AutomationScope::Write => ControllerScope::Write, + AutomationScope::Submit => ControllerScope::Submit, } } } diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index 65805bf8c8..5f8c4def76 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -12,13 +12,13 @@ pub enum AuthenticateControllerArgs { pub struct OpenIdAuthenticateControllerArgs { pub jwt: String, pub controller_id: ControllerId, - pub scope: GrantableScope, + pub scope: AutomationScope, pub metadata: Metadata, pub max_time_to_live: Option, } #[derive(CandidType, Serialize, Deserialize, Clone)] -pub enum GrantableScope { +pub enum AutomationScope { Write, Submit, } diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index c57fcabe29..9679bc3894 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -233,6 +233,7 @@ pub fn list_controllers() -> Controllers { } #[doc(hidden)] +#[update] pub async fn authenticate_controller( args: AuthenticateControllerArgs, ) -> AuthenticateControllerResultResponse { diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index e6c3b32c9f..c427d9ea69 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -11,7 +11,6 @@ use crate::user::core::types::state::{ use crate::{Doc, SetDoc}; use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; use junobuild_utils::encode_doc_data; @@ -176,6 +175,7 @@ mod tests { use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; + use junobuild_auth::openid::types::provider::OpenIdProvider; // ------------------------ // WebAuthnData diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 9e4fec0266..582ab2b94c 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -22,6 +22,13 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; +type AuthenticateControllerArgs = variant { + OpenId : OpenIdAuthenticateControllerArgs; +}; +type AuthenticateControllerResultResponse = variant { + Ok; + Err : AuthenticationControllerError; +}; type AuthenticateResultResponse = variant { Ok : Authentication; Err : AuthenticationError; @@ -42,13 +49,18 @@ type AuthenticationConfigInternetIdentity = record { }; type AuthenticationConfigOpenId = record { observatory_id : opt principal; - providers : vec record { OpenIdProvider; OpenIdProviderConfig }; + providers : vec record { OpenIdDelegationProvider; OpenIdProviderAuthConfig }; +}; +type AuthenticationControllerError = variant { + RegisterController : text; + VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; }; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -212,6 +224,18 @@ type ListRulesResults = record { }; type Memory = variant { Heap; Stable }; type MemorySize = record { stable : nat64; heap : nat64 }; +type OpenIdAuthProviderDelegationConfig = record { + targets : opt vec principal; + max_time_to_live : opt nat64; +}; +type OpenIdAuthenticateControllerArgs = record { + jwt : text; + metadata : vec record { text; text }; + scope : AutomationScope; + max_time_to_live : opt nat64; + controller_id : principal; +}; +type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; session_key : blob; @@ -223,15 +247,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; -type OpenIdProviderConfig = record { - delegation : opt OpenIdProviderDelegationConfig; +type OpenIdProviderAuthConfig = record { + delegation : opt OpenIdAuthProviderDelegationConfig; client_id : text; }; -type OpenIdProviderDelegationConfig = record { - targets : opt vec principal; - max_time_to_live : opt nat64; -}; type Permission = variant { Controllers; Private; Public; Managed }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -373,8 +392,16 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type VerifyOpenidAutomationCredentialsError = variant { + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); + authenticate_controller : (AuthenticateControllerArgs) -> ( + AuthenticateControllerResultResponse, + ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); commit_proposal_asset_upload : (CommitBatch) -> (); diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 4eb0e68586..030dc10729 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -22,6 +22,13 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; +type AuthenticateControllerArgs = variant { + OpenId : OpenIdAuthenticateControllerArgs; +}; +type AuthenticateControllerResultResponse = variant { + Ok; + Err : AuthenticationControllerError; +}; type AuthenticateResultResponse = variant { Ok : Authentication; Err : AuthenticationError; @@ -42,13 +49,18 @@ type AuthenticationConfigInternetIdentity = record { }; type AuthenticationConfigOpenId = record { observatory_id : opt principal; - providers : vec record { OpenIdProvider; OpenIdProviderConfig }; + providers : vec record { OpenIdDelegationProvider; OpenIdProviderAuthConfig }; +}; +type AuthenticationControllerError = variant { + RegisterController : text; + VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; }; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -212,6 +224,18 @@ type ListRulesResults = record { }; type Memory = variant { Heap; Stable }; type MemorySize = record { stable : nat64; heap : nat64 }; +type OpenIdAuthProviderDelegationConfig = record { + targets : opt vec principal; + max_time_to_live : opt nat64; +}; +type OpenIdAuthenticateControllerArgs = record { + jwt : text; + metadata : vec record { text; text }; + scope : AutomationScope; + max_time_to_live : opt nat64; + controller_id : principal; +}; +type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; session_key : blob; @@ -223,15 +247,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; -type OpenIdProviderConfig = record { - delegation : opt OpenIdProviderDelegationConfig; +type OpenIdProviderAuthConfig = record { + delegation : opt OpenIdAuthProviderDelegationConfig; client_id : text; }; -type OpenIdProviderDelegationConfig = record { - targets : opt vec principal; - max_time_to_live : opt nat64; -}; type Permission = variant { Controllers; Private; Public; Managed }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -373,8 +392,16 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type VerifyOpenidAutomationCredentialsError = variant { + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); + authenticate_controller : (AuthenticateControllerArgs) -> ( + AuthenticateControllerResultResponse, + ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); commit_proposal_asset_upload : (CommitBatch) -> (); diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index cc7fbe2464..9506498cb5 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -34,6 +34,12 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } +export type AuthenticateControllerArgs = { + OpenId: OpenIdAuthenticateControllerArgs; +}; +export type AuthenticateControllerResultResponse = + | { Ok: null } + | { Err: AuthenticationControllerError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; @@ -54,8 +60,11 @@ export interface AuthenticationConfigInternetIdentity { } export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; - providers: Array<[OpenIdProvider, OpenIdProviderConfig]>; + providers: Array<[OpenIdDelegationProvider, OpenIdProviderAuthConfig]>; } +export type AuthenticationControllerError = + | { RegisterController: string } + | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -64,6 +73,7 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -259,6 +269,18 @@ export interface MemorySize { stable: bigint; heap: bigint; } +export interface OpenIdAuthProviderDelegationConfig { + targets: [] | [Array]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAuthenticateControllerArgs { + jwt: string; + metadata: Array<[string, string]>; + scope: AutomationScope; + max_time_to_live: [] | [bigint]; + controller_id: Principal; +} +export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; session_key: Uint8Array; @@ -270,15 +292,10 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHubActions: null } | { Google: null } | { GitHubProxy: null }; -export interface OpenIdProviderConfig { - delegation: [] | [OpenIdProviderDelegationConfig]; +export interface OpenIdProviderAuthConfig { + delegation: [] | [OpenIdAuthProviderDelegationConfig]; client_id: string; } -export interface OpenIdProviderDelegationConfig { - targets: [] | [Array]; - max_time_to_live: [] | [bigint]; -} export type Permission = | { Controllers: null } | { Private: null } @@ -439,8 +456,18 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } +export type VerifyOpenidAutomationCredentialsError = + | { + GetCachedJwks: null; + } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; + authenticate_controller: ActorMethod< + [AuthenticateControllerArgs], + AuthenticateControllerResultResponse + >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; commit_proposal_asset_upload: ActorMethod<[CommitBatch], undefined>; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index 8fa34b8bfa..09f883de72 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -448,6 +474,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index c91ec13f54..8f4be55325 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -75,6 +75,33 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAuthenticateControllerArgs = IDL.Record({ + jwt: IDL.Text, + metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), + scope: AutomationScope, + max_time_to_live: IDL.Opt(IDL.Nat64), + controller_id: IDL.Principal + }); + const AuthenticateControllerArgs = IDL.Variant({ + OpenId: OpenIdAuthenticateControllerArgs + }); + const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + GetCachedJwks: IDL.Null, + JwtVerify: JwtVerifyError, + GetOrFetchJwks: GetOrRefreshJwksError + }); + const AuthenticationControllerError = IDL.Variant({ + RegisterController: IDL.Text, + VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + }); + const AuthenticateControllerResultResponse = IDL.Variant({ + Ok: IDL.Null, + Err: AuthenticationControllerError + }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, headers: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), @@ -158,22 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ - GitHubActions: IDL.Null, - Google: IDL.Null, - GitHubProxy: IDL.Null + const OpenIdDelegationProvider = IDL.Variant({ + GitHub: IDL.Null, + Google: IDL.Null }); - const OpenIdProviderDelegationConfig = IDL.Record({ + const OpenIdAuthProviderDelegationConfig = IDL.Record({ targets: IDL.Opt(IDL.Vec(IDL.Principal)), max_time_to_live: IDL.Opt(IDL.Nat64) }); - const OpenIdProviderConfig = IDL.Record({ - delegation: IDL.Opt(OpenIdProviderDelegationConfig), + const OpenIdProviderAuthConfig = IDL.Record({ + delegation: IDL.Opt(OpenIdAuthProviderDelegationConfig), client_id: IDL.Text }); const AuthenticationConfigOpenId = IDL.Record({ observatory_id: IDL.Opt(IDL.Principal), - providers: IDL.Vec(IDL.Tuple(OpenIdProvider, OpenIdProviderConfig)) + providers: IDL.Vec(IDL.Tuple(OpenIdDelegationProvider, OpenIdProviderAuthConfig)) }); const AuthenticationConfigInternetIdentity = IDL.Record({ derivation_origin: IDL.Opt(IDL.Text), @@ -448,6 +474,11 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), + authenticate_controller: IDL.Func( + [AuthenticateControllerArgs], + [AuthenticateControllerResultResponse], + [] + ), commit_asset_upload: IDL.Func([CommitBatch], [], []), commit_proposal: IDL.Func([CommitProposal], [IDL.Null], []), commit_proposal_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index 61bee19770..ffe75739e6 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -22,6 +22,13 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; +type AuthenticateControllerArgs = variant { + OpenId : OpenIdAuthenticateControllerArgs; +}; +type AuthenticateControllerResultResponse = variant { + Ok; + Err : AuthenticationControllerError; +}; type AuthenticateResultResponse = variant { Ok : Authentication; Err : AuthenticationError; @@ -42,13 +49,18 @@ type AuthenticationConfigInternetIdentity = record { }; type AuthenticationConfigOpenId = record { observatory_id : opt principal; - providers : vec record { OpenIdProvider; OpenIdProviderConfig }; + providers : vec record { OpenIdDelegationProvider; OpenIdProviderAuthConfig }; +}; +type AuthenticationControllerError = variant { + RegisterController : text; + VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; }; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -212,6 +224,18 @@ type ListRulesResults = record { }; type Memory = variant { Heap; Stable }; type MemorySize = record { stable : nat64; heap : nat64 }; +type OpenIdAuthProviderDelegationConfig = record { + targets : opt vec principal; + max_time_to_live : opt nat64; +}; +type OpenIdAuthenticateControllerArgs = record { + jwt : text; + metadata : vec record { text; text }; + scope : AutomationScope; + max_time_to_live : opt nat64; + controller_id : principal; +}; +type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; session_key : blob; @@ -223,15 +247,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHubActions; Google; GitHubProxy }; -type OpenIdProviderConfig = record { - delegation : opt OpenIdProviderDelegationConfig; +type OpenIdProviderAuthConfig = record { + delegation : opt OpenIdAuthProviderDelegationConfig; client_id : text; }; -type OpenIdProviderDelegationConfig = record { - targets : opt vec principal; - max_time_to_live : opt nat64; -}; type Permission = variant { Controllers; Private; Public; Managed }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -373,8 +392,16 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; +type VerifyOpenidAutomationCredentialsError = variant { + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); + authenticate_controller : (AuthenticateControllerArgs) -> ( + AuthenticateControllerResultResponse, + ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); commit_proposal_asset_upload : (CommitBatch) -> (); From f87b61a605e000cc0efaea60633d09ece8add8b7 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 11:39:24 +0100 Subject: [PATCH 15/64] feat: actions in observatory --- src/observatory/src/openid/scheduler.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/observatory/src/openid/scheduler.rs b/src/observatory/src/openid/scheduler.rs index 3ffc530e82..cbdc468770 100644 --- a/src/observatory/src/openid/scheduler.rs +++ b/src/observatory/src/openid/scheduler.rs @@ -9,10 +9,14 @@ use std::time::Duration; pub fn defer_restart_monitoring() { // Early spare one timer if no scheduler is enabled. - let enabled_count = [OpenIdProvider::Google, OpenIdProvider::GitHubProxy] - .into_iter() - .filter(|provider| is_scheduler_enabled(provider)) - .count(); + let enabled_count = [ + OpenIdProvider::Google, + OpenIdProvider::GitHubProxy, + OpenIdProvider::GitHubActions, + ] + .into_iter() + .filter(|provider| is_scheduler_enabled(provider)) + .count(); if enabled_count == 0 { return; @@ -24,7 +28,11 @@ pub fn defer_restart_monitoring() { } async fn restart_monitoring() { - for provider in [OpenIdProvider::Google, OpenIdProvider::GitHubProxy] { + for provider in [ + OpenIdProvider::Google, + OpenIdProvider::GitHubProxy, + OpenIdProvider::GitHubActions, + ] { schedule_certificate_update(provider, None); } } From ed29f566b5b920e654e8692ef0066033e8cbcb24 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 12:03:07 +0100 Subject: [PATCH 16/64] feat: one time upgrade --- src/console/src/lib.rs | 1 + src/console/src/memory/lifecycle.rs | 6 +++- src/console/src/upgrade/impls.rs | 55 +++++++++++++++++++++++++++++ src/console/src/upgrade/mod.rs | 2 ++ src/console/src/upgrade/types.rs | 55 +++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/console/src/upgrade/impls.rs create mode 100644 src/console/src/upgrade/mod.rs create mode 100644 src/console/src/upgrade/types.rs diff --git a/src/console/src/lib.rs b/src/console/src/lib.rs index 02650cc546..d4432531bb 100644 --- a/src/console/src/lib.rs +++ b/src/console/src/lib.rs @@ -17,6 +17,7 @@ mod rates; mod segments; mod store; mod types; +mod upgrade; use crate::types::interface::AuthenticationArgs; use crate::types::interface::AuthenticationResult; diff --git a/src/console/src/memory/lifecycle.rs b/src/console/src/memory/lifecycle.rs index c4d911d416..1bf23cf919 100644 --- a/src/console/src/memory/lifecycle.rs +++ b/src/console/src/memory/lifecycle.rs @@ -10,6 +10,7 @@ use junobuild_shared::ic::api::caller; use junobuild_shared::memory::upgrade::{read_post_upgrade, write_pre_upgrade}; use junobuild_shared::segments::controllers::init_admin_controllers; use std::collections::HashMap; +use crate::upgrade::types::upgrade::UpgradeState; #[init] fn init() { @@ -50,9 +51,12 @@ fn post_upgrade() { let memory = get_memory_upgrades(); let state_bytes = read_post_upgrade(&memory); - let state: State = from_reader(&*state_bytes) + // TODO: remove once stable memory introduced on mainnet + let upgrade_state: UpgradeState = from_reader(&*state_bytes) .expect("Failed to decode the state of the console in post_upgrade hook."); + let state: State = upgrade_state.into(); + STATE.with(|s| *s.borrow_mut() = state); defer_init_certified_assets(); diff --git a/src/console/src/upgrade/impls.rs b/src/console/src/upgrade/impls.rs new file mode 100644 index 0000000000..974b9ad7b0 --- /dev/null +++ b/src/console/src/upgrade/impls.rs @@ -0,0 +1,55 @@ +use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::state::types::state::{AuthenticationHeapState, OpenIdState}; +use crate::types::state::{HeapState, State}; +use crate::upgrade::types::upgrade::{UpgradeAuthenticationHeapState, UpgradeHeapState, UpgradeOpenIdProvider, UpgradeState}; + +impl From for State { + fn from(upgrade: UpgradeState) -> Self { + State { + stable: upgrade.stable, + heap: upgrade.heap.into(), + } + } +} + +impl From for HeapState { + fn from(upgrade: UpgradeHeapState) -> Self { + HeapState { + authentication: upgrade.authentication.map(|auth| auth.into()), + controllers: upgrade.controllers, + mission_controls: upgrade.mission_controls, + payments: upgrade.payments, + invitation_codes: upgrade.invitation_codes, + factory_fees: upgrade.factory_fees, + factory_rates: upgrade.factory_rates, + storage: upgrade.storage, + releases_metadata: upgrade.releases_metadata, + } + } +} + +impl From for AuthenticationHeapState { + fn from(upgrade: UpgradeAuthenticationHeapState) -> Self { + AuthenticationHeapState { + config: upgrade.config, + salt: upgrade.salt, + openid: upgrade.openid.map(|openid_state| { + OpenIdState { + certificates: openid_state.certificates + .into_iter() + .map(|(provider, cert)| (provider.into(), cert)) + .collect() + } + }), + } + } +} + +impl From for OpenIdProvider { + fn from(old: UpgradeOpenIdProvider) -> Self { + match old { + UpgradeOpenIdProvider::Google => OpenIdProvider::Google, + UpgradeOpenIdProvider::GitHub => OpenIdProvider::GitHubProxy, + } + } +} \ No newline at end of file diff --git a/src/console/src/upgrade/mod.rs b/src/console/src/upgrade/mod.rs new file mode 100644 index 0000000000..39fcb9cfe1 --- /dev/null +++ b/src/console/src/upgrade/mod.rs @@ -0,0 +1,2 @@ +mod impls; +pub mod types; diff --git a/src/console/src/upgrade/types.rs b/src/console/src/upgrade/types.rs new file mode 100644 index 0000000000..8075586c5e --- /dev/null +++ b/src/console/src/upgrade/types.rs @@ -0,0 +1,55 @@ +pub mod upgrade { + use std::collections::HashMap; + use candid::{CandidType, Deserialize}; + use serde::Serialize; + use junobuild_auth::state::types::config::AuthenticationConfig; + use junobuild_auth::state::types::state::{OpenIdCachedCertificate, Salt}; + use junobuild_shared::types::state::Controllers; + use junobuild_storage::types::state::StorageHeapState; + use crate::types::state::{Accounts, FactoryFees, FactoryRates, IcpPayments, InvitationCodes, ReleasesMetadata, StableState}; + use crate::memory::manager::init_stable_state; + + #[derive(Serialize, Deserialize)] + pub struct UpgradeState { + // Direct stable state: State that is uses stable memory directly as its store. No need for pre/post upgrade hooks. + #[serde(skip, default = "init_stable_state")] + pub stable: StableState, + + pub heap: UpgradeHeapState, + } + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct UpgradeHeapState { + #[deprecated(note = "Deprecated. Use stable memory instead.")] + pub mission_controls: Accounts, + #[deprecated(note = "Deprecated. Use stable memory instead.")] + pub payments: IcpPayments, + pub invitation_codes: InvitationCodes, + pub controllers: Controllers, + pub factory_fees: Option, + pub factory_rates: Option, + pub storage: StorageHeapState, + pub authentication: Option, + pub releases_metadata: ReleasesMetadata, + } + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct UpgradeAuthenticationHeapState { + pub config: AuthenticationConfig, + pub salt: Option, + pub openid: Option, + } + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct UpgradeOpenIdState { + pub certificates: HashMap, + } + + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum UpgradeOpenIdProvider { + Google, + GitHub, + } +} From 20c1f5fbf6a6478f7af487e23b30095958357664 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 12:09:55 +0100 Subject: [PATCH 17/64] feat: one time upgrade --- src/console/src/memory/lifecycle.rs | 2 +- src/console/src/upgrade/impls.rs | 21 +++++----- src/console/src/upgrade/types.rs | 11 ++++-- src/observatory/src/lib.rs | 1 + src/observatory/src/memory/lifecycle.rs | 6 ++- src/observatory/src/upgrade/impls.rs | 51 +++++++++++++++++++++++++ src/observatory/src/upgrade/mod.rs | 2 + src/observatory/src/upgrade/types.rs | 40 +++++++++++++++++++ 8 files changed, 118 insertions(+), 16 deletions(-) create mode 100644 src/observatory/src/upgrade/impls.rs create mode 100644 src/observatory/src/upgrade/mod.rs create mode 100644 src/observatory/src/upgrade/types.rs diff --git a/src/console/src/memory/lifecycle.rs b/src/console/src/memory/lifecycle.rs index 1bf23cf919..5a782b9791 100644 --- a/src/console/src/memory/lifecycle.rs +++ b/src/console/src/memory/lifecycle.rs @@ -4,13 +4,13 @@ use crate::fees::init_factory_fees; use crate::memory::manager::{get_memory_upgrades, init_stable_state, STATE}; use crate::rates::init::init_factory_rates; use crate::types::state::{HeapState, ReleasesMetadata, State}; +use crate::upgrade::types::upgrade::UpgradeState; use ciborium::{from_reader, into_writer}; use ic_cdk_macros::{init, post_upgrade, pre_upgrade}; use junobuild_shared::ic::api::caller; use junobuild_shared::memory::upgrade::{read_post_upgrade, write_pre_upgrade}; use junobuild_shared::segments::controllers::init_admin_controllers; use std::collections::HashMap; -use crate::upgrade::types::upgrade::UpgradeState; #[init] fn init() { diff --git a/src/console/src/upgrade/impls.rs b/src/console/src/upgrade/impls.rs index 974b9ad7b0..f4a6f20b88 100644 --- a/src/console/src/upgrade/impls.rs +++ b/src/console/src/upgrade/impls.rs @@ -1,7 +1,9 @@ +use crate::types::state::{HeapState, State}; +use crate::upgrade::types::upgrade::{ + UpgradeAuthenticationHeapState, UpgradeHeapState, UpgradeOpenIdProvider, UpgradeState, +}; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_auth::state::types::state::{AuthenticationHeapState, OpenIdState}; -use crate::types::state::{HeapState, State}; -use crate::upgrade::types::upgrade::{UpgradeAuthenticationHeapState, UpgradeHeapState, UpgradeOpenIdProvider, UpgradeState}; impl From for State { fn from(upgrade: UpgradeState) -> Self { @@ -33,13 +35,12 @@ impl From for AuthenticationHeapState { AuthenticationHeapState { config: upgrade.config, salt: upgrade.salt, - openid: upgrade.openid.map(|openid_state| { - OpenIdState { - certificates: openid_state.certificates - .into_iter() - .map(|(provider, cert)| (provider.into(), cert)) - .collect() - } + openid: upgrade.openid.map(|openid_state| OpenIdState { + certificates: openid_state + .certificates + .into_iter() + .map(|(provider, cert)| (provider.into(), cert)) + .collect(), }), } } @@ -52,4 +53,4 @@ impl From for OpenIdProvider { UpgradeOpenIdProvider::GitHub => OpenIdProvider::GitHubProxy, } } -} \ No newline at end of file +} diff --git a/src/console/src/upgrade/types.rs b/src/console/src/upgrade/types.rs index 8075586c5e..cde8370010 100644 --- a/src/console/src/upgrade/types.rs +++ b/src/console/src/upgrade/types.rs @@ -1,13 +1,16 @@ pub mod upgrade { - use std::collections::HashMap; + use crate::memory::manager::init_stable_state; + use crate::types::state::{ + Accounts, FactoryFees, FactoryRates, IcpPayments, InvitationCodes, ReleasesMetadata, + StableState, + }; use candid::{CandidType, Deserialize}; - use serde::Serialize; use junobuild_auth::state::types::config::AuthenticationConfig; use junobuild_auth::state::types::state::{OpenIdCachedCertificate, Salt}; use junobuild_shared::types::state::Controllers; use junobuild_storage::types::state::StorageHeapState; - use crate::types::state::{Accounts, FactoryFees, FactoryRates, IcpPayments, InvitationCodes, ReleasesMetadata, StableState}; - use crate::memory::manager::init_stable_state; + use serde::Serialize; + use std::collections::HashMap; #[derive(Serialize, Deserialize)] pub struct UpgradeState { diff --git a/src/observatory/src/lib.rs b/src/observatory/src/lib.rs index e183fe0da0..34d7e821c4 100644 --- a/src/observatory/src/lib.rs +++ b/src/observatory/src/lib.rs @@ -11,6 +11,7 @@ mod random; mod store; mod templates; mod types; +mod upgrade; use crate::types::interface::GetNotifications; use crate::types::interface::NotifyStatus; diff --git a/src/observatory/src/memory/lifecycle.rs b/src/observatory/src/memory/lifecycle.rs index f841d7183f..3527855fd2 100644 --- a/src/observatory/src/memory/lifecycle.rs +++ b/src/observatory/src/memory/lifecycle.rs @@ -3,6 +3,7 @@ use crate::memory::state::STATE; use crate::openid::scheduler::defer_restart_monitoring; use crate::random::defer_init_random_seed; use crate::types::state::{HeapState, State}; +use crate::upgrade::types::upgrade::UpgradeState; use ciborium::{from_reader, into_writer}; use ic_cdk_macros::{init, post_upgrade, pre_upgrade}; use junobuild_shared::ic::api::caller; @@ -45,9 +46,12 @@ fn post_upgrade() { let memory = get_memory_upgrades(); let state_bytes = read_post_upgrade(&memory); - let state: State = from_reader(&*state_bytes) + // TODO: remove once stable memory introduced on mainnet + let upgrade_state: UpgradeState = from_reader(&*state_bytes) .expect("Failed to decode the state of the observatory in post_upgrade hook."); + let state: State = upgrade_state.into(); + STATE.with(|s| *s.borrow_mut() = state); init_runtime_state(); diff --git a/src/observatory/src/upgrade/impls.rs b/src/observatory/src/upgrade/impls.rs new file mode 100644 index 0000000000..2c6ad30b25 --- /dev/null +++ b/src/observatory/src/upgrade/impls.rs @@ -0,0 +1,51 @@ +use crate::types::state::{HeapState, OpenId, State}; +use crate::upgrade::types::upgrade::{ + UpgradeHeapState, UpgradeOpenId, UpgradeOpenIdProvider, UpgradeState, +}; +use junobuild_auth::openid::types::provider::OpenIdProvider; + +impl From for State { + fn from(upgrade: UpgradeState) -> Self { + State { + stable: upgrade.stable, + heap: upgrade.heap.into(), + } + } +} + +impl From for HeapState { + fn from(upgrade: UpgradeHeapState) -> Self { + HeapState { + controllers: upgrade.controllers, + env: upgrade.env, + openid: upgrade.openid.map(|openid| openid.into()), + rates: upgrade.rates, + } + } +} + +impl From for OpenId { + fn from(upgrade: UpgradeOpenId) -> Self { + OpenId { + certificates: upgrade + .certificates + .into_iter() + .map(|(provider, cert)| (provider.into(), cert)) + .collect(), + schedulers: upgrade + .schedulers + .into_iter() + .map(|(provider, scheduler)| (provider.into(), scheduler)) + .collect(), + } + } +} + +impl From for OpenIdProvider { + fn from(old: UpgradeOpenIdProvider) -> Self { + match old { + UpgradeOpenIdProvider::Google => OpenIdProvider::Google, + UpgradeOpenIdProvider::GitHub => OpenIdProvider::GitHubProxy, + } + } +} diff --git a/src/observatory/src/upgrade/mod.rs b/src/observatory/src/upgrade/mod.rs new file mode 100644 index 0000000000..39fcb9cfe1 --- /dev/null +++ b/src/observatory/src/upgrade/mod.rs @@ -0,0 +1,2 @@ +mod impls; +pub mod types; diff --git a/src/observatory/src/upgrade/types.rs b/src/observatory/src/upgrade/types.rs new file mode 100644 index 0000000000..d5cd9d858d --- /dev/null +++ b/src/observatory/src/upgrade/types.rs @@ -0,0 +1,40 @@ +pub mod upgrade { + use crate::memory::init_stable_state; + use crate::types::state::{Env, OpenIdScheduler, Rates, StableState}; + use candid::{CandidType, Deserialize}; + use junobuild_auth::openid::types::provider::OpenIdCertificate; + use junobuild_shared::types::state::Controllers; + use serde::Serialize; + use std::collections::HashMap; + + #[derive(Serialize, Deserialize)] + pub struct UpgradeState { + // Direct stable state: State that is uses stable memory directly as its store. No need for pre/post upgrade hooks. + #[serde(skip, default = "init_stable_state")] + pub stable: StableState, + + pub heap: UpgradeHeapState, + } + + #[derive(Default, CandidType, Serialize, Deserialize)] + pub struct UpgradeHeapState { + pub controllers: Controllers, + pub env: Option, + pub openid: Option, + pub rates: Option, + } + + #[derive(Default, CandidType, Serialize, Deserialize)] + pub struct UpgradeOpenId { + pub certificates: HashMap, + pub schedulers: HashMap, + } + + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum UpgradeOpenIdProvider { + Google, + GitHub, + } +} From 0ad8e611edbb67a930b69cea690ce6be5f30b481 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 12:40:48 +0100 Subject: [PATCH 18/64] feat: export auth --- src/libs/satellite/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 9679bc3894..c5199eda74 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -561,7 +561,7 @@ macro_rules! include_satellite { post_upgrade, pre_upgrade, reject_proposal, set_asset_token, set_auth_config, set_controllers, set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, submit_proposal, switch_storage_system_memory, upload_asset_chunk, - upload_proposal_asset_chunk, + upload_proposal_asset_chunk, authenticate_controller, }; ic_cdk::export_candid!(); From 235abb50573bb407113317897e00e7b8adc5f63c Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 28 Jan 2026 15:09:04 +0100 Subject: [PATCH 19/64] chore: fmt --- src/libs/satellite/src/lib.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index c5199eda74..db1474a5f0 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -18,7 +18,9 @@ mod sdk; mod types; mod user; +use crate::controllers::types::AuthenticateControllerArgs; use crate::db::types::config::DbConfig; +use crate::db::types::interface::SetDbConfig; use crate::guards::{ caller_is_admin_controller, caller_is_controller, caller_is_controller_with_write, }; @@ -29,6 +31,7 @@ use crate::types::interface::{ use crate::types::state::CollectionType; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; use junobuild_auth::state::types::config::AuthenticationConfig; +use junobuild_auth::state::types::interface::SetAuthenticationConfig; use junobuild_cdn::proposals::{ CommitProposal, ListProposalResults, ListProposalsParams, Proposal, ProposalId, ProposalType, RejectProposal, @@ -63,11 +66,9 @@ use memory::lifecycle; // ============================================================================================ // These types are made available for use in Serverless Functions. // ============================================================================================ -use crate::controllers::types::AuthenticateControllerArgs; -use crate::db::types::interface::SetDbConfig; -use junobuild_auth::state::types::interface::SetAuthenticationConfig; pub use sdk::core::*; pub use sdk::internal; + // --------------------------------------------------------- // Init and Upgrade // --------------------------------------------------------- @@ -548,20 +549,20 @@ pub fn memory_size() -> MemorySize { macro_rules! include_satellite { () => { use junobuild_satellite::{ - authenticate, commit_asset_upload, commit_proposal, commit_proposal_asset_upload, - commit_proposal_many_assets_upload, count_assets, count_collection_assets, - count_collection_docs, count_docs, count_proposals, del_asset, del_assets, - del_controllers, del_custom_domain, del_doc, del_docs, del_filtered_assets, - del_filtered_docs, del_many_assets, del_many_docs, del_rule, delete_proposal_assets, - deposit_cycles, get_asset, get_auth_config, get_config, get_db_config, get_delegation, - get_doc, get_many_assets, get_many_docs, get_proposal, get_storage_config, - http_request, http_request_streaming_callback, init, init_asset_upload, init_proposal, - init_proposal_asset_upload, init_proposal_many_assets_upload, list_assets, - list_controllers, list_custom_domains, list_docs, list_proposals, list_rules, - post_upgrade, pre_upgrade, reject_proposal, set_asset_token, set_auth_config, - set_controllers, set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, - set_storage_config, submit_proposal, switch_storage_system_memory, upload_asset_chunk, - upload_proposal_asset_chunk, authenticate_controller, + authenticate, authenticate_controller, authenticate_controller, commit_asset_upload, + commit_proposal, commit_proposal_asset_upload, commit_proposal_many_assets_upload, + count_assets, count_collection_assets, count_collection_docs, count_docs, + count_proposals, del_asset, del_assets, del_controllers, del_custom_domain, del_doc, + del_docs, del_filtered_assets, del_filtered_docs, del_many_assets, del_many_docs, + del_rule, delete_proposal_assets, deposit_cycles, get_asset, get_auth_config, + get_config, get_db_config, get_delegation, get_doc, get_many_assets, get_many_docs, + get_proposal, get_storage_config, http_request, http_request_streaming_callback, init, + init_asset_upload, init_proposal, init_proposal_asset_upload, + init_proposal_many_assets_upload, list_assets, list_controllers, list_custom_domains, + list_docs, list_proposals, list_rules, post_upgrade, pre_upgrade, reject_proposal, + set_asset_token, set_auth_config, set_controllers, set_custom_domain, set_db_config, + set_doc, set_many_docs, set_rule, set_storage_config, submit_proposal, + switch_storage_system_memory, upload_asset_chunk, upload_proposal_asset_chunk, }; ic_cdk::export_candid!(); From 46fb0aa638e980f38dfe40f4e1f8a98438712d0c Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 06:28:52 +0100 Subject: [PATCH 20/64] chore: remove unused impl --- src/libs/auth/src/openid/delegation/impls.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/libs/auth/src/openid/delegation/impls.rs b/src/libs/auth/src/openid/delegation/impls.rs index 26a63fb684..b7678db777 100644 --- a/src/libs/auth/src/openid/delegation/impls.rs +++ b/src/libs/auth/src/openid/delegation/impls.rs @@ -29,21 +29,6 @@ impl<'a> From<&'a OpenIdCredential> for OpenIdCredentialKey<'a> { } } -impl TryFrom<&OpenIdProvider> for OpenIdDelegationProvider { - type Error = String; - - fn try_from(provider: &OpenIdProvider) -> Result { - match provider { - OpenIdProvider::Google => Ok(OpenIdDelegationProvider::Google), - OpenIdProvider::GitHubAuth => Ok(OpenIdDelegationProvider::GitHub), - _ => Err(format!( - "{:?} is not supported for user authentication", - provider - )), - } - } -} - impl From<&OpenIdDelegationProvider> for OpenIdProvider { fn from(delegation_provider: &OpenIdDelegationProvider) -> Self { match delegation_provider { From 5f193fc10b7d23145b2bd46cf6efbfb1e28b8c89 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 06:30:56 +0100 Subject: [PATCH 21/64] feat: rename --- src/libs/auth/src/openid/delegation/verify.rs | 14 +++++------ src/libs/auth/src/openid/jwt/provider.rs | 24 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/libs/auth/src/openid/delegation/verify.rs b/src/libs/auth/src/openid/delegation/verify.rs index 1078079e4d..7d60a487a1 100644 --- a/src/libs/auth/src/openid/delegation/verify.rs +++ b/src/libs/auth/src/openid/delegation/verify.rs @@ -5,7 +5,7 @@ use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; -use crate::openid::jwt::{unsafe_find_jwt_auth_provider, verify_openid_jwt}; +use crate::openid::jwt::{unsafe_find_jwt_delegation_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdProvider; use crate::openid::utils::build_nonce; use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProviders}; @@ -21,16 +21,16 @@ pub async fn verify_openid_credentials_with_jwks_renewal( providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdDelegationCredentialsResult { - let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) + let (delegation_provider, config) = unsafe_find_jwt_delegation_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; - let provider: OpenIdProvider = (&auth_provider).into(); + let provider: OpenIdProvider = (&delegation_provider).into(); let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &auth_provider, &config.client_id, salt) + verify_openid_credentials(jwt, &jwks, &delegation_provider, &config.client_id, salt) } pub fn verify_openid_credentials_with_cached_jwks( @@ -39,14 +39,14 @@ pub fn verify_openid_credentials_with_cached_jwks( providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdDelegationCredentialsResult { - let (auth_provider, config) = unsafe_find_jwt_auth_provider(providers, jwt) + let (delegation_provider, config) = unsafe_find_jwt_delegation_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; - let provider: OpenIdProvider = (&auth_provider).into(); + let provider: OpenIdProvider = (&delegation_provider).into(); let jwks = get_jwks(&provider, auth_heap).ok_or(VerifyOpenidCredentialsError::GetCachedJwks)?; - verify_openid_credentials(jwt, &jwks, &auth_provider, &config.client_id, salt) + verify_openid_credentials(jwt, &jwks, &delegation_provider, &config.client_id, salt) } fn verify_openid_credentials( diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index eef8c1bb12..8f5e2e7332 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -7,7 +7,7 @@ use jsonwebtoken::dangerous; /// ⚠️ **Warning:** This function decodes the JWT payload *without verifying its signature*. /// Use only to inspect claims (e.g., `iss`) before performing a verified decode. -pub fn unsafe_find_jwt_auth_provider<'a>( +pub fn unsafe_find_jwt_delegation_provider<'a>( providers: &'a OpenIdAuthProviders, jwt: &str, ) -> Result<(OpenIdDelegationProvider, &'a OpenIdAuthProviderConfig), JwtFindProviderError> { @@ -33,7 +33,7 @@ pub fn unsafe_find_jwt_auth_provider<'a>( #[cfg(test)] mod tests { - use super::unsafe_find_jwt_auth_provider; + use super::unsafe_find_jwt_delegation_provider; use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::state::types::config::{OpenIdAuthProviderConfig, OpenIdAuthProviders}; @@ -71,7 +71,7 @@ mod tests { let provs = providers_with_google(); let (provider, cfg) = - unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match provider"); + unsafe_find_jwt_delegation_provider(&provs, &jwt).expect("should match provider"); assert_eq!(provider, OpenIdDelegationProvider::Google); assert_eq!(cfg.client_id, "client-123"); @@ -83,8 +83,8 @@ mod tests { let jwt = jwt_with(json!({"alg":"RS256"}), json!({"iss": iss})); let provs = providers_with_google(); - let (provider, _) = - unsafe_find_jwt_auth_provider(&provs, &jwt).expect("should match even without typ"); + let (provider, _) = unsafe_find_jwt_delegation_provider(&provs, &jwt) + .expect("should match even without typ"); assert_eq!(provider, OpenIdDelegationProvider::Google); } @@ -94,7 +94,7 @@ mod tests { let jwt = jwt_with(json!({"alg":"HS256","typ":"JWT"}), json!({"iss": iss})); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "alg"), @@ -111,7 +111,7 @@ mod tests { ); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "typ"), @@ -123,7 +123,7 @@ mod tests { fn returns_no_matching_provider_when_issuer_missing() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({})); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -134,7 +134,7 @@ mod tests { json!({"iss":"https://unknown.example.com"}), ); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -142,7 +142,7 @@ mod tests { fn malformed_token_is_badsig() { let jwt = "definitely-not-a-jwt"; let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -155,7 +155,7 @@ mod tests { let jwt = format!("{h}.{p}.{s}"); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -163,7 +163,7 @@ mod tests { fn empty_iss_is_no_match() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({"iss": ""})); let provs = providers_with_google(); - let err = unsafe_find_jwt_auth_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } } From 1a2f7b71dbfcaa03136e1fb6dc752c91edf9d02d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 07:26:13 +0100 Subject: [PATCH 22/64] refactor: move --- src/console/src/accounts/impls.rs | 2 +- src/console/src/auth/delegation.rs | 11 ++++++----- src/console/src/auth/register.rs | 4 ++-- src/console/src/types.rs | 2 +- src/libs/auth/src/delegation/get.rs | 6 ++++-- src/libs/auth/src/delegation/impls.rs | 2 +- src/libs/auth/src/delegation/prepare.rs | 6 ++++-- src/libs/auth/src/delegation/utils/duration.rs | 2 +- src/libs/auth/src/delegation/utils/seed.rs | 4 ++-- src/libs/auth/src/delegation/utils/targets.rs | 2 +- .../src/openid/{ => credentials}/delegation/impls.rs | 6 ++++-- .../src/openid/{ => credentials}/delegation/mod.rs | 0 .../src/openid/{ => credentials}/delegation/types.rs | 0 .../src/openid/{ => credentials}/delegation/verify.rs | 6 +++--- src/libs/auth/src/openid/credentials/mod.rs | 1 + src/libs/auth/src/openid/jwt/provider.rs | 4 ++-- src/libs/auth/src/openid/mod.rs | 2 +- src/libs/auth/src/state/types.rs | 2 +- src/libs/satellite/src/auth/delegation.rs | 11 ++++++----- src/libs/satellite/src/auth/register.rs | 4 ++-- src/libs/satellite/src/user/core/impls.rs | 4 ++-- 21 files changed, 45 insertions(+), 36 deletions(-) rename src/libs/auth/src/openid/{ => credentials}/delegation/impls.rs (89%) rename src/libs/auth/src/openid/{ => credentials}/delegation/mod.rs (100%) rename src/libs/auth/src/openid/{ => credentials}/delegation/types.rs (100%) rename src/libs/auth/src/openid/{ => credentials}/delegation/verify.rs (92%) create mode 100644 src/libs/auth/src/openid/credentials/mod.rs diff --git a/src/console/src/accounts/impls.rs b/src/console/src/accounts/impls.rs index 516dac591f..d258d59daa 100644 --- a/src/console/src/accounts/impls.rs +++ b/src/console/src/accounts/impls.rs @@ -1,7 +1,7 @@ use crate::constants::E8S_PER_ICP; use crate::types::state::{Account, OpenIdData, Provider}; use ic_cdk::api::time; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; use junobuild_auth::profile::types::OpenIdProfile; use junobuild_shared::types::state::{MissionControlId, UserId}; diff --git a/src/console/src/auth/delegation.rs b/src/console/src/auth/delegation.rs index b259366fce..c183f6254f 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -1,13 +1,14 @@ use crate::auth::strategy_impls::AuthHeap; use crate::certification::strategy_impls::AuthCertificate; +use junobuild_auth::delegation; use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; -use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::config::OpenIdAuthProviders; -use junobuild_auth::{delegation, openid}; pub type OpenIdPrepareDelegationResult = Result< ( @@ -23,7 +24,7 @@ pub async fn openid_prepare_delegation( providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { let (credential, provider) = - match openid::delegation::verify_openid_credentials_with_jwks_renewal( + match credentials::delegation::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -48,7 +49,7 @@ pub fn openid_get_delegation( providers: &OpenIdAuthProviders, ) -> GetDelegationResult { let (credential, provider) = - match openid::delegation::verify_openid_credentials_with_cached_jwks( + match credentials::delegation::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/console/src/auth/register.rs b/src/console/src/auth/register.rs index 4da81b00c6..613ff553fc 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -3,8 +3,8 @@ use crate::types::state::OpenId; use crate::types::state::{Account, OpenIdData, Provider}; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; -use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; pub async fn register_account( public_key: &UserKey, diff --git a/src/console/src/types.rs b/src/console/src/types.rs index 70eb514de2..7be910ed89 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -4,7 +4,7 @@ pub mod state { use candid::CandidType; use ic_ledger_types::{BlockIndex, Tokens}; use ic_stable_structures::StableBTreeMap; - use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; + use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::state::AuthenticationHeapState; use junobuild_cdn::proposals::{ProposalsStable, SegmentDeploymentVersion}; use junobuild_cdn::storage::{ProposalAssetsStable, ProposalContentChunksStable}; diff --git a/src/libs/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 2117577734..561a116180 100644 --- a/src/libs/auth/src/delegation/get.rs +++ b/src/libs/auth/src/delegation/get.rs @@ -4,8 +4,10 @@ use crate::delegation::types::{ use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::interface::{ + OpenIdCredential, OpenIdCredentialKey, +}; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::read_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/impls.rs b/src/libs/auth/src/delegation/impls.rs index dbdfc2a9ec..fbb44d7a98 100644 --- a/src/libs/auth/src/delegation/impls.rs +++ b/src/libs/auth/src/delegation/impls.rs @@ -1,5 +1,5 @@ use crate::delegation::types::{GetDelegationError, PrepareDelegationError}; -use crate::openid::delegation::types::errors::VerifyOpenidCredentialsError; +use crate::openid::credentials::delegation::types::errors::VerifyOpenidCredentialsError; impl From for GetDelegationError { fn from(e: VerifyOpenidCredentialsError) -> Self { diff --git a/src/libs/auth/src/delegation/prepare.rs b/src/libs/auth/src/delegation/prepare.rs index 6889ba555f..295e66c018 100644 --- a/src/libs/auth/src/delegation/prepare.rs +++ b/src/libs/auth/src/delegation/prepare.rs @@ -6,8 +6,10 @@ use crate::delegation::utils::duration::build_expiration; use crate::delegation::utils::seed::calculate_seed; use crate::delegation::utils::signature::{build_signature_inputs, build_signature_msg}; use crate::delegation::utils::targets::build_targets; -use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::interface::{ + OpenIdCredential, OpenIdCredentialKey, +}; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_salt; use crate::state::services::mutate_state; use crate::strategies::{AuthCertificateStrategy, AuthHeapStrategy}; diff --git a/src/libs/auth/src/delegation/utils/duration.rs b/src/libs/auth/src/delegation/utils/duration.rs index 7a7ca03d3a..a922d9e1e8 100644 --- a/src/libs/auth/src/delegation/utils/duration.rs +++ b/src/libs/auth/src/delegation/utils/duration.rs @@ -1,5 +1,5 @@ use crate::delegation::constants::{DEFAULT_EXPIRATION_PERIOD_NS, MAX_EXPIRATION_PERIOD_NS}; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use ic_cdk::api::time; diff --git a/src/libs/auth/src/delegation/utils/seed.rs b/src/libs/auth/src/delegation/utils/seed.rs index 3911f4dbfc..967206c38d 100644 --- a/src/libs/auth/src/delegation/utils/seed.rs +++ b/src/libs/auth/src/delegation/utils/seed.rs @@ -1,4 +1,4 @@ -use crate::openid::delegation::types::interface::OpenIdCredentialKey; +use crate::openid::credentials::delegation::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; @@ -30,7 +30,7 @@ fn hash_bytes(value: impl AsRef<[u8]>) -> Hash { #[cfg(test)] mod tests { use super::calculate_seed; - use crate::openid::delegation::types::interface::OpenIdCredentialKey; + use crate::openid::credentials::delegation::types::interface::OpenIdCredentialKey; use crate::state::types::state::Salt; use ic_certification::Hash; use sha2::{Digest, Sha256}; diff --git a/src/libs/auth/src/delegation/utils/targets.rs b/src/libs/auth/src/delegation/utils/targets.rs index 7704204e0f..cb9f2c4097 100644 --- a/src/libs/auth/src/delegation/utils/targets.rs +++ b/src/libs/auth/src/delegation/utils/targets.rs @@ -1,5 +1,5 @@ use crate::delegation::types::DelegationTargets; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::id; diff --git a/src/libs/auth/src/openid/delegation/impls.rs b/src/libs/auth/src/openid/credentials/delegation/impls.rs similarity index 89% rename from src/libs/auth/src/openid/delegation/impls.rs rename to src/libs/auth/src/openid/credentials/delegation/impls.rs index b7678db777..db004037db 100644 --- a/src/libs/auth/src/openid/delegation/impls.rs +++ b/src/libs/auth/src/openid/credentials/delegation/impls.rs @@ -1,5 +1,7 @@ -use crate::openid::delegation::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::interface::{ + OpenIdCredential, OpenIdCredentialKey, +}; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::types::token::Claims; use crate::openid::types::provider::OpenIdProvider; use jsonwebtoken::TokenData; diff --git a/src/libs/auth/src/openid/delegation/mod.rs b/src/libs/auth/src/openid/credentials/delegation/mod.rs similarity index 100% rename from src/libs/auth/src/openid/delegation/mod.rs rename to src/libs/auth/src/openid/credentials/delegation/mod.rs diff --git a/src/libs/auth/src/openid/delegation/types.rs b/src/libs/auth/src/openid/credentials/delegation/types.rs similarity index 100% rename from src/libs/auth/src/openid/delegation/types.rs rename to src/libs/auth/src/openid/credentials/delegation/types.rs diff --git a/src/libs/auth/src/openid/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs similarity index 92% rename from src/libs/auth/src/openid/delegation/verify.rs rename to src/libs/auth/src/openid/credentials/delegation/verify.rs index 7d60a487a1..82d546b194 100644 --- a/src/libs/auth/src/openid/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,6 +1,6 @@ -use crate::openid::delegation::types::errors::VerifyOpenidCredentialsError; -use crate::openid::delegation::types::interface::OpenIdCredential; -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::errors::VerifyOpenidCredentialsError; +use crate::openid::credentials::delegation::types::interface::OpenIdCredential; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; diff --git a/src/libs/auth/src/openid/credentials/mod.rs b/src/libs/auth/src/openid/credentials/mod.rs new file mode 100644 index 0000000000..94f5b8fd92 --- /dev/null +++ b/src/libs/auth/src/openid/credentials/mod.rs @@ -0,0 +1 @@ +pub mod delegation; diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 8f5e2e7332..b57e6a5ae9 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,4 +1,4 @@ -use crate::openid::delegation::types::provider::OpenIdDelegationProvider; +use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::jwt::types::token::UnsafeClaims; @@ -34,7 +34,7 @@ pub fn unsafe_find_jwt_delegation_provider<'a>( #[cfg(test)] mod tests { use super::unsafe_find_jwt_delegation_provider; - use crate::openid::delegation::types::provider::OpenIdDelegationProvider; + use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::state::types::config::{OpenIdAuthProviderConfig, OpenIdAuthProviders}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index e962e11ab5..86389ac488 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -1,5 +1,5 @@ pub mod automation; -pub mod delegation; +pub mod credentials; mod impls; pub mod jwkset; pub mod jwt; diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index 303be1689a..05664fd8eb 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -53,7 +53,7 @@ pub(crate) mod runtime_state { pub mod config { use crate::delegation::types::DelegationTargets; - use crate::openid::delegation::types::provider::OpenIdDelegationProvider; + use crate::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use candid::{CandidType, Deserialize, Principal}; use junobuild_shared::types::core::DomainName; use junobuild_shared::types::state::{Timestamp, Version}; diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index b259366fce..c183f6254f 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -1,13 +1,14 @@ use crate::auth::strategy_impls::AuthHeap; use crate::certification::strategy_impls::AuthCertificate; +use junobuild_auth::delegation; use junobuild_auth::delegation::types::{ GetDelegationError, GetDelegationResult, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, }; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; -use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::config::OpenIdAuthProviders; -use junobuild_auth::{delegation, openid}; pub type OpenIdPrepareDelegationResult = Result< ( @@ -23,7 +24,7 @@ pub async fn openid_prepare_delegation( providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { let (credential, provider) = - match openid::delegation::verify_openid_credentials_with_jwks_renewal( + match credentials::delegation::verify_openid_credentials_with_jwks_renewal( &args.jwt, &args.salt, providers, &AuthHeap, ) .await @@ -48,7 +49,7 @@ pub fn openid_get_delegation( providers: &OpenIdAuthProviders, ) -> GetDelegationResult { let (credential, provider) = - match openid::delegation::verify_openid_credentials_with_cached_jwks( + match credentials::delegation::verify_openid_credentials_with_cached_jwks( &args.jwt, &args.salt, providers, &AuthHeap, ) { Ok(value) => value, diff --git a/src/libs/satellite/src/auth/register.rs b/src/libs/satellite/src/auth/register.rs index 2e1742ec76..5ba589dda8 100644 --- a/src/libs/satellite/src/auth/register.rs +++ b/src/libs/satellite/src/auth/register.rs @@ -7,8 +7,8 @@ use crate::user::core::types::state::{OpenIdData, ProviderData, UserData}; use crate::Doc; use candid::Principal; use junobuild_auth::delegation::types::UserKey; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; -use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use junobuild_collections::constants::db::COLLECTION_USER_KEY; use junobuild_collections::msg::msg_db_collection_not_found; use junobuild_shared::ic::api::id; diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index af023a0237..db7e322b3c 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -9,8 +9,8 @@ use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; use crate::{Doc, SetDoc}; -use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; -use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::credentials::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; use junobuild_utils::encode_doc_data; From 648538b8b7d2bc92a61af1dfec98419eb30ddf53 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 07:27:40 +0100 Subject: [PATCH 23/64] refactor: move automation --- src/libs/auth/src/openid/{ => credentials}/automation/mod.rs | 0 .../auth/src/openid/{ => credentials}/automation/types.rs | 0 .../auth/src/openid/{ => credentials}/automation/verify.rs | 2 +- src/libs/auth/src/openid/credentials/delegation/verify.rs | 1 - src/libs/auth/src/openid/credentials/mod.rs | 1 + src/libs/auth/src/openid/mod.rs | 1 - src/libs/satellite/src/controllers/authenticate.rs | 4 ++-- src/libs/satellite/src/controllers/types.rs | 2 +- 8 files changed, 5 insertions(+), 6 deletions(-) rename src/libs/auth/src/openid/{ => credentials}/automation/mod.rs (100%) rename src/libs/auth/src/openid/{ => credentials}/automation/types.rs (100%) rename src/libs/auth/src/openid/{ => credentials}/automation/verify.rs (94%) diff --git a/src/libs/auth/src/openid/automation/mod.rs b/src/libs/auth/src/openid/credentials/automation/mod.rs similarity index 100% rename from src/libs/auth/src/openid/automation/mod.rs rename to src/libs/auth/src/openid/credentials/automation/mod.rs diff --git a/src/libs/auth/src/openid/automation/types.rs b/src/libs/auth/src/openid/credentials/automation/types.rs similarity index 100% rename from src/libs/auth/src/openid/automation/types.rs rename to src/libs/auth/src/openid/credentials/automation/types.rs diff --git a/src/libs/auth/src/openid/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs similarity index 94% rename from src/libs/auth/src/openid/automation/verify.rs rename to src/libs/auth/src/openid/credentials/automation/verify.rs index 20236f718c..146c2250cd 100644 --- a/src/libs/auth/src/openid/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -1,4 +1,4 @@ -use crate::openid::automation::types::errors::VerifyOpenidAutomationCredentialsError; +use crate::openid::credentials::automation::types::errors::VerifyOpenidAutomationCredentialsError; use crate::openid::jwkset::get_or_refresh_jwks; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 5e2a1f6323..82d546b194 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -12,7 +12,6 @@ use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProvider use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; - type VerifyOpenIdDelegationCredentialsResult = Result<(OpenIdCredential, OpenIdDelegationProvider), VerifyOpenidCredentialsError>; diff --git a/src/libs/auth/src/openid/credentials/mod.rs b/src/libs/auth/src/openid/credentials/mod.rs index 94f5b8fd92..d766412ebc 100644 --- a/src/libs/auth/src/openid/credentials/mod.rs +++ b/src/libs/auth/src/openid/credentials/mod.rs @@ -1 +1,2 @@ pub mod delegation; +pub mod automation; diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index 86389ac488..74d28a01c7 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -1,4 +1,3 @@ -pub mod automation; pub mod credentials; mod impls; pub mod jwkset; diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index 0b4e8e8af1..cf2f49db1f 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -5,17 +5,17 @@ use crate::controllers::types::{ AuthenticateControllerResult, AuthenticationControllerError, OpenIdAuthenticateControllerArgs, }; use ic_cdk::api::time; -use junobuild_auth::openid; use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_shared::segments::controllers::assert_controllers; use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; use std::cmp::min; +use junobuild_auth::openid::credentials; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, ) -> AuthenticateControllerResult { - match openid::automation::verify_openid_credentials_with_jwks_renewal( + match credentials::automation::verify_openid_credentials_with_jwks_renewal( &args.jwt, &OpenIdProvider::GitHubActions, &AuthHeap, diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index 5f8c4def76..b19fb3667c 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,5 +1,5 @@ use candid::{CandidType, Deserialize}; -use junobuild_auth::openid::automation::types::errors::VerifyOpenidAutomationCredentialsError; +use junobuild_auth::openid::credentials::automation::types::errors::VerifyOpenidAutomationCredentialsError; use junobuild_shared::types::state::{ControllerId, Metadata}; use serde::Serialize; From 755e0b8c35d7c747ebbf5ae67709d526c6d6ae85 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 09:25:02 +0100 Subject: [PATCH 24/64] feat: automation config --- src/libs/auth/src/openid/impls.rs | 150 +++++++++++++++++++++++++++++- src/libs/auth/src/openid/types.rs | 7 ++ src/libs/auth/src/state/types.rs | 53 +++++++++++ 3 files changed, 209 insertions(+), 1 deletion(-) diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 215f4d5b60..9b17246913 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,5 +1,5 @@ use crate::openid::jwt::types::cert::Jwks; -use crate::openid::types::provider::{OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider}; +use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider}; use ic_cdk::api::time; use junobuild_shared::data::version::next_version; use junobuild_shared::types::state::{Version, Versioned}; @@ -50,6 +50,28 @@ impl OpenIdDelegationProvider { } } +impl From<&OpenIdAutomationProvider> for OpenIdProvider { + fn from(automation_provider: &OpenIdAutomationProvider) -> Self { + match automation_provider { + OpenIdAutomationProvider::GitHub => OpenIdProvider::GitHubActions, + } + } +} + +impl OpenIdAutomationProvider { + pub fn jwks_url(&self) -> &'static str { + match self { + Self::GitHub => OpenIdProvider::GitHubActions.jwks_url(), + } + } + + pub fn issuers(&self) -> &[&'static str] { + match self { + Self::GitHub => OpenIdProvider::GitHubActions.issuers(), + } + } +} + impl Versioned for OpenIdCertificate { fn version(&self) -> Option { self.version @@ -97,3 +119,129 @@ impl Display for OpenIdProvider { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_openid_provider_jwks_urls() { + assert_eq!( + OpenIdProvider::Google.jwks_url(), + "https://www.googleapis.com/oauth2/v3/certs" + ); + assert_eq!( + OpenIdProvider::GitHubAuth.jwks_url(), + "https://api.juno.build/v1/auth/certs" + ); + assert_eq!( + OpenIdProvider::GitHubActions.jwks_url(), + "https://token.actions.githubusercontent.com/.well-known/jwks" + ); + } + + #[test] + fn test_openid_provider_issuers() { + assert_eq!( + OpenIdProvider::Google.issuers(), + &["https://accounts.google.com", "accounts.google.com"] + ); + assert_eq!( + OpenIdProvider::GitHubAuth.issuers(), + &["https://api.juno.build/auth/github"] + ); + assert_eq!( + OpenIdProvider::GitHubActions.issuers(), + &["https://token.actions.githubusercontent.com"] + ); + } + + #[test] + fn test_delegation_provider_to_openid_provider() { + assert_eq!( + OpenIdProvider::from(&OpenIdDelegationProvider::Google), + OpenIdProvider::Google + ); + assert_eq!( + OpenIdProvider::from(&OpenIdDelegationProvider::GitHub), + OpenIdProvider::GitHubAuth + ); + } + + #[test] + fn test_delegation_provider_jwks_urls() { + assert_eq!( + OpenIdDelegationProvider::Google.jwks_url(), + "https://www.googleapis.com/oauth2/v3/certs" + ); + assert_eq!( + OpenIdDelegationProvider::GitHub.jwks_url(), + "https://api.juno.build/v1/auth/certs" + ); + } + + #[test] + fn test_delegation_provider_issuers() { + assert_eq!( + OpenIdDelegationProvider::Google.issuers(), + &["https://accounts.google.com", "accounts.google.com"] + ); + assert_eq!( + OpenIdDelegationProvider::GitHub.issuers(), + &["https://api.juno.build/auth/github"] + ); + } + + #[test] + fn test_automation_provider_to_openid_provider() { + assert_eq!( + OpenIdProvider::from(&OpenIdAutomationProvider::GitHub), + OpenIdProvider::GitHubActions + ); + } + + #[test] + fn test_automation_provider_jwks_urls() { + assert_eq!( + OpenIdAutomationProvider::GitHub.jwks_url(), + "https://token.actions.githubusercontent.com/.well-known/jwks" + ); + } + + #[test] + fn test_automation_provider_issuers() { + assert_eq!( + OpenIdAutomationProvider::GitHub.issuers(), + &["https://token.actions.githubusercontent.com"] + ); + } + + #[test] + fn test_openid_certificate_init() { + let jwks = Jwks { keys: vec![] }; + let cert = OpenIdCertificate::init(&jwks); + + assert_eq!(cert.version, Some(1)); + assert_eq!(cert.created_at, cert.updated_at); + } + + #[test] + fn test_openid_certificate_update() { + let jwks = Jwks { keys: vec![] }; + let initial = OpenIdCertificate::init(&jwks); + + let new_jwks = Jwks { keys: vec![] }; + let updated = OpenIdCertificate::update(&initial, &new_jwks); + + assert_eq!(updated.version, Some(2)); + assert_eq!(updated.created_at, initial.created_at); + assert!(updated.updated_at >= initial.updated_at); + } + + #[test] + fn test_openid_provider_display() { + assert_eq!(format!("{}", OpenIdProvider::Google), "Google"); + assert_eq!(format!("{}", OpenIdProvider::GitHubAuth), "GitHub Proxy"); + assert_eq!(format!("{}", OpenIdProvider::GitHubActions), "GitHub Actions"); + } +} \ No newline at end of file diff --git a/src/libs/auth/src/openid/types.rs b/src/libs/auth/src/openid/types.rs index 35b6b6f238..86606e1a7a 100644 --- a/src/libs/auth/src/openid/types.rs +++ b/src/libs/auth/src/openid/types.rs @@ -21,6 +21,13 @@ pub mod provider { GitHub, } + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum OpenIdAutomationProvider { + GitHub, + } + #[derive(CandidType, Serialize, Deserialize, Clone)] pub struct OpenIdCertificate { pub jwks: Jwks, diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index ccae2a31c0..2e49eb58a3 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -5,12 +5,17 @@ pub mod state { use candid::CandidType; use serde::{Deserialize, Serialize}; use std::collections::HashMap; + use crate::state::types::automation::AutomationConfig; pub type Salt = [u8; 32]; #[derive(Default, CandidType, Serialize, Deserialize, Clone)] pub struct AuthenticationHeapState { + /// Configuration for user authentication via delegation (Internet Identity, Google, GitHub). + /// Note: Field name kept as "config" for backward compatibility during upgrades. pub config: AuthenticationConfig, + /// Configuration for CI/CD authentication. + pub automation: Option, pub salt: Option, pub openid: Option, } @@ -104,6 +109,54 @@ pub mod config { } } +pub mod automation { + use std::collections::{BTreeMap, HashMap}; + use candid::{CandidType, Deserialize, Principal}; + use serde::Serialize; + use junobuild_shared::types::state::{Timestamp, Version}; + use crate::openid::types::provider::{OpenIdAutomationProvider}; + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct AutomationConfig { + pub openid: Option, + pub version: Option, + pub created_at: Option, + pub updated_at: Option, + } + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct AutomationConfigOpenId { + pub providers: OpenIdAutomationProviders, + pub observatory_id: Option, + } + + pub type OpenIdAutomationProviders = BTreeMap; + + // Repository identifier for GitHub automation. + // Corresponds to the `repository` claim in GitHub OIDC tokens (e.g., "octo-org/octo-repo"). + // See: https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token + #[derive(CandidType, Serialize, Deserialize, Clone, Debug, Hash, Eq, PartialEq)] + pub struct RepositoryKey { + // Repository owner (e.g. "octo-org") + pub owner: String, + // Repository name (e.g. "octo-repo") + pub name: String, + } + + pub type OpenIdAutomationRepositories = HashMap; + + #[derive(Default, CandidType, Serialize, Deserialize, Clone, Debug)] + pub struct OpenIdAutomationProviderConfig { + pub repositories: OpenIdAutomationRepositories, + } + + #[derive(CandidType, Serialize, Deserialize, Clone, Debug)] + pub struct OpenIdAutomationRepositoryConfig { + // Optionally restrict to specific branches (e.g. ["main", "develop"]) + pub branches: Option>, + } +} + pub mod interface { use crate::state::types::config::{ AuthenticationConfigInternetIdentity, AuthenticationConfigOpenId, AuthenticationRules, From 4319cb0ab8e4e5471ad22c4b445149e2fb1935b9 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 10:50:54 +0100 Subject: [PATCH 25/64] chore: fmt --- src/libs/auth/src/openid/impls.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 9b17246913..56d73fa8ad 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,5 +1,7 @@ use crate::openid::jwt::types::cert::Jwks; -use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider}; +use crate::openid::types::provider::{ + OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider, +}; use ic_cdk::api::time; use junobuild_shared::data::version::next_version; use junobuild_shared::types::state::{Version, Versioned}; @@ -242,6 +244,9 @@ mod tests { fn test_openid_provider_display() { assert_eq!(format!("{}", OpenIdProvider::Google), "Google"); assert_eq!(format!("{}", OpenIdProvider::GitHubAuth), "GitHub Proxy"); - assert_eq!(format!("{}", OpenIdProvider::GitHubActions), "GitHub Actions"); + assert_eq!( + format!("{}", OpenIdProvider::GitHubActions), + "GitHub Actions" + ); } -} \ No newline at end of file +} From 3201b64a71abafb6da3f370951549a3936d9a64e Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 11:06:34 +0100 Subject: [PATCH 26/64] feat: init automation to none --- src/libs/auth/src/state/heap.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/auth/src/state/heap.rs b/src/libs/auth/src/state/heap.rs index 18cba3e7ca..8dd29829ab 100644 --- a/src/libs/auth/src/state/heap.rs +++ b/src/libs/auth/src/state/heap.rs @@ -23,6 +23,7 @@ fn insert_config_impl(config: &AuthenticationConfig, state: &mut Option { *state = Some(AuthenticationHeapState { config: config.clone(), + automation: None, salt: None, openid: None, }) @@ -48,6 +49,7 @@ fn insert_salt_impl(salt: &Salt, state: &mut Option) { None => { *state = Some(AuthenticationHeapState { config: AuthenticationConfig::default(), + automation: None, salt: Some(*salt), openid: None, }) From 8487ecf249597661ff739c608a7192abc9bc25b4 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 11:07:26 +0100 Subject: [PATCH 27/64] feat: make unsafe_find_jwt_provider generic again --- .../openid/credentials/delegation/verify.rs | 6 ++-- src/libs/auth/src/openid/impls.rs | 17 ++++++++-- src/libs/auth/src/openid/jwt/provider.rs | 33 ++++++++++--------- src/libs/auth/src/openid/jwt/types.rs | 6 ++++ 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 979232804d..2a585f3594 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -4,7 +4,7 @@ use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; -use crate::openid::jwt::{unsafe_find_jwt_delegation_provider, verify_openid_jwt}; +use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdDelegationProvider; use crate::openid::types::provider::OpenIdProvider; use crate::openid::utils::build_nonce; @@ -21,7 +21,7 @@ pub async fn verify_openid_credentials_with_jwks_renewal( providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdDelegationCredentialsResult { - let (delegation_provider, config) = unsafe_find_jwt_delegation_provider(providers, jwt) + let (delegation_provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; let provider: OpenIdProvider = (&delegation_provider).into(); @@ -39,7 +39,7 @@ pub fn verify_openid_credentials_with_cached_jwks( providers: &OpenIdAuthProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdDelegationCredentialsResult { - let (delegation_provider, config) = unsafe_find_jwt_delegation_provider(providers, jwt) + let (delegation_provider, config) = unsafe_find_jwt_provider(providers, jwt) .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; let provider: OpenIdProvider = (&delegation_provider).into(); diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 56d73fa8ad..b76fcbdfca 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,11 +1,10 @@ use crate::openid::jwt::types::cert::Jwks; -use crate::openid::types::provider::{ - OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider, -}; +use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider}; use ic_cdk::api::time; use junobuild_shared::data::version::next_version; use junobuild_shared::types::state::{Version, Versioned}; use std::fmt::{Display, Formatter, Result as FmtResult}; +use crate::openid::jwt::types::provider::JwtIssuers; impl OpenIdProvider { pub fn jwks_url(&self) -> &'static str { @@ -74,6 +73,18 @@ impl OpenIdAutomationProvider { } } +impl JwtIssuers for OpenIdDelegationProvider { + fn issuers(&self) -> &[&'static str] { + self.issuers() + } +} + +impl JwtIssuers for OpenIdAutomationProvider { + fn issuers(&self) -> &[&'static str] { + self.issuers() + } +} + impl Versioned for OpenIdCertificate { fn version(&self) -> Option { self.version diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 360babd0c5..060157aaef 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,16 +1,19 @@ +use std::collections::BTreeMap; use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::jwt::types::token::UnsafeClaims; -use crate::openid::types::provider::OpenIdDelegationProvider; -use crate::state::types::config::{OpenIdAuthProviderConfig, OpenIdAuthProviders}; use jsonwebtoken::dangerous; +use crate::openid::jwt::types::provider::JwtIssuers; /// ⚠️ **Warning:** This function decodes the JWT payload *without verifying its signature*. /// Use only to inspect claims (e.g., `iss`) before performing a verified decode. -pub fn unsafe_find_jwt_delegation_provider<'a>( - providers: &'a OpenIdAuthProviders, +pub fn unsafe_find_jwt_provider<'a, Provider, Config>( + providers: &'a BTreeMap, jwt: &str, -) -> Result<(OpenIdDelegationProvider, &'a OpenIdAuthProviderConfig), JwtFindProviderError> { +) -> Result<(Provider, &'a Config), JwtFindProviderError> +where + Provider: Clone + JwtIssuers, +{ // 1) Header sanity check decode_jwt_header(jwt).map_err(JwtFindProviderError::from)?; @@ -33,7 +36,7 @@ pub fn unsafe_find_jwt_delegation_provider<'a>( #[cfg(test)] mod tests { - use super::unsafe_find_jwt_delegation_provider; + use super::unsafe_find_jwt_provider; use crate::openid::jwt::types::errors::JwtFindProviderError; use crate::openid::types::provider::OpenIdDelegationProvider; use crate::state::types::config::{OpenIdAuthProviderConfig, OpenIdAuthProviders}; @@ -71,7 +74,7 @@ mod tests { let provs = providers_with_google(); let (provider, cfg) = - unsafe_find_jwt_delegation_provider(&provs, &jwt).expect("should match provider"); + unsafe_find_jwt_provider(&provs, &jwt).expect("should match provider"); assert_eq!(provider, OpenIdDelegationProvider::Google); assert_eq!(cfg.client_id, "client-123"); @@ -83,7 +86,7 @@ mod tests { let jwt = jwt_with(json!({"alg":"RS256"}), json!({"iss": iss})); let provs = providers_with_google(); - let (provider, _) = unsafe_find_jwt_delegation_provider(&provs, &jwt) + let (provider, _) = unsafe_find_jwt_provider(&provs, &jwt) .expect("should match even without typ"); assert_eq!(provider, OpenIdDelegationProvider::Google); } @@ -94,7 +97,7 @@ mod tests { let jwt = jwt_with(json!({"alg":"HS256","typ":"JWT"}), json!({"iss": iss})); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "alg"), @@ -111,7 +114,7 @@ mod tests { ); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); match err { JwtFindProviderError::BadClaim(f) => assert_eq!(f, "typ"), @@ -123,7 +126,7 @@ mod tests { fn returns_no_matching_provider_when_issuer_missing() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({})); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -134,7 +137,7 @@ mod tests { json!({"iss":"https://unknown.example.com"}), ); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } @@ -142,7 +145,7 @@ mod tests { fn malformed_token_is_badsig() { let jwt = "definitely-not-a-jwt"; let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -155,7 +158,7 @@ mod tests { let jwt = format!("{h}.{p}.{s}"); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::BadSig(_))); } @@ -163,7 +166,7 @@ mod tests { fn empty_iss_is_no_match() { let jwt = jwt_with(json!({"alg":"RS256","typ":"JWT"}), json!({"iss": ""})); let provs = providers_with_google(); - let err = unsafe_find_jwt_delegation_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_provider(&provs, &jwt).unwrap_err(); assert!(matches!(err, JwtFindProviderError::NoMatchingProvider)); } } diff --git a/src/libs/auth/src/openid/jwt/types.rs b/src/libs/auth/src/openid/jwt/types.rs index 30baa34a83..dedcc4eb23 100644 --- a/src/libs/auth/src/openid/jwt/types.rs +++ b/src/libs/auth/src/openid/jwt/types.rs @@ -193,3 +193,9 @@ pub(crate) mod errors { BadClaim(String), } } + +pub mod provider { + pub trait JwtIssuers { + fn issuers(&self) -> &[&'static str]; + } +} \ No newline at end of file From 5f5dd948b36c978bba356d21bc3473cf8b0ac9b8 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 11:09:17 +0100 Subject: [PATCH 28/64] chore: merge main --- src/libs/auth/src/openid/jwt/provider.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 2a291ae389..060157aaef 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -34,7 +34,6 @@ where Err(JwtFindProviderError::NoMatchingProvider) } - #[cfg(test)] mod tests { use super::unsafe_find_jwt_provider; From 17524bd7a6b967cdd12ac62283c9ef10c8bfb02d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 11:13:15 +0100 Subject: [PATCH 29/64] feat: verify with providers --- src/libs/auth/src/delegation/impls.rs | 2 +- .../src/openid/credentials/automation/verify.rs | 16 +++++++++++----- .../src/openid/credentials/delegation/types.rs | 15 --------------- .../src/openid/credentials/delegation/verify.rs | 2 +- src/libs/auth/src/openid/credentials/mod.rs | 1 + .../openid/credentials/{automation => }/types.rs | 9 +++++---- .../satellite/src/controllers/authenticate.rs | 8 ++++++-- 7 files changed, 25 insertions(+), 28 deletions(-) rename src/libs/auth/src/openid/credentials/{automation => }/types.rs (60%) diff --git a/src/libs/auth/src/delegation/impls.rs b/src/libs/auth/src/delegation/impls.rs index fbb44d7a98..c2aee21bf2 100644 --- a/src/libs/auth/src/delegation/impls.rs +++ b/src/libs/auth/src/delegation/impls.rs @@ -1,5 +1,5 @@ use crate::delegation::types::{GetDelegationError, PrepareDelegationError}; -use crate::openid::credentials::delegation::types::errors::VerifyOpenidCredentialsError; +use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; impl From for GetDelegationError { fn from(e: VerifyOpenidCredentialsError) -> Self { diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 146c2250cd..114611ace9 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -1,22 +1,28 @@ -use crate::openid::credentials::automation::types::errors::VerifyOpenidAutomationCredentialsError; +use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::get_or_refresh_jwks; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; -use crate::openid::jwt::verify_openid_jwt; +use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdProvider; +use crate::state::types::automation::OpenIdAutomationProviders; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdAutomationCredentialsResult = Result<(), VerifyOpenidAutomationCredentialsError>; +type VerifyOpenIdAutomationCredentialsResult = Result<(), VerifyOpenidCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, - provider: &OpenIdProvider, + providers: &OpenIdAutomationProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdAutomationCredentialsResult { + let (automation_provider, config) = unsafe_find_jwt_provider(providers, jwt) + .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; + + let provider: OpenIdProvider = (&automation_provider).into(); + let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) .await - .map_err(VerifyOpenidAutomationCredentialsError::GetOrFetchJwks)?; + .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; verify_openid_credentials(jwt, &jwks, &provider) } diff --git a/src/libs/auth/src/openid/credentials/delegation/types.rs b/src/libs/auth/src/openid/credentials/delegation/types.rs index af0991b32c..375d7599ab 100644 --- a/src/libs/auth/src/openid/credentials/delegation/types.rs +++ b/src/libs/auth/src/openid/credentials/delegation/types.rs @@ -17,18 +17,3 @@ pub mod interface { pub locale: Option, } } - -pub(crate) mod errors { - use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; - use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; - use candid::{CandidType, Deserialize}; - use serde::Serialize; - - #[derive(CandidType, Serialize, Deserialize, Debug)] - pub enum VerifyOpenidCredentialsError { - GetOrFetchJwks(GetOrRefreshJwksError), - GetCachedJwks, - JwtFindProvider(JwtFindProviderError), - JwtVerify(JwtVerifyError), - } -} diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 2a585f3594..e4b72a0711 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,4 +1,4 @@ -use crate::openid::credentials::delegation::types::errors::VerifyOpenidCredentialsError; +use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::credentials::delegation::types::interface::OpenIdCredential; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; diff --git a/src/libs/auth/src/openid/credentials/mod.rs b/src/libs/auth/src/openid/credentials/mod.rs index d766412ebc..c4602919fd 100644 --- a/src/libs/auth/src/openid/credentials/mod.rs +++ b/src/libs/auth/src/openid/credentials/mod.rs @@ -1,2 +1,3 @@ pub mod delegation; pub mod automation; +pub mod types; diff --git a/src/libs/auth/src/openid/credentials/automation/types.rs b/src/libs/auth/src/openid/credentials/types.rs similarity index 60% rename from src/libs/auth/src/openid/credentials/automation/types.rs rename to src/libs/auth/src/openid/credentials/types.rs index 9fd4d03ff1..40f964e922 100644 --- a/src/libs/auth/src/openid/credentials/automation/types.rs +++ b/src/libs/auth/src/openid/credentials/types.rs @@ -1,13 +1,14 @@ -pub mod errors { +pub(crate) mod errors { use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; - use crate::openid::jwt::types::errors::JwtVerifyError; + use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; use candid::{CandidType, Deserialize}; use serde::Serialize; #[derive(CandidType, Serialize, Deserialize, Debug)] - pub enum VerifyOpenidAutomationCredentialsError { + pub enum VerifyOpenidCredentialsError { GetOrFetchJwks(GetOrRefreshJwksError), GetCachedJwks, + JwtFindProvider(JwtFindProviderError), JwtVerify(JwtVerifyError), } -} +} \ No newline at end of file diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index cf2f49db1f..832486ebba 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -5,19 +5,23 @@ use crate::controllers::types::{ AuthenticateControllerResult, AuthenticationControllerError, OpenIdAuthenticateControllerArgs, }; use ic_cdk::api::time; -use junobuild_auth::openid::types::provider::OpenIdProvider; use junobuild_shared::segments::controllers::assert_controllers; use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; use std::cmp::min; use junobuild_auth::openid::credentials; +use junobuild_auth::state::get_providers; pub async fn openid_authenticate_controller( args: &OpenIdAuthenticateControllerArgs, + // TODO: Result> ) -> AuthenticateControllerResult { + // TODO: get_automation_providers + let providers = get_providers(&AuthHeap)?; + match credentials::automation::verify_openid_credentials_with_jwks_renewal( &args.jwt, - &OpenIdProvider::GitHubActions, + &providers, &AuthHeap, ) .await From cc8713e31920e3442c0aabc909d740e15f214249 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 13:45:16 +0100 Subject: [PATCH 30/64] feat: rework for consistency and similar pattern plus integration in state --- .../src/automation}/constants.rs | 4 +- src/libs/auth/src/automation/impls.rs | 17 +++++ src/libs/auth/src/automation/mod.rs | 7 +++ src/libs/auth/src/automation/prepare.rs | 31 ++++++++++ src/libs/auth/src/automation/types.rs | 43 +++++++++++++ .../auth/src/automation/utils/duration.rs | 25 ++++++++ src/libs/auth/src/automation/utils/mod.rs | 2 + src/libs/auth/src/automation/utils/scope.rs | 19 ++++++ src/libs/auth/src/lib.rs | 1 + .../openid/credentials/automation/verify.rs | 12 ++-- src/libs/auth/src/state/errors.rs | 2 + src/libs/auth/src/state/heap.rs | 31 +++++++++- src/libs/auth/src/state/mod.rs | 2 +- src/libs/auth/src/state/store.rs | 16 ++++- src/libs/auth/src/state/types.rs | 8 +++ src/libs/satellite/src/api/controllers.rs | 4 +- .../satellite/src/controllers/authenticate.rs | 62 ++++++++----------- .../satellite/src/controllers/automation.rs | 40 ++++++++++++ src/libs/satellite/src/controllers/mod.rs | 2 +- src/libs/satellite/src/controllers/types.rs | 20 ++---- src/libs/satellite/src/impls.rs | 6 +- src/libs/satellite/src/types.rs | 4 +- 22 files changed, 286 insertions(+), 72 deletions(-) rename src/libs/{satellite/src/controllers => auth/src/automation}/constants.rs (50%) create mode 100644 src/libs/auth/src/automation/impls.rs create mode 100644 src/libs/auth/src/automation/mod.rs create mode 100644 src/libs/auth/src/automation/prepare.rs create mode 100644 src/libs/auth/src/automation/types.rs create mode 100644 src/libs/auth/src/automation/utils/duration.rs create mode 100644 src/libs/auth/src/automation/utils/mod.rs create mode 100644 src/libs/auth/src/automation/utils/scope.rs create mode 100644 src/libs/satellite/src/controllers/automation.rs diff --git a/src/libs/satellite/src/controllers/constants.rs b/src/libs/auth/src/automation/constants.rs similarity index 50% rename from src/libs/satellite/src/controllers/constants.rs rename to src/libs/auth/src/automation/constants.rs index 7db3c782bc..d027c00359 100644 --- a/src/libs/satellite/src/controllers/constants.rs +++ b/src/libs/auth/src/automation/constants.rs @@ -1,7 +1,7 @@ const MINUTE_NS: u64 = 60 * 1_000_000_000; // 10 minutes in nanoseconds -pub const DEFAULT_CONTROLLER_DURATION_NS: u64 = 10 * MINUTE_NS; +pub const DEFAULT_EXPIRATION_PERIOD_NS: u64 = 10 * MINUTE_NS; // The maximum duration for a automation controller -pub const MAX_CONTROLLER_DURATION_NS: u64 = 60 * MINUTE_NS; +pub const MAX_EXPIRATION_PERIOD_NS: u64 = 60 * MINUTE_NS; diff --git a/src/libs/auth/src/automation/impls.rs b/src/libs/auth/src/automation/impls.rs new file mode 100644 index 0000000000..da847697ba --- /dev/null +++ b/src/libs/auth/src/automation/impls.rs @@ -0,0 +1,17 @@ +use crate::automation::types::PrepareAutomationError; +use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; + +impl From for PrepareAutomationError { + fn from(e: VerifyOpenidCredentialsError) -> Self { + match e { + VerifyOpenidCredentialsError::GetOrFetchJwks(err) => { + PrepareAutomationError::GetOrFetchJwks(err) + } + VerifyOpenidCredentialsError::GetCachedJwks => PrepareAutomationError::GetCachedJwks, + VerifyOpenidCredentialsError::JwtFindProvider(err) => { + PrepareAutomationError::JwtFindProvider(err) + } + VerifyOpenidCredentialsError::JwtVerify(err) => PrepareAutomationError::JwtVerify(err), + } + } +} \ No newline at end of file diff --git a/src/libs/auth/src/automation/mod.rs b/src/libs/auth/src/automation/mod.rs new file mode 100644 index 0000000000..f546f4b03c --- /dev/null +++ b/src/libs/auth/src/automation/mod.rs @@ -0,0 +1,7 @@ +mod prepare; +pub mod types; +mod utils; +mod constants; +mod impls; + +pub use prepare::*; \ No newline at end of file diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs new file mode 100644 index 0000000000..0390f77753 --- /dev/null +++ b/src/libs/auth/src/automation/prepare.rs @@ -0,0 +1,31 @@ +use junobuild_shared::segments::controllers::assert_controllers; +use junobuild_shared::types::state::ControllerId; +use crate::automation::types::{PrepareAutomationError, PrepareAutomationResult, PreparedAutomation, PreparedControllerAutomation}; +use crate::automation::utils::duration::build_expiration; +use crate::automation::utils::scope::build_scope; +use crate::openid::types::provider::{OpenIdAutomationProvider}; +use crate::strategies::{AuthHeapStrategy}; + +pub fn openid_prepare_automation( + controller_id: &ControllerId, + provider: &OpenIdAutomationProvider, + auth_heap: &impl AuthHeapStrategy, +) -> PrepareAutomationResult { + let controllers: [ControllerId; 1] = [controller_id.clone()]; + + assert_controllers(&controllers).map_err(PrepareAutomationError::InvalidController)?; + + // TODO: Assert do not exist + + let expires_at = build_expiration(provider, auth_heap); + + let scope = build_scope(provider, auth_heap); + + let controller: PreparedControllerAutomation = PreparedControllerAutomation { + id: controller_id.clone(), + expires_at, + scope + }; + + Ok(PreparedAutomation { controller }) +} \ No newline at end of file diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs new file mode 100644 index 0000000000..f1e45026e6 --- /dev/null +++ b/src/libs/auth/src/automation/types.rs @@ -0,0 +1,43 @@ +use candid::{CandidType, Deserialize}; +use serde::Serialize; +use junobuild_shared::types::interface::SetController; +use junobuild_shared::types::state::ControllerId; +use crate::delegation::types::SessionKey; +use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; +use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; +use crate::state::types::state::Salt; + +#[derive(CandidType, Serialize, Deserialize)] +pub struct OpenIdPrepareAutomationArgs { + pub jwt: String, + pub controller_id: ControllerId, +} + +pub type PrepareAutomationResult = Result; + +#[derive(CandidType, Serialize, Deserialize)] +pub struct PreparedAutomation { + pub controller: PreparedControllerAutomation, +} + +#[derive(CandidType, Serialize, Deserialize)] +pub struct PreparedControllerAutomation { + pub id: ControllerId, + pub scope: AutomationScope, + pub expires_at: u64, +} + +#[derive(CandidType, Serialize, Deserialize, Clone, Debug)] +pub enum AutomationScope { + Write, + Submit, +} + +#[derive(CandidType, Serialize, Deserialize, Debug)] +pub enum PrepareAutomationError { + InvalidController(String), + GetOrFetchJwks(GetOrRefreshJwksError), + GetCachedJwks, + JwtFindProvider(JwtFindProviderError), + JwtVerify(JwtVerifyError), +} \ No newline at end of file diff --git a/src/libs/auth/src/automation/utils/duration.rs b/src/libs/auth/src/automation/utils/duration.rs new file mode 100644 index 0000000000..d6b3c78e4b --- /dev/null +++ b/src/libs/auth/src/automation/utils/duration.rs @@ -0,0 +1,25 @@ +use crate::automation::constants::{DEFAULT_EXPIRATION_PERIOD_NS, MAX_EXPIRATION_PERIOD_NS}; +use crate::openid::types::provider::OpenIdAutomationProvider; +use crate::state::get_automation; +use crate::strategies::AuthHeapStrategy; +use ic_cdk::api::time; +use std::cmp::min; + +pub fn build_expiration( + provider: &OpenIdAutomationProvider, + auth_heap: &impl AuthHeapStrategy, +) -> u64 { + let max_time_to_live = get_automation(auth_heap) + .as_ref() + .and_then(|automation| automation.openid.as_ref()) + .and_then(|openid| openid.providers.get(provider)) + .and_then(|openid| openid.controller.as_ref()) + .and_then(|controller| controller.max_time_to_live); + + let controller_duration = min( + max_time_to_live.unwrap_or(DEFAULT_EXPIRATION_PERIOD_NS), + MAX_EXPIRATION_PERIOD_NS, + ); + + time().saturating_add(controller_duration) +} diff --git a/src/libs/auth/src/automation/utils/mod.rs b/src/libs/auth/src/automation/utils/mod.rs new file mode 100644 index 0000000000..25a801248a --- /dev/null +++ b/src/libs/auth/src/automation/utils/mod.rs @@ -0,0 +1,2 @@ +pub mod duration; +pub mod scope; \ No newline at end of file diff --git a/src/libs/auth/src/automation/utils/scope.rs b/src/libs/auth/src/automation/utils/scope.rs new file mode 100644 index 0000000000..aa579d9040 --- /dev/null +++ b/src/libs/auth/src/automation/utils/scope.rs @@ -0,0 +1,19 @@ +use crate::automation::types::AutomationScope; +use crate::openid::types::provider::{OpenIdAutomationProvider}; +use crate::state::get_automation; +use crate::strategies::AuthHeapStrategy; + +// We default to AutomationScope::Write because practically that's what most developers use. +// i.e. most developers expect their GitHub Actions build to take effect +pub fn build_scope( + provider: &OpenIdAutomationProvider, + auth_heap: &impl AuthHeapStrategy, +) -> AutomationScope { + get_automation(auth_heap) + .as_ref() + .and_then(|automation| automation.openid.as_ref()) + .and_then(|openid| openid.providers.get(provider)) + .and_then(|openid| openid.controller.as_ref()) + .and_then(|controller| controller.scope.clone()) + .unwrap_or(AutomationScope::Write) +} \ No newline at end of file diff --git a/src/libs/auth/src/lib.rs b/src/libs/auth/src/lib.rs index 968d88c334..88bb97093e 100644 --- a/src/libs/auth/src/lib.rs +++ b/src/libs/auth/src/lib.rs @@ -4,5 +4,6 @@ pub mod profile; mod random; pub mod state; pub mod strategies; +pub mod automation; pub use state::errors; diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 114611ace9..1ff1f9b8ee 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -4,11 +4,11 @@ use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; -use crate::openid::types::provider::OpenIdProvider; +use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; use crate::state::types::automation::OpenIdAutomationProviders; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdAutomationCredentialsResult = Result<(), VerifyOpenidCredentialsError>; +type VerifyOpenIdAutomationCredentialsResult = Result; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, @@ -24,13 +24,13 @@ pub async fn verify_openid_credentials_with_jwks_renewal( .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &provider) + verify_openid_credentials(jwt, &jwks, &automation_provider) } fn verify_openid_credentials( jwt: &str, jwks: &Jwks, - provider: &OpenIdProvider, + provider: &OpenIdAutomationProvider, ) -> VerifyOpenIdAutomationCredentialsResult { let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { // if claims.aud != client_id.as_str() { @@ -61,7 +61,7 @@ fn verify_openid_credentials( &assert_audience, &assert_no_replay, ) - .map_err(VerifyOpenidAutomationCredentialsError::JwtVerify)?; + .map_err(VerifyOpenidCredentialsError::JwtVerify)?; - Ok(()) + Ok(provider.clone()) } diff --git a/src/libs/auth/src/state/errors.rs b/src/libs/auth/src/state/errors.rs index 77c596c6ae..e630223e2b 100644 --- a/src/libs/auth/src/state/errors.rs +++ b/src/libs/auth/src/state/errors.rs @@ -2,5 +2,7 @@ pub const JUNO_AUTH_ERROR_INVALID_ORIGIN: &str = "juno.auth.error.invalid_origin"; // No authentication configuration found. pub const JUNO_AUTH_ERROR_NOT_CONFIGURED: &str = "juno.auth.error.not_configured"; +// No automation configuration found. +pub const JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED: &str = "juno.auth.error.automation_not_configured"; // Authentication with OpenId disabled. pub const JUNO_AUTH_ERROR_OPENID_DISABLED: &str = "juno.auth.error.openid_disabled"; diff --git a/src/libs/auth/src/state/heap.rs b/src/libs/auth/src/state/heap.rs index 8dd29829ab..4f0e6b8443 100644 --- a/src/libs/auth/src/state/heap.rs +++ b/src/libs/auth/src/state/heap.rs @@ -4,7 +4,7 @@ use crate::state::types::state::Salt; use crate::state::types::state::{AuthenticationHeapState, OpenIdCachedCertificate, OpenIdState}; use crate::strategies::AuthHeapStrategy; use std::collections::hash_map::Entry; - +use crate::state::types::automation::AutomationConfig; // --------------------------------------------------------- // Config // --------------------------------------------------------- @@ -32,6 +32,35 @@ fn insert_config_impl(config: &AuthenticationConfig, state: &mut Option Option { + auth_heap + .with_auth_state(|authentication| { + authentication.as_ref().and_then(|auth| auth.automation.clone()) + }) +} + +pub fn insert_automation(auth_heap: &impl AuthHeapStrategy, automation: &Option) { + auth_heap.with_auth_state_mut(|authentication| insert_automation_impl(automation, authentication)) +} + +fn insert_automation_impl(automation: &Option, state: &mut Option) { + match state { + None => { + *state = Some(AuthenticationHeapState { + config: AuthenticationConfig::default(), + automation: automation.clone(), + salt: None, + openid: None, + }) + } + Some(state) => state.automation = automation.clone(), + } +} + // --------------------------------------------------------- // Salt // --------------------------------------------------------- diff --git a/src/libs/auth/src/state/mod.rs b/src/libs/auth/src/state/mod.rs index 7d5fdf1328..2989008e1c 100644 --- a/src/libs/auth/src/state/mod.rs +++ b/src/libs/auth/src/state/mod.rs @@ -10,7 +10,7 @@ pub mod types; pub use heap::{ cache_certificate, get_cached_certificate, get_config, get_openid_state, get_salt, insert_salt, - record_fetch_attempt, + record_fetch_attempt, get_automation }; pub use runtime::*; pub use store::*; diff --git a/src/libs/auth/src/state/store.rs b/src/libs/auth/src/state/store.rs index 7e727e12f9..fb990453ef 100644 --- a/src/libs/auth/src/state/store.rs +++ b/src/libs/auth/src/state/store.rs @@ -1,6 +1,6 @@ -use crate::errors::{JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISABLED}; +use crate::errors::{JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED, JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISABLED}; use crate::state::assert::assert_set_config; -use crate::state::heap::get_config; +use crate::state::heap::{get_automation, get_config}; use crate::state::heap::insert_config; use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProviders}; use crate::state::types::interface::SetAuthenticationConfig; @@ -8,6 +8,7 @@ use crate::state::{get_salt, insert_salt}; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::print; use junobuild_shared::random::raw_rand; +use crate::state::types::automation::OpenIdAutomationProviders; pub fn set_config( auth_heap: &impl AuthHeapStrategy, @@ -56,3 +57,14 @@ pub fn get_auth_providers( Ok(openid.providers.clone()) } + +pub fn get_automation_providers( + auth_heap: &impl AuthHeapStrategy, +) -> Result { + let config = get_automation(auth_heap).ok_or(JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED.to_string())?; + let openid = config + .openid + .ok_or(JUNO_AUTH_ERROR_OPENID_DISABLED.to_string())?; + + Ok(openid.providers.clone()) +} diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index 2e49eb58a3..fa782049ae 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -114,6 +114,7 @@ pub mod automation { use candid::{CandidType, Deserialize, Principal}; use serde::Serialize; use junobuild_shared::types::state::{Timestamp, Version}; + use crate::automation::types::AutomationScope; use crate::openid::types::provider::{OpenIdAutomationProvider}; #[derive(Default, CandidType, Serialize, Deserialize, Clone)] @@ -148,6 +149,7 @@ pub mod automation { #[derive(Default, CandidType, Serialize, Deserialize, Clone, Debug)] pub struct OpenIdAutomationProviderConfig { pub repositories: OpenIdAutomationRepositories, + pub controller: Option, } #[derive(CandidType, Serialize, Deserialize, Clone, Debug)] @@ -155,6 +157,12 @@ pub mod automation { // Optionally restrict to specific branches (e.g. ["main", "develop"]) pub branches: Option>, } + + #[derive(CandidType, Serialize, Deserialize, Clone, Debug)] + pub struct OpenIdAutomationProviderControllerConfig { + pub scope: Option, + pub max_time_to_live: Option, + } } pub mod interface { diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index 76a2e049c2..f2ddb7b89f 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -1,6 +1,6 @@ use crate::controllers::openid_authenticate_controller; use crate::controllers::store::{delete_controllers, set_controllers as set_controllers_store}; -use crate::controllers::types::{AuthenticateControllerArgs, AuthenticateControllerResult}; +use crate::controllers::types::{AuthenticateControllerArgs, AuthenticateAutomationResult}; use crate::{get_admin_controllers, get_controllers}; use ic_cdk::trap; use junobuild_shared::constants::shared::MAX_NUMBER_OF_SATELLITE_CONTROLLERS; @@ -54,7 +54,7 @@ pub fn list_controllers() -> Controllers { pub async fn authenticate_controller( args: AuthenticateControllerArgs, -) -> AuthenticateControllerResult { +) -> AuthenticateAutomationResult { match args { AuthenticateControllerArgs::OpenId(args) => openid_authenticate_controller(&args).await, } diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs index 832486ebba..071c3acf57 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -1,54 +1,42 @@ use crate::auth::strategy_impls::AuthHeap; -use crate::controllers::constants::{DEFAULT_CONTROLLER_DURATION_NS, MAX_CONTROLLER_DURATION_NS}; use crate::controllers::store::set_controllers; use crate::controllers::types::{ - AuthenticateControllerResult, AuthenticationControllerError, OpenIdAuthenticateControllerArgs, + AuthenticateAutomationResult, AuthenticationAutomationError, }; -use ic_cdk::api::time; -use junobuild_shared::segments::controllers::assert_controllers; +use junobuild_auth::state::{get_automation_providers}; use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; -use std::cmp::min; -use junobuild_auth::openid::credentials; -use junobuild_auth::state::get_providers; +use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PreparedAutomation}; +use crate::controllers::automation; pub async fn openid_authenticate_controller( - args: &OpenIdAuthenticateControllerArgs, + args: &OpenIdPrepareAutomationArgs, // TODO: Result> -) -> AuthenticateControllerResult { - // TODO: get_automation_providers - let providers = get_providers(&AuthHeap)?; - - match credentials::automation::verify_openid_credentials_with_jwks_renewal( - &args.jwt, - &providers, - &AuthHeap, - ) - .await - { - Ok(_) => register_controller(args) - .map_err(|err| AuthenticationControllerError::RegisterController(err.to_string())), - Err(err) => Err(AuthenticationControllerError::VerifyOpenIdCredentials(err)), - } -} - -fn register_controller(args: &OpenIdAuthenticateControllerArgs) -> Result<(), String> { - let controllers: [ControllerId; 1] = [args.controller_id.clone()]; - - assert_controllers(&controllers)?; +) -> Result { + let providers = get_automation_providers(&AuthHeap)?; + + // TODO: rate? + + let prepared_automation = automation::openid_prepare_delegation(args, &providers).await; + + let result = match prepared_automation { + Ok((automation, _ , __)) => { + register_controller(&automation); + Ok(()) + } + Err(err) => Err(AuthenticationAutomationError::PrepareAutomation(err)), + }; - // TODO: Assert do not exist + Ok(result) +} - let expires_at = min( - args.max_time_to_live - .unwrap_or(DEFAULT_CONTROLLER_DURATION_NS), - MAX_CONTROLLER_DURATION_NS, - ); +fn register_controller(prepared_automation: &PreparedAutomation) { + let controllers: [ControllerId; 1] = [prepared_automation.controller.id.clone()]; let controller: SetController = SetController { - scope: args.scope.clone().into(), + scope: prepared_automation.controller.scope.clone().into(), metadata: args.metadata.clone(), - expires_at: Some(time().saturating_add(expires_at)), + expires_at: Some(prepared_automation.controller.expires_at), }; set_controllers(&controllers, &controller); diff --git a/src/libs/satellite/src/controllers/automation.rs b/src/libs/satellite/src/controllers/automation.rs new file mode 100644 index 0000000000..c1595d5c27 --- /dev/null +++ b/src/libs/satellite/src/controllers/automation.rs @@ -0,0 +1,40 @@ +use junobuild_auth::automation; +use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, PreparedDelegation}; +use junobuild_auth::openid::credentials; +use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::types::provider::OpenIdDelegationProvider; +use junobuild_auth::state::types::automation::OpenIdAutomationProviders; +use crate::auth::strategy_impls::AuthHeap; + +pub type OpenIdPrepareAutomationResult = Result< + ( + PreparedAutomation, + OpenIdDelegationProvider, + // TODO: credential + OpenIdCredential, + ), + PrepareAutomationError, +>; + +pub async fn openid_prepare_delegation( + args: &OpenIdPrepareAutomationArgs, + providers: &OpenIdAutomationProviders, +) -> OpenIdPrepareAutomationResult { + let provider = + match credentials::automation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareAutomationError::from(err)), + }; + + let result = automation::openid_prepare_automation( + &args.controller_id, + &provider, + &AuthHeap, + ); + + result.map(|prepared_delegation| (prepared_delegation, provider, credential)) +} diff --git a/src/libs/satellite/src/controllers/mod.rs b/src/libs/satellite/src/controllers/mod.rs index a1406ae0cf..d85fb30397 100644 --- a/src/libs/satellite/src/controllers/mod.rs +++ b/src/libs/satellite/src/controllers/mod.rs @@ -1,7 +1,7 @@ mod authenticate; -mod constants; mod impls; pub mod store; pub mod types; +mod automation; pub use authenticate::*; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/controllers/types.rs index b19fb3667c..3707bfa2d1 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/controllers/types.rs @@ -1,20 +1,10 @@ use candid::{CandidType, Deserialize}; -use junobuild_auth::openid::credentials::automation::types::errors::VerifyOpenidAutomationCredentialsError; -use junobuild_shared::types::state::{ControllerId, Metadata}; use serde::Serialize; +use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PrepareAutomationError}; #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticateControllerArgs { - OpenId(OpenIdAuthenticateControllerArgs), -} - -#[derive(CandidType, Serialize, Deserialize)] -pub struct OpenIdAuthenticateControllerArgs { - pub jwt: String, - pub controller_id: ControllerId, - pub scope: AutomationScope, - pub metadata: Metadata, - pub max_time_to_live: Option, + OpenId(OpenIdPrepareAutomationArgs), } #[derive(CandidType, Serialize, Deserialize, Clone)] @@ -24,9 +14,9 @@ pub enum AutomationScope { } #[derive(CandidType, Serialize, Deserialize)] -pub enum AuthenticationControllerError { - VerifyOpenIdCredentials(VerifyOpenidAutomationCredentialsError), +pub enum AuthenticationAutomationError { + PrepareAutomation(PrepareAutomationError), RegisterController(String), } -pub type AuthenticateControllerResult = Result<(), AuthenticationControllerError>; +pub type AuthenticateAutomationResult = Result<(), AuthenticationAutomationError>; diff --git a/src/libs/satellite/src/impls.rs b/src/libs/satellite/src/impls.rs index c66f4fd33a..8990b9ceb4 100644 --- a/src/libs/satellite/src/impls.rs +++ b/src/libs/satellite/src/impls.rs @@ -1,4 +1,4 @@ -use crate::controllers::types::AuthenticateControllerResult; +use crate::controllers::types::AuthenticateAutomationResult; use crate::memory::internal::init_stable_state; use crate::types::interface::{ AuthenticateControllerResultResponse, AuthenticateResultResponse, AuthenticationResult, @@ -49,8 +49,8 @@ impl From for AuthenticateResultResponse { } } -impl From for AuthenticateControllerResultResponse { - fn from(r: AuthenticateControllerResult) -> Self { +impl From for AuthenticateControllerResultResponse { + fn from(r: AuthenticateAutomationResult) -> Self { match r { Ok(v) => Self::Ok(v), Err(e) => Self::Err(e), diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index edc724250b..3c83cdc125 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -56,7 +56,7 @@ pub mod state { } pub mod interface { - use crate::controllers::types::AuthenticationControllerError; + use crate::controllers::types::AuthenticationAutomationError; use crate::db::types::config::DbConfig; use crate::Doc; use candid::CandidType; @@ -123,7 +123,7 @@ pub mod interface { #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticateControllerResultResponse { Ok(()), - Err(AuthenticationControllerError), + Err(AuthenticationAutomationError), } } From a0319b5243b4de7ec28e3b0dc31383de0bb2b73c Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 13:56:10 +0100 Subject: [PATCH 31/64] feat: move in mod --- src/libs/satellite/src/api/automation.rs | 13 ++++++++ src/libs/satellite/src/api/controllers.rs | 10 ------ src/libs/satellite/src/api/mod.rs | 1 + .../authenticate.rs | 18 +++++------ .../{controllers => automation}/automation.rs | 31 +++++++++---------- .../src/{controllers => automation}/impls.rs | 2 +- src/libs/satellite/src/automation/mod.rs | 4 +++ .../src/{controllers => automation}/types.rs | 4 +-- src/libs/satellite/src/controllers/mod.rs | 6 ---- src/libs/satellite/src/impls.rs | 6 ++-- src/libs/satellite/src/lib.rs | 23 +++++++------- src/libs/satellite/src/types.rs | 4 +-- 12 files changed, 60 insertions(+), 62 deletions(-) create mode 100644 src/libs/satellite/src/api/automation.rs rename src/libs/satellite/src/{controllers => automation}/authenticate.rs (77%) rename src/libs/satellite/src/{controllers => automation}/automation.rs (55%) rename src/libs/satellite/src/{controllers => automation}/impls.rs (86%) create mode 100644 src/libs/satellite/src/automation/mod.rs rename src/libs/satellite/src/{controllers => automation}/types.rs (94%) diff --git a/src/libs/satellite/src/api/automation.rs b/src/libs/satellite/src/api/automation.rs new file mode 100644 index 0000000000..d46658c382 --- /dev/null +++ b/src/libs/satellite/src/api/automation.rs @@ -0,0 +1,13 @@ +use crate::automation::authenticate::openid_authenticate_automation; +use crate::automation::types::{AuthenticateAutomationArgs, AuthenticateAutomationResult}; +use junobuild_shared::ic::UnwrapOrTrap; + +pub async fn authenticate_automation( + args: AuthenticateAutomationArgs, +) -> AuthenticateAutomationResult { + match args { + AuthenticateAutomationArgs::OpenId(args) => { + openid_authenticate_automation(&args).await.unwrap_or_trap() + } + } +} diff --git a/src/libs/satellite/src/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index f2ddb7b89f..d11d23cdfb 100644 --- a/src/libs/satellite/src/api/controllers.rs +++ b/src/libs/satellite/src/api/controllers.rs @@ -1,6 +1,4 @@ -use crate::controllers::openid_authenticate_controller; use crate::controllers::store::{delete_controllers, set_controllers as set_controllers_store}; -use crate::controllers::types::{AuthenticateControllerArgs, AuthenticateAutomationResult}; use crate::{get_admin_controllers, get_controllers}; use ic_cdk::trap; use junobuild_shared::constants::shared::MAX_NUMBER_OF_SATELLITE_CONTROLLERS; @@ -51,11 +49,3 @@ pub fn del_controllers( pub fn list_controllers() -> Controllers { get_controllers() } - -pub async fn authenticate_controller( - args: AuthenticateControllerArgs, -) -> AuthenticateAutomationResult { - match args { - AuthenticateControllerArgs::OpenId(args) => openid_authenticate_controller(&args).await, - } -} diff --git a/src/libs/satellite/src/api/mod.rs b/src/libs/satellite/src/api/mod.rs index 061385cc3f..b5da645856 100644 --- a/src/libs/satellite/src/api/mod.rs +++ b/src/libs/satellite/src/api/mod.rs @@ -1,4 +1,5 @@ pub mod auth; +pub mod automation; pub mod cdn; pub mod config; pub mod controllers; diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs similarity index 77% rename from src/libs/satellite/src/controllers/authenticate.rs rename to src/libs/satellite/src/automation/authenticate.rs index 071c3acf57..21ceab530a 100644 --- a/src/libs/satellite/src/controllers/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -1,26 +1,24 @@ use crate::auth::strategy_impls::AuthHeap; +use crate::automation::automation; +use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; use crate::controllers::store::set_controllers; -use crate::controllers::types::{ - AuthenticateAutomationResult, AuthenticationAutomationError, -}; -use junobuild_auth::state::{get_automation_providers}; +use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PreparedAutomation}; +use junobuild_auth::state::get_automation_providers; use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; -use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PreparedAutomation}; -use crate::controllers::automation; -pub async fn openid_authenticate_controller( +pub async fn openid_authenticate_automation( args: &OpenIdPrepareAutomationArgs, // TODO: Result> ) -> Result { let providers = get_automation_providers(&AuthHeap)?; - + // TODO: rate? - let prepared_automation = automation::openid_prepare_delegation(args, &providers).await; + let prepared_automation = automation::openid_prepare_automation(args, &providers).await; let result = match prepared_automation { - Ok((automation, _ , __)) => { + Ok((automation, _, __)) => { register_controller(&automation); Ok(()) } diff --git a/src/libs/satellite/src/controllers/automation.rs b/src/libs/satellite/src/automation/automation.rs similarity index 55% rename from src/libs/satellite/src/controllers/automation.rs rename to src/libs/satellite/src/automation/automation.rs index c1595d5c27..3426697fcf 100644 --- a/src/libs/satellite/src/controllers/automation.rs +++ b/src/libs/satellite/src/automation/automation.rs @@ -1,10 +1,12 @@ +use crate::auth::strategy_impls::AuthHeap; use junobuild_auth::automation; -use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, PreparedDelegation}; +use junobuild_auth::automation::types::{ + OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, PreparedDelegation, +}; use junobuild_auth::openid::credentials; use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::automation::OpenIdAutomationProviders; -use crate::auth::strategy_impls::AuthHeap; pub type OpenIdPrepareAutomationResult = Result< ( @@ -16,25 +18,20 @@ pub type OpenIdPrepareAutomationResult = Result< PrepareAutomationError, >; -pub async fn openid_prepare_delegation( +pub async fn openid_prepare_automation( args: &OpenIdPrepareAutomationArgs, providers: &OpenIdAutomationProviders, ) -> OpenIdPrepareAutomationResult { - let provider = - match credentials::automation::verify_openid_credentials_with_jwks_renewal( - &args.jwt, providers, &AuthHeap, - ) - .await - { - Ok(value) => value, - Err(err) => return Err(PrepareAutomationError::from(err)), - }; + let provider = match credentials::automation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareAutomationError::from(err)), + }; - let result = automation::openid_prepare_automation( - &args.controller_id, - &provider, - &AuthHeap, - ); + let result = automation::openid_prepare_automation(&args.controller_id, &provider, &AuthHeap); result.map(|prepared_delegation| (prepared_delegation, provider, credential)) } diff --git a/src/libs/satellite/src/controllers/impls.rs b/src/libs/satellite/src/automation/impls.rs similarity index 86% rename from src/libs/satellite/src/controllers/impls.rs rename to src/libs/satellite/src/automation/impls.rs index d588dca7bc..971d7e0769 100644 --- a/src/libs/satellite/src/controllers/impls.rs +++ b/src/libs/satellite/src/automation/impls.rs @@ -1,4 +1,4 @@ -use crate::controllers::types::AutomationScope; +use crate::automation::types::AutomationScope; use junobuild_shared::types::state::ControllerScope; impl From for ControllerScope { diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs new file mode 100644 index 0000000000..7f2c56e0eb --- /dev/null +++ b/src/libs/satellite/src/automation/mod.rs @@ -0,0 +1,4 @@ +pub mod authenticate; +mod automation; +mod impls; +pub mod types; diff --git a/src/libs/satellite/src/controllers/types.rs b/src/libs/satellite/src/automation/types.rs similarity index 94% rename from src/libs/satellite/src/controllers/types.rs rename to src/libs/satellite/src/automation/types.rs index 3707bfa2d1..00f6496828 100644 --- a/src/libs/satellite/src/controllers/types.rs +++ b/src/libs/satellite/src/automation/types.rs @@ -1,9 +1,9 @@ use candid::{CandidType, Deserialize}; -use serde::Serialize; use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PrepareAutomationError}; +use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] -pub enum AuthenticateControllerArgs { +pub enum AuthenticateAutomationArgs { OpenId(OpenIdPrepareAutomationArgs), } diff --git a/src/libs/satellite/src/controllers/mod.rs b/src/libs/satellite/src/controllers/mod.rs index d85fb30397..55c88cbf3d 100644 --- a/src/libs/satellite/src/controllers/mod.rs +++ b/src/libs/satellite/src/controllers/mod.rs @@ -1,7 +1 @@ -mod authenticate; -mod impls; pub mod store; -pub mod types; -mod automation; - -pub use authenticate::*; diff --git a/src/libs/satellite/src/impls.rs b/src/libs/satellite/src/impls.rs index 8990b9ceb4..3e2abd2eff 100644 --- a/src/libs/satellite/src/impls.rs +++ b/src/libs/satellite/src/impls.rs @@ -1,7 +1,7 @@ -use crate::controllers::types::AuthenticateAutomationResult; +use crate::automation::types::AuthenticateAutomationResult; use crate::memory::internal::init_stable_state; use crate::types::interface::{ - AuthenticateControllerResultResponse, AuthenticateResultResponse, AuthenticationResult, + AuthenticateAutomationResultResponse, AuthenticateResultResponse, AuthenticationResult, GetDelegationResultResponse, }; use crate::types::state::{CollectionType, HeapState, RuntimeState, State}; @@ -49,7 +49,7 @@ impl From for AuthenticateResultResponse { } } -impl From for AuthenticateControllerResultResponse { +impl From for AuthenticateAutomationResultResponse { fn from(r: AuthenticateAutomationResult) -> Self { match r { Ok(v) => Self::Ok(v), diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index f81da3a632..ef2c479a64 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -3,6 +3,7 @@ mod api; mod assets; mod auth; +mod automation; mod certification; mod controllers; mod db; @@ -18,17 +19,17 @@ mod sdk; mod types; mod user; -use crate::controllers::types::AuthenticateControllerArgs; use crate::db::types::config::DbConfig; use crate::db::types::interface::SetDbConfig; use crate::guards::{ caller_is_admin_controller, caller_is_controller, caller_is_controller_with_write, }; use crate::types::interface::{ - AuthenticateControllerResultResponse, AuthenticateResultResponse, AuthenticationArgs, Config, + AuthenticateAutomationResultResponse, AuthenticateResultResponse, AuthenticationArgs, Config, DeleteProposalAssets, GetDelegationArgs, GetDelegationResultResponse, }; use crate::types::state::CollectionType; +use automation::types::AuthenticateAutomationArgs; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; use junobuild_auth::state::types::config::AuthenticationConfig; use junobuild_auth::state::types::interface::SetAuthenticationConfig; @@ -177,6 +178,14 @@ pub fn get_delegation(args: GetDelegationArgs) -> GetDelegationResultResponse { api::auth::get_delegation(&args).into() } +#[doc(hidden)] +#[update] +pub async fn authenticate_automation( + args: AuthenticateAutomationArgs, +) -> AuthenticateAutomationResultResponse { + api::automation::authenticate_automation(args).await.into() +} + // --------------------------------------------------------- // Rules // --------------------------------------------------------- @@ -233,14 +242,6 @@ pub fn list_controllers() -> Controllers { api::controllers::list_controllers() } -#[doc(hidden)] -#[update] -pub async fn authenticate_controller( - args: AuthenticateControllerArgs, -) -> AuthenticateControllerResultResponse { - api::controllers::authenticate_controller(args).await.into() -} - // --------------------------------------------------------- // Proposal // --------------------------------------------------------- @@ -549,7 +550,7 @@ pub fn memory_size() -> MemorySize { macro_rules! include_satellite { () => { use junobuild_satellite::{ - authenticate, authenticate_controller, commit_asset_upload, commit_proposal, + authenticate, authenticate_automation, commit_asset_upload, commit_proposal, commit_proposal_asset_upload, commit_proposal_many_assets_upload, count_assets, count_collection_assets, count_collection_docs, count_docs, count_proposals, del_asset, del_assets, del_controllers, del_custom_domain, del_doc, del_docs, del_filtered_assets, diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index 3c83cdc125..3e5f975faa 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -56,7 +56,7 @@ pub mod state { } pub mod interface { - use crate::controllers::types::AuthenticationAutomationError; + use crate::automation::types::AuthenticationAutomationError; use crate::db::types::config::DbConfig; use crate::Doc; use candid::CandidType; @@ -121,7 +121,7 @@ pub mod interface { } #[derive(CandidType, Serialize, Deserialize)] - pub enum AuthenticateControllerResultResponse { + pub enum AuthenticateAutomationResultResponse { Ok(()), Err(AuthenticationAutomationError), } From 254af33e78db414a3caa37fbeede8dda4fcb9ac0 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 29 Jan 2026 13:57:00 +0100 Subject: [PATCH 32/64] fix: deleted types --- src/libs/auth/src/openid/credentials/automation/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/auth/src/openid/credentials/automation/mod.rs b/src/libs/auth/src/openid/credentials/automation/mod.rs index d9a483abcc..06e9b633bf 100644 --- a/src/libs/auth/src/openid/credentials/automation/mod.rs +++ b/src/libs/auth/src/openid/credentials/automation/mod.rs @@ -1,4 +1,3 @@ -pub mod types; mod verify; pub use verify::*; From 8b6b5b2b02c0e31708d3cbae207242c46c518c4b Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 06:49:06 +0100 Subject: [PATCH 33/64] fix: issues (expect remaining credential todo) --- src/libs/auth/src/automation/impls.rs | 14 +++++++++++-- src/libs/auth/src/automation/mod.rs | 6 +++--- src/libs/auth/src/automation/prepare.rs | 21 +++++++++++-------- src/libs/auth/src/automation/types.rs | 10 ++++----- src/libs/auth/src/automation/utils/mod.rs | 2 +- src/libs/auth/src/automation/utils/scope.rs | 4 ++-- src/libs/auth/src/lib.rs | 2 +- .../openid/credentials/automation/verify.rs | 3 ++- .../openid/credentials/delegation/verify.rs | 2 +- src/libs/auth/src/openid/credentials/mod.rs | 2 +- src/libs/auth/src/openid/impls.rs | 8 ++++--- src/libs/auth/src/openid/jwt/provider.rs | 8 +++---- src/libs/auth/src/openid/jwt/types.rs | 2 +- src/libs/auth/src/state/errors.rs | 3 ++- src/libs/auth/src/state/heap.rs | 19 ++++++++++------- src/libs/auth/src/state/mod.rs | 4 ++-- src/libs/auth/src/state/store.rs | 12 +++++++---- src/libs/auth/src/state/types.rs | 16 +++++++------- .../satellite/src/automation/authenticate.rs | 5 ++--- .../satellite/src/automation/automation.rs | 3 +-- src/libs/satellite/src/automation/impls.rs | 11 ---------- src/libs/satellite/src/automation/mod.rs | 1 - src/libs/satellite/src/automation/types.rs | 6 ------ 23 files changed, 86 insertions(+), 78 deletions(-) delete mode 100644 src/libs/satellite/src/automation/impls.rs diff --git a/src/libs/auth/src/automation/impls.rs b/src/libs/auth/src/automation/impls.rs index da847697ba..2f8f284a3c 100644 --- a/src/libs/auth/src/automation/impls.rs +++ b/src/libs/auth/src/automation/impls.rs @@ -1,5 +1,6 @@ -use crate::automation::types::PrepareAutomationError; +use crate::automation::types::{AutomationScope, PrepareAutomationError}; use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; +use junobuild_shared::types::state::ControllerScope; impl From for PrepareAutomationError { fn from(e: VerifyOpenidCredentialsError) -> Self { @@ -14,4 +15,13 @@ impl From for PrepareAutomationError { VerifyOpenidCredentialsError::JwtVerify(err) => PrepareAutomationError::JwtVerify(err), } } -} \ No newline at end of file +} + +impl From for ControllerScope { + fn from(scope: AutomationScope) -> Self { + match scope { + AutomationScope::Write => ControllerScope::Write, + AutomationScope::Submit => ControllerScope::Submit, + } + } +} diff --git a/src/libs/auth/src/automation/mod.rs b/src/libs/auth/src/automation/mod.rs index f546f4b03c..4db9a9240a 100644 --- a/src/libs/auth/src/automation/mod.rs +++ b/src/libs/auth/src/automation/mod.rs @@ -1,7 +1,7 @@ +mod constants; +mod impls; mod prepare; pub mod types; mod utils; -mod constants; -mod impls; -pub use prepare::*; \ No newline at end of file +pub use prepare::*; diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs index 0390f77753..cfb69840aa 100644 --- a/src/libs/auth/src/automation/prepare.rs +++ b/src/libs/auth/src/automation/prepare.rs @@ -1,10 +1,13 @@ -use junobuild_shared::segments::controllers::assert_controllers; -use junobuild_shared::types::state::ControllerId; -use crate::automation::types::{PrepareAutomationError, PrepareAutomationResult, PreparedAutomation, PreparedControllerAutomation}; +use crate::automation::types::{ + PrepareAutomationError, PrepareAutomationResult, PreparedAutomation, + PreparedControllerAutomation, +}; use crate::automation::utils::duration::build_expiration; use crate::automation::utils::scope::build_scope; -use crate::openid::types::provider::{OpenIdAutomationProvider}; -use crate::strategies::{AuthHeapStrategy}; +use crate::openid::types::provider::OpenIdAutomationProvider; +use crate::strategies::AuthHeapStrategy; +use junobuild_shared::segments::controllers::assert_controllers; +use junobuild_shared::types::state::ControllerId; pub fn openid_prepare_automation( controller_id: &ControllerId, @@ -18,14 +21,14 @@ pub fn openid_prepare_automation( // TODO: Assert do not exist let expires_at = build_expiration(provider, auth_heap); - + let scope = build_scope(provider, auth_heap); let controller: PreparedControllerAutomation = PreparedControllerAutomation { id: controller_id.clone(), expires_at, - scope + scope, }; - + Ok(PreparedAutomation { controller }) -} \ No newline at end of file +} diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index f1e45026e6..f78c00889b 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -1,11 +1,11 @@ -use candid::{CandidType, Deserialize}; -use serde::Serialize; -use junobuild_shared::types::interface::SetController; -use junobuild_shared::types::state::ControllerId; use crate::delegation::types::SessionKey; use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; use crate::state::types::state::Salt; +use candid::{CandidType, Deserialize}; +use junobuild_shared::types::interface::SetController; +use junobuild_shared::types::state::ControllerId; +use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] pub struct OpenIdPrepareAutomationArgs { @@ -40,4 +40,4 @@ pub enum PrepareAutomationError { GetCachedJwks, JwtFindProvider(JwtFindProviderError), JwtVerify(JwtVerifyError), -} \ No newline at end of file +} diff --git a/src/libs/auth/src/automation/utils/mod.rs b/src/libs/auth/src/automation/utils/mod.rs index 25a801248a..3f52d1de62 100644 --- a/src/libs/auth/src/automation/utils/mod.rs +++ b/src/libs/auth/src/automation/utils/mod.rs @@ -1,2 +1,2 @@ pub mod duration; -pub mod scope; \ No newline at end of file +pub mod scope; diff --git a/src/libs/auth/src/automation/utils/scope.rs b/src/libs/auth/src/automation/utils/scope.rs index aa579d9040..2a5053ffc4 100644 --- a/src/libs/auth/src/automation/utils/scope.rs +++ b/src/libs/auth/src/automation/utils/scope.rs @@ -1,5 +1,5 @@ use crate::automation::types::AutomationScope; -use crate::openid::types::provider::{OpenIdAutomationProvider}; +use crate::openid::types::provider::OpenIdAutomationProvider; use crate::state::get_automation; use crate::strategies::AuthHeapStrategy; @@ -16,4 +16,4 @@ pub fn build_scope( .and_then(|openid| openid.controller.as_ref()) .and_then(|controller| controller.scope.clone()) .unwrap_or(AutomationScope::Write) -} \ No newline at end of file +} diff --git a/src/libs/auth/src/lib.rs b/src/libs/auth/src/lib.rs index 88bb97093e..f753a00059 100644 --- a/src/libs/auth/src/lib.rs +++ b/src/libs/auth/src/lib.rs @@ -1,9 +1,9 @@ +pub mod automation; pub mod delegation; pub mod openid; pub mod profile; mod random; pub mod state; pub mod strategies; -pub mod automation; pub use state::errors; diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 1ff1f9b8ee..06d71d1797 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -8,7 +8,8 @@ use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; use crate::state::types::automation::OpenIdAutomationProviders; use crate::strategies::AuthHeapStrategy; -type VerifyOpenIdAutomationCredentialsResult = Result; +type VerifyOpenIdAutomationCredentialsResult = + Result; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index dcf78d4616..eb93c1c7b1 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,5 +1,5 @@ -use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::credentials::delegation::types::interface::OpenIdDelegationCredential; +use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; diff --git a/src/libs/auth/src/openid/credentials/mod.rs b/src/libs/auth/src/openid/credentials/mod.rs index c4602919fd..6da0e24a5d 100644 --- a/src/libs/auth/src/openid/credentials/mod.rs +++ b/src/libs/auth/src/openid/credentials/mod.rs @@ -1,3 +1,3 @@ -pub mod delegation; pub mod automation; +pub mod delegation; pub mod types; diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index b76fcbdfca..4176a2b0dd 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,10 +1,12 @@ use crate::openid::jwt::types::cert::Jwks; -use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider}; +use crate::openid::jwt::types::provider::JwtIssuers; +use crate::openid::types::provider::{ + OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider, +}; use ic_cdk::api::time; use junobuild_shared::data::version::next_version; use junobuild_shared::types::state::{Version, Versioned}; use std::fmt::{Display, Formatter, Result as FmtResult}; -use crate::openid::jwt::types::provider::JwtIssuers; impl OpenIdProvider { pub fn jwks_url(&self) -> &'static str { @@ -75,7 +77,7 @@ impl OpenIdAutomationProvider { impl JwtIssuers for OpenIdDelegationProvider { fn issuers(&self) -> &[&'static str] { - self.issuers() + self.issuers() } } diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 060157aaef..afdb7660c0 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,9 +1,9 @@ -use std::collections::BTreeMap; use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::errors::JwtFindProviderError; +use crate::openid::jwt::types::provider::JwtIssuers; use crate::openid::jwt::types::token::UnsafeClaims; use jsonwebtoken::dangerous; -use crate::openid::jwt::types::provider::JwtIssuers; +use std::collections::BTreeMap; /// ⚠️ **Warning:** This function decodes the JWT payload *without verifying its signature*. /// Use only to inspect claims (e.g., `iss`) before performing a verified decode. @@ -86,8 +86,8 @@ mod tests { let jwt = jwt_with(json!({"alg":"RS256"}), json!({"iss": iss})); let provs = providers_with_google(); - let (provider, _) = unsafe_find_jwt_provider(&provs, &jwt) - .expect("should match even without typ"); + let (provider, _) = + unsafe_find_jwt_provider(&provs, &jwt).expect("should match even without typ"); assert_eq!(provider, OpenIdDelegationProvider::Google); } diff --git a/src/libs/auth/src/openid/jwt/types.rs b/src/libs/auth/src/openid/jwt/types.rs index dedcc4eb23..15b0b55b61 100644 --- a/src/libs/auth/src/openid/jwt/types.rs +++ b/src/libs/auth/src/openid/jwt/types.rs @@ -198,4 +198,4 @@ pub mod provider { pub trait JwtIssuers { fn issuers(&self) -> &[&'static str]; } -} \ No newline at end of file +} diff --git a/src/libs/auth/src/state/errors.rs b/src/libs/auth/src/state/errors.rs index e630223e2b..596b9aa08f 100644 --- a/src/libs/auth/src/state/errors.rs +++ b/src/libs/auth/src/state/errors.rs @@ -3,6 +3,7 @@ pub const JUNO_AUTH_ERROR_INVALID_ORIGIN: &str = "juno.auth.error.invalid_origin // No authentication configuration found. pub const JUNO_AUTH_ERROR_NOT_CONFIGURED: &str = "juno.auth.error.not_configured"; // No automation configuration found. -pub const JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED: &str = "juno.auth.error.automation_not_configured"; +pub const JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED: &str = + "juno.auth.error.automation_not_configured"; // Authentication with OpenId disabled. pub const JUNO_AUTH_ERROR_OPENID_DISABLED: &str = "juno.auth.error.openid_disabled"; diff --git a/src/libs/auth/src/state/heap.rs b/src/libs/auth/src/state/heap.rs index 4f0e6b8443..cf26ac3164 100644 --- a/src/libs/auth/src/state/heap.rs +++ b/src/libs/auth/src/state/heap.rs @@ -1,10 +1,10 @@ use crate::openid::types::provider::{OpenIdCertificate, OpenIdProvider}; +use crate::state::types::automation::AutomationConfig; use crate::state::types::config::AuthenticationConfig; use crate::state::types::state::Salt; use crate::state::types::state::{AuthenticationHeapState, OpenIdCachedCertificate, OpenIdState}; use crate::strategies::AuthHeapStrategy; use std::collections::hash_map::Entry; -use crate::state::types::automation::AutomationConfig; // --------------------------------------------------------- // Config // --------------------------------------------------------- @@ -37,17 +37,22 @@ fn insert_config_impl(config: &AuthenticationConfig, state: &mut Option Option { - auth_heap - .with_auth_state(|authentication| { - authentication.as_ref().and_then(|auth| auth.automation.clone()) - }) + auth_heap.with_auth_state(|authentication| { + authentication + .as_ref() + .and_then(|auth| auth.automation.clone()) + }) } pub fn insert_automation(auth_heap: &impl AuthHeapStrategy, automation: &Option) { - auth_heap.with_auth_state_mut(|authentication| insert_automation_impl(automation, authentication)) + auth_heap + .with_auth_state_mut(|authentication| insert_automation_impl(automation, authentication)) } -fn insert_automation_impl(automation: &Option, state: &mut Option) { +fn insert_automation_impl( + automation: &Option, + state: &mut Option, +) { match state { None => { *state = Some(AuthenticationHeapState { diff --git a/src/libs/auth/src/state/mod.rs b/src/libs/auth/src/state/mod.rs index 2989008e1c..dbe71a87cb 100644 --- a/src/libs/auth/src/state/mod.rs +++ b/src/libs/auth/src/state/mod.rs @@ -9,8 +9,8 @@ mod store; pub mod types; pub use heap::{ - cache_certificate, get_cached_certificate, get_config, get_openid_state, get_salt, insert_salt, - record_fetch_attempt, get_automation + cache_certificate, get_automation, get_cached_certificate, get_config, get_openid_state, + get_salt, insert_salt, record_fetch_attempt, }; pub use runtime::*; pub use store::*; diff --git a/src/libs/auth/src/state/store.rs b/src/libs/auth/src/state/store.rs index fb990453ef..cdf70a80e5 100644 --- a/src/libs/auth/src/state/store.rs +++ b/src/libs/auth/src/state/store.rs @@ -1,14 +1,17 @@ -use crate::errors::{JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED, JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISABLED}; +use crate::errors::{ + JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED, JUNO_AUTH_ERROR_NOT_CONFIGURED, + JUNO_AUTH_ERROR_OPENID_DISABLED, +}; use crate::state::assert::assert_set_config; -use crate::state::heap::{get_automation, get_config}; use crate::state::heap::insert_config; +use crate::state::heap::{get_automation, get_config}; +use crate::state::types::automation::OpenIdAutomationProviders; use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProviders}; use crate::state::types::interface::SetAuthenticationConfig; use crate::state::{get_salt, insert_salt}; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::print; use junobuild_shared::random::raw_rand; -use crate::state::types::automation::OpenIdAutomationProviders; pub fn set_config( auth_heap: &impl AuthHeapStrategy, @@ -61,7 +64,8 @@ pub fn get_auth_providers( pub fn get_automation_providers( auth_heap: &impl AuthHeapStrategy, ) -> Result { - let config = get_automation(auth_heap).ok_or(JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED.to_string())?; + let config = + get_automation(auth_heap).ok_or(JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED.to_string())?; let openid = config .openid .ok_or(JUNO_AUTH_ERROR_OPENID_DISABLED.to_string())?; diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index fa782049ae..114f2d32f5 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -1,11 +1,11 @@ pub mod state { use crate::delegation::types::Timestamp; use crate::openid::types::provider::{OpenIdCertificate, OpenIdProvider}; + use crate::state::types::automation::AutomationConfig; use crate::state::types::config::AuthenticationConfig; use candid::CandidType; use serde::{Deserialize, Serialize}; use std::collections::HashMap; - use crate::state::types::automation::AutomationConfig; pub type Salt = [u8; 32]; @@ -110,12 +110,12 @@ pub mod config { } pub mod automation { - use std::collections::{BTreeMap, HashMap}; + use crate::automation::types::AutomationScope; + use crate::openid::types::provider::OpenIdAutomationProvider; use candid::{CandidType, Deserialize, Principal}; - use serde::Serialize; use junobuild_shared::types::state::{Timestamp, Version}; - use crate::automation::types::AutomationScope; - use crate::openid::types::provider::{OpenIdAutomationProvider}; + use serde::Serialize; + use std::collections::{BTreeMap, HashMap}; #[derive(Default, CandidType, Serialize, Deserialize, Clone)] pub struct AutomationConfig { @@ -131,7 +131,8 @@ pub mod automation { pub observatory_id: Option, } - pub type OpenIdAutomationProviders = BTreeMap; + pub type OpenIdAutomationProviders = + BTreeMap; // Repository identifier for GitHub automation. // Corresponds to the `repository` claim in GitHub OIDC tokens (e.g., "octo-org/octo-repo"). @@ -144,7 +145,8 @@ pub mod automation { pub name: String, } - pub type OpenIdAutomationRepositories = HashMap; + pub type OpenIdAutomationRepositories = + HashMap; #[derive(Default, CandidType, Serialize, Deserialize, Clone, Debug)] pub struct OpenIdAutomationProviderConfig { diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index 21ceab530a..a52c8d1ef9 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -6,6 +6,7 @@ use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PreparedAut use junobuild_auth::state::get_automation_providers; use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; +use std::collections::HashMap; pub async fn openid_authenticate_automation( args: &OpenIdPrepareAutomationArgs, @@ -33,11 +34,9 @@ fn register_controller(prepared_automation: &PreparedAutomation) { let controller: SetController = SetController { scope: prepared_automation.controller.scope.clone().into(), - metadata: args.metadata.clone(), + metadata: HashMap::default(), // TODO args.metadata.clone(), expires_at: Some(prepared_automation.controller.expires_at), }; set_controllers(&controllers, &controller); - - Ok(()) } diff --git a/src/libs/satellite/src/automation/automation.rs b/src/libs/satellite/src/automation/automation.rs index 3426697fcf..ca86159a31 100644 --- a/src/libs/satellite/src/automation/automation.rs +++ b/src/libs/satellite/src/automation/automation.rs @@ -1,10 +1,9 @@ use crate::auth::strategy_impls::AuthHeap; use junobuild_auth::automation; use junobuild_auth::automation::types::{ - OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, PreparedDelegation, + OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, }; use junobuild_auth::openid::credentials; -use junobuild_auth::openid::credentials::delegation::types::interface::OpenIdCredential; use junobuild_auth::openid::types::provider::OpenIdDelegationProvider; use junobuild_auth::state::types::automation::OpenIdAutomationProviders; diff --git a/src/libs/satellite/src/automation/impls.rs b/src/libs/satellite/src/automation/impls.rs deleted file mode 100644 index 971d7e0769..0000000000 --- a/src/libs/satellite/src/automation/impls.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::automation::types::AutomationScope; -use junobuild_shared::types::state::ControllerScope; - -impl From for ControllerScope { - fn from(scope: AutomationScope) -> Self { - match scope { - AutomationScope::Write => ControllerScope::Write, - AutomationScope::Submit => ControllerScope::Submit, - } - } -} diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index 7f2c56e0eb..b05e7d69fa 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,4 +1,3 @@ pub mod authenticate; mod automation; -mod impls; pub mod types; diff --git a/src/libs/satellite/src/automation/types.rs b/src/libs/satellite/src/automation/types.rs index 00f6496828..981b4d1d1d 100644 --- a/src/libs/satellite/src/automation/types.rs +++ b/src/libs/satellite/src/automation/types.rs @@ -7,12 +7,6 @@ pub enum AuthenticateAutomationArgs { OpenId(OpenIdPrepareAutomationArgs), } -#[derive(CandidType, Serialize, Deserialize, Clone)] -pub enum AutomationScope { - Write, - Submit, -} - #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticationAutomationError { PrepareAutomation(PrepareAutomationError), From 05139a26067f4b2ccafde36c5bfaa6715263a520 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 06:55:09 +0100 Subject: [PATCH 34/64] feat: claims with trait --- .../openid/credentials/automation/impls.rs | 27 +++++++++++++ .../src/openid/credentials/automation/mod.rs | 2 + .../openid/credentials/automation/types.rs | 40 +++++++++++++++++++ .../openid/credentials/automation/verify.rs | 15 ++++--- .../openid/credentials/delegation/impls.rs | 13 ++++-- .../openid/credentials/delegation/types.rs | 25 ++++++++++++ .../openid/credentials/delegation/verify.rs | 6 +-- src/libs/auth/src/openid/jwt/types.rs | 21 +--------- src/libs/auth/src/openid/jwt/verify.rs | 11 +++-- .../satellite/src/automation/automation.rs | 27 +++++++------ 10 files changed, 139 insertions(+), 48 deletions(-) create mode 100644 src/libs/auth/src/openid/credentials/automation/impls.rs create mode 100644 src/libs/auth/src/openid/credentials/automation/types.rs diff --git a/src/libs/auth/src/openid/credentials/automation/impls.rs b/src/libs/auth/src/openid/credentials/automation/impls.rs new file mode 100644 index 0000000000..922619b1de --- /dev/null +++ b/src/libs/auth/src/openid/credentials/automation/impls.rs @@ -0,0 +1,27 @@ +use crate::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use crate::openid::credentials::automation::types::token::AutomationClaims; +use crate::openid::jwt::types::token::JwtClaims; +use jsonwebtoken::TokenData; + +// TODO: implement fields in AutomationClaims +impl From> for OpenIdAutomationCredential { + fn from(token: TokenData) -> Self { + Self { + sub: token.claims.sub, + iss: token.claims.iss, + jti: None, + repository: None, + repository_owner: None, + r#ref: None, + run_id: None, + run_number: None, + run_attempt: None, + } + } +} + +impl JwtClaims for AutomationClaims { + fn iat(&self) -> Option { + self.iat + } +} diff --git a/src/libs/auth/src/openid/credentials/automation/mod.rs b/src/libs/auth/src/openid/credentials/automation/mod.rs index 06e9b633bf..35c6668fcf 100644 --- a/src/libs/auth/src/openid/credentials/automation/mod.rs +++ b/src/libs/auth/src/openid/credentials/automation/mod.rs @@ -1,3 +1,5 @@ +mod impls; +pub mod types; mod verify; pub use verify::*; diff --git a/src/libs/auth/src/openid/credentials/automation/types.rs b/src/libs/auth/src/openid/credentials/automation/types.rs new file mode 100644 index 0000000000..8ade5a0334 --- /dev/null +++ b/src/libs/auth/src/openid/credentials/automation/types.rs @@ -0,0 +1,40 @@ +pub mod interface { + pub struct OpenIdAutomationCredential { + pub iss: String, + pub sub: String, + + // See https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token + pub jti: Option, + pub repository: Option, + pub repository_owner: Option, + pub r#ref: Option, + pub run_id: Option, + pub run_number: Option, + pub run_attempt: Option, + } +} + +pub(crate) mod token { + use candid::Deserialize; + use serde::Serialize; + + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct AutomationClaims { + pub iss: String, + pub sub: String, + pub aud: String, + pub exp: Option, + pub nbf: Option, + pub iat: Option, + + pub nonce: Option, + + pub email: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub picture: Option, + pub locale: Option, + } +} diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 06d71d1797..698bd32a60 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -1,15 +1,16 @@ +use crate::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use crate::openid::credentials::automation::types::token::AutomationClaims; use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::get_or_refresh_jwks; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; -use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; use crate::state::types::automation::OpenIdAutomationProviders; use crate::strategies::AuthHeapStrategy; type VerifyOpenIdAutomationCredentialsResult = - Result; + Result<(OpenIdAutomationCredential, OpenIdAutomationProvider), VerifyOpenidCredentialsError>; pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, @@ -33,7 +34,7 @@ fn verify_openid_credentials( jwks: &Jwks, provider: &OpenIdAutomationProvider, ) -> VerifyOpenIdAutomationCredentialsResult { - let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { + let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { // if claims.aud != client_id.as_str() { // return Err(JwtVerifyError::BadClaim("aud".to_string())); // } @@ -43,7 +44,7 @@ fn verify_openid_credentials( Ok(()) }; - let assert_no_replay = |claims: &Claims| -> Result<(), JwtVerifyError> { + let assert_no_replay = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { // let nonce = build_nonce(salt); // // if claims.nonce.as_deref() != Some(nonce.as_str()) { @@ -55,7 +56,7 @@ fn verify_openid_credentials( Ok(()) }; - verify_openid_jwt( + let token = verify_openid_jwt( jwt, provider.issuers(), &jwks.keys, @@ -64,5 +65,7 @@ fn verify_openid_credentials( ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; - Ok(provider.clone()) + let credential = OpenIdAutomationCredential::from(token); + + Ok((credential, provider.clone())) } diff --git a/src/libs/auth/src/openid/credentials/delegation/impls.rs b/src/libs/auth/src/openid/credentials/delegation/impls.rs index 5d4bf214d0..d0b1f9aec5 100644 --- a/src/libs/auth/src/openid/credentials/delegation/impls.rs +++ b/src/libs/auth/src/openid/credentials/delegation/impls.rs @@ -1,11 +1,12 @@ use crate::openid::credentials::delegation::types::interface::{ OpenIdDelegationCredential, OpenIdDelegationCredentialKey, }; -use crate::openid::jwt::types::token::Claims; +use crate::openid::credentials::delegation::types::token::DelegationClaims; +use crate::openid::jwt::types::token::JwtClaims; use jsonwebtoken::TokenData; -impl From> for OpenIdDelegationCredential { - fn from(token: TokenData) -> Self { +impl From> for OpenIdDelegationCredential { + fn from(token: TokenData) -> Self { Self { sub: token.claims.sub, iss: token.claims.iss, @@ -28,3 +29,9 @@ impl<'a> From<&'a OpenIdDelegationCredential> for OpenIdDelegationCredentialKey< } } } + +impl JwtClaims for DelegationClaims { + fn iat(&self) -> Option { + self.iat + } +} diff --git a/src/libs/auth/src/openid/credentials/delegation/types.rs b/src/libs/auth/src/openid/credentials/delegation/types.rs index d563357ebc..85bf159cb0 100644 --- a/src/libs/auth/src/openid/credentials/delegation/types.rs +++ b/src/libs/auth/src/openid/credentials/delegation/types.rs @@ -17,3 +17,28 @@ pub mod interface { pub locale: Option, } } + +pub(crate) mod token { + use candid::Deserialize; + use serde::Serialize; + + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct DelegationClaims { + pub iss: String, + pub sub: String, + pub aud: String, + pub exp: Option, + pub nbf: Option, + pub iat: Option, + + pub nonce: Option, + + pub email: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub picture: Option, + pub locale: Option, + } +} diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index eb93c1c7b1..1ab14012ac 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,9 +1,9 @@ use crate::openid::credentials::delegation::types::interface::OpenIdDelegationCredential; +use crate::openid::credentials::delegation::types::token::DelegationClaims; use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; -use crate::openid::jwt::types::token::Claims; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdDelegationProvider; use crate::openid::types::provider::OpenIdProvider; @@ -56,7 +56,7 @@ fn verify_openid_credentials( client_id: &OpenIdAuthProviderClientId, salt: &Salt, ) -> VerifyOpenIdDelegationCredentialsResult { - let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { + let assert_audience = |claims: &DelegationClaims| -> Result<(), JwtVerifyError> { if claims.aud != client_id.as_str() { return Err(JwtVerifyError::BadClaim("aud".to_string())); } @@ -64,7 +64,7 @@ fn verify_openid_credentials( Ok(()) }; - let assert_no_replay = |claims: &Claims| -> Result<(), JwtVerifyError> { + let assert_no_replay = |claims: &DelegationClaims| -> Result<(), JwtVerifyError> { let nonce = build_nonce(salt); if claims.nonce.as_deref() != Some(nonce.as_str()) { diff --git a/src/libs/auth/src/openid/jwt/types.rs b/src/libs/auth/src/openid/jwt/types.rs index 15b0b55b61..080269b7af 100644 --- a/src/libs/auth/src/openid/jwt/types.rs +++ b/src/libs/auth/src/openid/jwt/types.rs @@ -1,25 +1,8 @@ pub(crate) mod token { use candid::Deserialize; - use serde::Serialize; - #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct Claims { - pub iss: String, - pub sub: String, - pub aud: String, - pub exp: Option, - pub nbf: Option, - pub iat: Option, - - pub nonce: Option, - - pub email: Option, - pub name: Option, - pub given_name: Option, - pub family_name: Option, - pub preferred_username: Option, - pub picture: Option, - pub locale: Option, + pub trait JwtClaims { + fn iat(&self) -> Option; } #[derive(Clone, Deserialize)] diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index 37d3fdc55a..78ff1d9030 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -1,13 +1,15 @@ use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::cert::{JwkParams, JwkType}; -use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError, token::Claims}; +use crate::openid::jwt::types::token::JwtClaims; +use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError}; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; +use serde::de::DeserializeOwned; fn pick_key<'a>(kid: &str, jwks: &'a [Jwk]) -> Option<&'a Jwk> { jwks.iter().find(|j| j.kid.as_deref() == Some(kid)) } -pub fn verify_openid_jwt( +pub fn verify_openid_jwt( jwt: &str, issuers: &[&str], jwks: &[Jwk], @@ -15,6 +17,7 @@ pub fn verify_openid_jwt( assert_no_replay: Replay, ) -> Result, JwtVerifyError> where + Claims: DeserializeOwned + JwtClaims, Aud: FnOnce(&Claims) -> Result<(), JwtVerifyError>, Replay: FnOnce(&Claims) -> Result<(), JwtVerifyError>, { @@ -72,7 +75,7 @@ where const MAX_VALIDITY_WINDOW_NS: u64 = 10 * 60 * 1_000_000_000; // 10 min const IAT_FUTURE_SKEW_NS: u64 = 2 * 60 * 1_000_000_000; // 2 min - let iat_s = c.iat.ok_or(JwtVerifyError::BadClaim("iat".to_string()))?; + let iat_s = c.iat().ok_or(JwtVerifyError::BadClaim("iat".to_string()))?; let iat_ns = iat_s.saturating_mul(1_000_000_000); // Reject if token is from the future @@ -109,7 +112,7 @@ fn now_ns() -> u64 { mod verify_tests { use super::verify_openid_jwt; use crate::openid::jwt::types::cert::{JwkParams, JwkParamsRsa, JwkType}; - use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError, token::Claims}; + use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError}; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/src/libs/satellite/src/automation/automation.rs b/src/libs/satellite/src/automation/automation.rs index ca86159a31..699955de1f 100644 --- a/src/libs/satellite/src/automation/automation.rs +++ b/src/libs/satellite/src/automation/automation.rs @@ -4,15 +4,15 @@ use junobuild_auth::automation::types::{ OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, }; use junobuild_auth::openid::credentials; -use junobuild_auth::openid::types::provider::OpenIdDelegationProvider; +use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; use junobuild_auth::state::types::automation::OpenIdAutomationProviders; pub type OpenIdPrepareAutomationResult = Result< ( PreparedAutomation, - OpenIdDelegationProvider, - // TODO: credential - OpenIdCredential, + OpenIdAutomationProvider, + OpenIdAutomationCredential, ), PrepareAutomationError, >; @@ -21,16 +21,17 @@ pub async fn openid_prepare_automation( args: &OpenIdPrepareAutomationArgs, providers: &OpenIdAutomationProviders, ) -> OpenIdPrepareAutomationResult { - let provider = match credentials::automation::verify_openid_credentials_with_jwks_renewal( - &args.jwt, providers, &AuthHeap, - ) - .await - { - Ok(value) => value, - Err(err) => return Err(PrepareAutomationError::from(err)), - }; + let (credential, provider) = + match credentials::automation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, providers, &AuthHeap, + ) + .await + { + Ok(value) => value, + Err(err) => return Err(PrepareAutomationError::from(err)), + }; let result = automation::openid_prepare_automation(&args.controller_id, &provider, &AuthHeap); - result.map(|prepared_delegation| (prepared_delegation, provider, credential)) + result.map(|prepared_automation| (prepared_automation, provider, credential)) } From 82452ddcd4f659410981b02b163701dcc1e79f7a Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 07:30:24 +0100 Subject: [PATCH 35/64] test: adapt --- .../openid/credentials/delegation/types.rs | 1 + .../openid/credentials/delegation/verify.rs | 222 ++++++++++++++++++ src/libs/auth/src/openid/impls.rs | 2 +- src/libs/auth/src/openid/jwt/kid.rs | 30 ++- src/libs/auth/src/openid/jwt/verify.rs | 41 +++- src/libs/satellite/src/user/core/impls.rs | 10 +- src/libs/shared/src/ic/api.rs | 51 +++- 7 files changed, 336 insertions(+), 21 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/delegation/types.rs b/src/libs/auth/src/openid/credentials/delegation/types.rs index 85bf159cb0..fd3fd98b23 100644 --- a/src/libs/auth/src/openid/credentials/delegation/types.rs +++ b/src/libs/auth/src/openid/credentials/delegation/types.rs @@ -4,6 +4,7 @@ pub mod interface { pub sub: &'a String, } + #[derive(Debug)] pub struct OpenIdDelegationCredential { pub iss: String, pub sub: String, diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 1ab14012ac..77a01a06ae 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -87,3 +87,225 @@ fn verify_openid_credentials( Ok((credential, provider.clone())) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::openid::jwt::types::cert::{Jwk, JwkParams, JwkParamsRsa, JwkType, Jwks}; + use crate::openid::types::provider::OpenIdDelegationProvider; + use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; + use std::time::{SystemTime, UNIX_EPOCH}; + + const TEST_RSA_PEM: &str = include_str!("../../../../tests/keys/test_rsa.pem"); + const N_B64URL: &str = "qtQHkWpyd489-_bWjRtrvlQX9CwiQreOsi6kNeeySznI8u-8sxyuO3spW1r2pRmu-rc4jnD9vY6eTGZ3WFNIMxe1geXsF_3nQc5fcNJUUZj19BZE4Ud3dCmUQ4ezkslTvBj8RgD-iBJL7BT7YpxpPgvmqQy_9IgYUkDW4I9_e6kME5kVpySvpRznlk73PfAaDkHWmUTN0j2WcxkW09SGJ_f-tStaYXtc4uH5J-PWMRjwsfL66A_sxLxAwUODJ0VUbeDxVFHGJa0L-58_6GYDTqeel1vH4XjezDL8lf53YRyva3aFxGrC_JeLuIUaJOJX1hXWQb2DruB4hVcQX9afrQ"; + const E_B64URL: &str = "AQAB"; + const KID: &str = "test-kid"; + const ISS_GOOGLE: &str = "https://accounts.google.com"; + const CLIENT_ID: &str = "test-client-id"; + + fn now_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + fn test_salt() -> Salt { + [42u8; 32] + } + + fn test_jwks() -> Jwks { + Jwks { + keys: vec![Jwk { + kty: JwkType::Rsa, + alg: Some("RS256".into()), + kid: Some(KID.into()), + params: JwkParams::Rsa(JwkParamsRsa { + n: N_B64URL.into(), + e: E_B64URL.into(), + }), + }], + } + } + + fn create_token(claims: &DelegationClaims) -> String { + let mut header = Header::new(Algorithm::RS256); + header.kid = Some(KID.into()); + header.typ = Some("JWT".into()); + + let key = EncodingKey::from_rsa_pem(TEST_RSA_PEM.as_bytes()).unwrap(); + encode(&header, claims, &key).unwrap() + } + + #[test] + fn verifies_valid_delegation_credentials() { + let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); + + let claims = DelegationClaims { + iss: ISS_GOOGLE.into(), + sub: "user-123".into(), + aud: CLIENT_ID.into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: Some(nonce), + email: Some("test@example.com".into()), + name: Some("Test User".into()), + given_name: None, + family_name: None, + preferred_username: None, + picture: None, + locale: None, + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdDelegationProvider::Google, + &CLIENT_ID.to_string(), + &salt, + ); + + assert!(result.is_ok()); + let (credential, provider) = result.unwrap(); + assert_eq!(provider, OpenIdDelegationProvider::Google); + assert_eq!(credential.email, Some("test@example.com".into())); + } + + #[test] + fn rejects_wrong_audience() { + let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); + + let claims = DelegationClaims { + iss: ISS_GOOGLE.into(), + sub: "user-123".into(), + aud: "wrong-client-id".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: Some(nonce), + email: None, + name: None, + given_name: None, + family_name: None, + preferred_username: None, + picture: None, + locale: None, + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdDelegationProvider::Google, + &CLIENT_ID.to_string(), + &salt, + ); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "aud" + )); + } + + #[test] + fn rejects_wrong_nonce() { + let now = now_secs(); + let salt = test_salt(); + + let claims = DelegationClaims { + iss: ISS_GOOGLE.into(), + sub: "user-123".into(), + aud: CLIENT_ID.into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: Some("wrong-nonce".into()), + email: None, + name: None, + given_name: None, + family_name: None, + preferred_username: None, + picture: None, + locale: None, + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdDelegationProvider::Google, + &CLIENT_ID.to_string(), + &salt, + ); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "nonce" + )); + } + + #[test] + fn decodes_all_profile_fields() { + let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); + + let claims = DelegationClaims { + iss: ISS_GOOGLE.into(), + sub: "user-123".into(), + aud: CLIENT_ID.into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: Some(nonce), + email: Some("hello@example.com".into()), + name: Some("Hello World".into()), + given_name: Some("Hello".into()), + family_name: Some("World".into()), + preferred_username: Some("hello_world".into()), + picture: Some("https://example.com/pic.png".into()), + locale: Some("en-US".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdDelegationProvider::Google, + &CLIENT_ID.to_string(), + &salt, + ); + + assert!(result.is_ok()); + let (credential, _) = result.unwrap(); + assert_eq!(credential.email.as_deref(), Some("hello@example.com")); + assert_eq!(credential.name.as_deref(), Some("Hello World")); + assert_eq!(credential.given_name.as_deref(), Some("Hello")); + assert_eq!(credential.family_name.as_deref(), Some("World")); + assert_eq!( + credential.preferred_username.as_deref(), + Some("hello_world") + ); + assert_eq!( + credential.picture.as_deref(), + Some("https://example.com/pic.png") + ); + assert_eq!(credential.locale.as_deref(), Some("en-US")); + } +} diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 4176a2b0dd..3885f25068 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -3,8 +3,8 @@ use crate::openid::jwt::types::provider::JwtIssuers; use crate::openid::types::provider::{ OpenIdAutomationProvider, OpenIdCertificate, OpenIdDelegationProvider, OpenIdProvider, }; -use ic_cdk::api::time; use junobuild_shared::data::version::next_version; +use junobuild_shared::ic::api::time; use junobuild_shared::types::state::{Version, Versioned}; use std::fmt::{Display, Formatter, Result as FmtResult}; diff --git a/src/libs/auth/src/openid/jwt/kid.rs b/src/libs/auth/src/openid/jwt/kid.rs index c4274b0635..229e88de45 100644 --- a/src/libs/auth/src/openid/jwt/kid.rs +++ b/src/libs/auth/src/openid/jwt/kid.rs @@ -21,8 +21,10 @@ pub fn unsafe_find_jwt_kid(jwt: &str) -> Result { #[cfg(test)] mod unsafe_find_kid_tests { use super::unsafe_find_jwt_kid; - use crate::openid::jwt::types::{errors::JwtFindKidError, token::Claims}; + use crate::openid::jwt::types::errors::JwtFindKidError; + use candid::Deserialize; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; + use serde::Serialize; use std::time::{SystemTime, UNIX_EPOCH}; const TEST_RSA_PEM: &str = include_str!("../../../tests/keys/test_rsa.pem"); @@ -45,9 +47,29 @@ mod unsafe_find_kid_tests { h } - fn claims_basic() -> Claims { + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct GoogleClaims { + pub iss: String, + pub sub: String, + pub aud: String, + pub exp: Option, + pub nbf: Option, + pub iat: Option, + + pub nonce: Option, + + pub email: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub picture: Option, + pub locale: Option, + } + + fn claims_basic() -> GoogleClaims { let now = now_secs(); - Claims { + GoogleClaims { iss: ISS.into(), sub: "sub".into(), aud: AUD.into(), @@ -65,7 +87,7 @@ mod unsafe_find_kid_tests { } } - fn sign_token(h: &Header, c: &Claims) -> String { + fn sign_token(h: &Header, c: &GoogleClaims) -> String { let enc = EncodingKey::from_rsa_pem(TEST_RSA_PEM.as_bytes()).expect("valid pem"); encode(h, c, &enc).expect("jwt encode") } diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index 78ff1d9030..7bec4648cb 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -112,8 +112,11 @@ fn now_ns() -> u64 { mod verify_tests { use super::verify_openid_jwt; use crate::openid::jwt::types::cert::{JwkParams, JwkParamsRsa, JwkType}; + use crate::openid::jwt::types::token::JwtClaims; use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError}; + use candid::Deserialize; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; + use serde::Serialize; use std::time::{SystemTime, UNIX_EPOCH}; const TEST_RSA_PEM: &str = include_str!("../../../tests/keys/test_rsa.pem"); @@ -140,6 +143,32 @@ mod verify_tests { h } + #[derive(Debug, Clone, Deserialize, Serialize)] + pub struct GoogleClaims { + pub iss: String, + pub sub: String, + pub aud: String, + pub exp: Option, + pub nbf: Option, + pub iat: Option, + + pub nonce: Option, + + pub email: Option, + pub name: Option, + pub given_name: Option, + pub family_name: Option, + pub preferred_username: Option, + pub picture: Option, + pub locale: Option, + } + + impl JwtClaims for GoogleClaims { + fn iat(&self) -> Option { + self.iat + } + } + fn claims( iss: &str, aud: &str, @@ -147,8 +176,8 @@ mod verify_tests { nbf: Option, nonce: Option<&str>, exp: Option, - ) -> Claims { - Claims { + ) -> GoogleClaims { + GoogleClaims { iss: iss.into(), sub: "sub".into(), aud: aud.into(), @@ -166,7 +195,7 @@ mod verify_tests { } } - fn sign_token(h: &Header, c: &Claims) -> String { + fn sign_token(h: &Header, c: &GoogleClaims) -> String { let enc = EncodingKey::from_rsa_pem(TEST_RSA_PEM.as_bytes()).expect("valid pem"); encode(h, c, &enc).expect("jwt encode") } @@ -183,14 +212,14 @@ mod verify_tests { } } - fn assert_audience(claims: &Claims) -> Result<(), JwtVerifyError> { + fn assert_audience(claims: &GoogleClaims) -> Result<(), JwtVerifyError> { if claims.aud != AUD_OK { return Err(JwtVerifyError::BadClaim("aud".to_string())); } Ok(()) } - fn assert_nonce(claims: &Claims) -> Result<(), JwtVerifyError> { + fn assert_nonce(claims: &GoogleClaims) -> Result<(), JwtVerifyError> { if claims.nonce.as_deref() != Some(NONCE_OK) { return Err(JwtVerifyError::BadClaim("nonce".to_string())); } @@ -511,7 +540,7 @@ mod verify_tests { fn decodes_optional_profile_claims() { let now = now_secs(); - let c = Claims { + let c = GoogleClaims { iss: ISS_GOOGLE.into(), sub: "sub-123".into(), aud: AUD_OK.into(), diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index 193e334fdf..2aa39684bd 100644 --- a/src/libs/satellite/src/user/core/impls.rs +++ b/src/libs/satellite/src/user/core/impls.rs @@ -175,7 +175,6 @@ mod tests { use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; - use junobuild_auth::openid::types::provider::OpenIdProvider; // ------------------------ // WebAuthnData @@ -341,13 +340,12 @@ mod tests { #[test] fn test_openid_provider_to_auth_provider() { assert!(matches!( - AuthProvider::try_from(&OpenIdProvider::Google), - Ok(AuthProvider::Google) + AuthProvider::from(&OpenIdDelegationProvider::Google), + AuthProvider::Google )); assert!(matches!( - AuthProvider::try_from(&OpenIdProvider::GitHubAuth), - Ok(AuthProvider::GitHub) + AuthProvider::from(&OpenIdDelegationProvider::GitHub), + AuthProvider::GitHub )); - assert!(AuthProvider::try_from(&OpenIdProvider::GitHubActions).is_err()); } } diff --git a/src/libs/shared/src/ic/api.rs b/src/libs/shared/src/ic/api.rs index 08fe61aa58..a28bdaf4d2 100644 --- a/src/libs/shared/src/ic/api.rs +++ b/src/libs/shared/src/ic/api.rs @@ -1,5 +1,7 @@ use candid::Principal; -use ic_cdk::api::{canister_self, debug_print, msg_caller}; + +#[cfg(target_arch = "wasm32")] +use ic_cdk::api::{canister_self, debug_print, msg_caller, time as ic_time}; /// Returns the **principal** of the current module. /// @@ -9,12 +11,18 @@ use ic_cdk::api::{canister_self, debug_print, msg_caller}; /// /// # Example /// ```ignore -/// let current_module = core::ic::id(); +/// let current_module = ic::api::id(); /// ``` +#[cfg(target_arch = "wasm32")] pub fn id() -> Principal { canister_self() } +#[cfg(not(target_arch = "wasm32"))] +pub fn id() -> Principal { + Principal::from_text("ck4tp-3iaaa-aaaal-ab7da-cai").unwrap() +} + /// Returns the **principal** of the caller that invoked the current module. /// /// This is a shorthand for [`ic_cdk::api::msg_caller`], @@ -23,12 +31,18 @@ pub fn id() -> Principal { /// /// # Example /// ```ignore -/// let user = core::ic::caller(); +/// let user = ic::api::caller(); /// ``` +#[cfg(target_arch = "wasm32")] pub fn caller() -> Principal { msg_caller() } +#[cfg(not(target_arch = "wasm32"))] +pub fn caller() -> Principal { + Principal::from_text("bphsl-fvy2d-emlkg-wuhfe-fylew-25w4a-vpdm3-ajsos-ao4x5-sxn5j-jqe").unwrap() +} + /// Prints a debug message to the Juno runtime logs. /// /// This is a shorthand for [`ic_cdk::api::debug_print`], @@ -39,8 +53,37 @@ pub fn caller() -> Principal { /// /// # Example /// ```ignore -/// core::ic::print("Satellite started successfully"); +/// ic::api::print("Satellite started successfully"); /// ``` +#[cfg(target_arch = "wasm32")] pub fn print>(s: S) { debug_print(s) } + +#[cfg(not(target_arch = "wasm32"))] +pub fn print>(_s: S) { + // Noop +} + +/// Returns the current timestamp in nanoseconds since the UNIX epoch. +/// +/// This is a shorthand for [`ic_cdk::api::time`] useful for +/// test in wasm32 environment. +/// +/// # Example +/// ```ignore +/// let now = ic::api::time(); +/// ``` +#[cfg(target_arch = "wasm32")] +pub fn time() -> u64 { + ic_time() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn time() -> u64 { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() as u64 +} From 45087b067ff5f7bcfcf84a19c5f3b3f297c80ece Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 09:05:38 +0100 Subject: [PATCH 36/64] feat: copy claim --- .../src/openid/credentials/automation/impls.rs | 15 +++++++-------- .../src/openid/credentials/automation/types.rs | 14 +++++++------- .../src/openid/credentials/automation/verify.rs | 5 +++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/impls.rs b/src/libs/auth/src/openid/credentials/automation/impls.rs index 922619b1de..22cab64769 100644 --- a/src/libs/auth/src/openid/credentials/automation/impls.rs +++ b/src/libs/auth/src/openid/credentials/automation/impls.rs @@ -3,19 +3,18 @@ use crate::openid::credentials::automation::types::token::AutomationClaims; use crate::openid::jwt::types::token::JwtClaims; use jsonwebtoken::TokenData; -// TODO: implement fields in AutomationClaims impl From> for OpenIdAutomationCredential { fn from(token: TokenData) -> Self { Self { sub: token.claims.sub, iss: token.claims.iss, - jti: None, - repository: None, - repository_owner: None, - r#ref: None, - run_id: None, - run_number: None, - run_attempt: None, + jti: token.claims.jti, + repository: token.claims.repository, + repository_owner: token.claims.repository_owner, + r#ref: token.claims.r#ref, + run_id: token.claims.run_id, + run_number: token.claims.run_number, + run_attempt: token.claims.run_attempt, } } } diff --git a/src/libs/auth/src/openid/credentials/automation/types.rs b/src/libs/auth/src/openid/credentials/automation/types.rs index 8ade5a0334..88b508be20 100644 --- a/src/libs/auth/src/openid/credentials/automation/types.rs +++ b/src/libs/auth/src/openid/credentials/automation/types.rs @@ -29,12 +29,12 @@ pub(crate) mod token { pub nonce: Option, - pub email: Option, - pub name: Option, - pub given_name: Option, - pub family_name: Option, - pub preferred_username: Option, - pub picture: Option, - pub locale: Option, + pub jti: Option, + pub repository: Option, + pub repository_owner: Option, + pub r#ref: Option, + pub run_id: Option, + pub run_number: Option, + pub run_attempt: Option, } } diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 698bd32a60..f132566b6c 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -6,7 +6,7 @@ use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; -use crate::state::types::automation::OpenIdAutomationProviders; +use crate::state::types::automation::{OpenIdAutomationProviderConfig, OpenIdAutomationProviders}; use crate::strategies::AuthHeapStrategy; type VerifyOpenIdAutomationCredentialsResult = @@ -26,13 +26,14 @@ pub async fn verify_openid_credentials_with_jwks_renewal( .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &automation_provider) + verify_openid_credentials(jwt, &jwks, &automation_provider, &config) } fn verify_openid_credentials( jwt: &str, jwks: &Jwks, provider: &OpenIdAutomationProvider, + config: &OpenIdAutomationProviderConfig, ) -> VerifyOpenIdAutomationCredentialsResult { let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { // if claims.aud != client_id.as_str() { From 70197c7ca9edbd16457d7ab21109bc257d801c10 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 09:08:55 +0100 Subject: [PATCH 37/64] refactor: move nonce --- src/libs/auth/src/openid/credentials/delegation/mod.rs | 1 + src/libs/auth/src/openid/credentials/delegation/utils/mod.rs | 1 + .../openid/{utils.rs => credentials/delegation/utils/nonce.rs} | 0 src/libs/auth/src/openid/credentials/delegation/verify.rs | 2 +- src/libs/auth/src/openid/mod.rs | 1 - 5 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 src/libs/auth/src/openid/credentials/delegation/utils/mod.rs rename src/libs/auth/src/openid/{utils.rs => credentials/delegation/utils/nonce.rs} (100%) diff --git a/src/libs/auth/src/openid/credentials/delegation/mod.rs b/src/libs/auth/src/openid/credentials/delegation/mod.rs index 35c6668fcf..9d1799239b 100644 --- a/src/libs/auth/src/openid/credentials/delegation/mod.rs +++ b/src/libs/auth/src/openid/credentials/delegation/mod.rs @@ -1,5 +1,6 @@ mod impls; pub mod types; +mod utils; mod verify; pub use verify::*; diff --git a/src/libs/auth/src/openid/credentials/delegation/utils/mod.rs b/src/libs/auth/src/openid/credentials/delegation/utils/mod.rs new file mode 100644 index 0000000000..1b0073882a --- /dev/null +++ b/src/libs/auth/src/openid/credentials/delegation/utils/mod.rs @@ -0,0 +1 @@ +pub mod nonce; diff --git a/src/libs/auth/src/openid/utils.rs b/src/libs/auth/src/openid/credentials/delegation/utils/nonce.rs similarity index 100% rename from src/libs/auth/src/openid/utils.rs rename to src/libs/auth/src/openid/credentials/delegation/utils/nonce.rs diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 77a01a06ae..fd65d9ca58 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,5 +1,6 @@ use crate::openid::credentials::delegation::types::interface::OpenIdDelegationCredential; use crate::openid::credentials::delegation::types::token::DelegationClaims; +use crate::openid::credentials::delegation::utils::nonce::build_nonce; use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; @@ -7,7 +8,6 @@ use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::OpenIdDelegationProvider; use crate::openid::types::provider::OpenIdProvider; -use crate::openid::utils::build_nonce; use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProviders}; use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index 74d28a01c7..422504801f 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -3,4 +3,3 @@ mod impls; pub mod jwkset; pub mod jwt; pub mod types; -mod utils; From 7bf62f25bc59743065cccab53ec2a962a0a0997f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 12:22:44 +0100 Subject: [PATCH 38/64] feat: print GitHub --- src/libs/auth/src/openid/impls.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 65b2aff552..07fd38113d 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -129,7 +129,7 @@ impl Display for OpenIdProvider { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { match self { OpenIdProvider::Google => write!(f, "Google"), - OpenIdProvider::GitHubAuth => write!(f, "GitHub Proxy"), + OpenIdProvider::GitHubAuth => write!(f, "GitHub"), OpenIdProvider::GitHubActions => write!(f, "GitHub Actions"), } } @@ -256,7 +256,7 @@ mod tests { #[test] fn test_openid_provider_display() { assert_eq!(format!("{}", OpenIdProvider::Google), "Google"); - assert_eq!(format!("{}", OpenIdProvider::GitHubAuth), "GitHub Proxy"); + assert_eq!(format!("{}", OpenIdProvider::GitHubAuth), "GitHub"); assert_eq!( format!("{}", OpenIdProvider::GitHubActions), "GitHub Actions" From a8d4cfc9d3cf60aada2a5564e81629f014fa570f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 30 Jan 2026 12:43:42 +0100 Subject: [PATCH 39/64] feat: assert repo --- .../openid/credentials/automation/types.rs | 15 +- .../openid/credentials/automation/verify.rs | 297 +++++++++++++++++- 2 files changed, 300 insertions(+), 12 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/types.rs b/src/libs/auth/src/openid/credentials/automation/types.rs index 88b508be20..dff4ff00b8 100644 --- a/src/libs/auth/src/openid/credentials/automation/types.rs +++ b/src/libs/auth/src/openid/credentials/automation/types.rs @@ -1,16 +1,17 @@ pub mod interface { + #[derive(Debug)] pub struct OpenIdAutomationCredential { pub iss: String, pub sub: String, // See https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token - pub jti: Option, - pub repository: Option, - pub repository_owner: Option, - pub r#ref: Option, - pub run_id: Option, - pub run_number: Option, - pub run_attempt: Option, + pub jti: Option, // "example-id" + pub repository: Option, // "octo-org/octo-repo" + pub repository_owner: Option, // "octo-org" + pub r#ref: Option, // "refs/heads/main" + pub run_id: Option, // "example-run-id" + pub run_number: Option, // 10" + pub run_attempt: Option, // "2" } } diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index f132566b6c..bd8ace69a2 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -6,7 +6,9 @@ use crate::openid::jwt::types::cert::Jwks; use crate::openid::jwt::types::errors::JwtVerifyError; use crate::openid::jwt::{unsafe_find_jwt_provider, verify_openid_jwt}; use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; -use crate::state::types::automation::{OpenIdAutomationProviderConfig, OpenIdAutomationProviders}; +use crate::state::types::automation::{ + OpenIdAutomationProviderConfig, OpenIdAutomationProviders, RepositoryKey, +}; use crate::strategies::AuthHeapStrategy; type VerifyOpenIdAutomationCredentialsResult = @@ -36,11 +38,41 @@ fn verify_openid_credentials( config: &OpenIdAutomationProviderConfig, ) -> VerifyOpenIdAutomationCredentialsResult { let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { - // if claims.aud != client_id.as_str() { - // return Err(JwtVerifyError::BadClaim("aud".to_string())); - // } + let repository = claims + .repository + .as_ref() + .ok_or_else(|| JwtVerifyError::BadClaim("repository".to_string()))?; + + let parts: Vec<&str> = repository.split('/').collect(); + if parts.len() != 2 { + return Err(JwtVerifyError::BadClaim("repository_format".to_string())); + } + + let repo_key = RepositoryKey { + owner: parts[0].to_string(), + name: parts[1].to_string(), + }; + + let repo_config = config + .repositories + .get(&repo_key) + .ok_or_else(|| JwtVerifyError::BadClaim("repository_unauthorized".to_string()))?; + + if let Some(allowed_branches) = &repo_config.branches { + let ref_claim = claims + .r#ref + .as_ref() + .ok_or_else(|| JwtVerifyError::BadClaim("ref".to_string()))?; + + // ref is like "refs/heads/main", extract branch name + let branch = ref_claim + .strip_prefix("refs/heads/") + .ok_or_else(|| JwtVerifyError::BadClaim("ref_format".to_string()))?; - // TODO: asser github username and repo + if !allowed_branches.contains(&branch.to_string()) { + return Err(JwtVerifyError::BadClaim("branch_unauthorized".to_string())); + } + } Ok(()) }; @@ -70,3 +102,258 @@ fn verify_openid_credentials( Ok((credential, provider.clone())) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::openid::jwt::types::cert::{Jwk, JwkParams, JwkParamsRsa, JwkType, Jwks}; + use crate::openid::types::provider::OpenIdAutomationProvider; + use crate::state::types::automation::{ + OpenIdAutomationProviderConfig, OpenIdAutomationRepositories, + OpenIdAutomationRepositoryConfig, RepositoryKey, + }; + use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; + use std::collections::HashMap; + use std::time::{SystemTime, UNIX_EPOCH}; + + const TEST_RSA_PEM: &str = include_str!("../../../../tests/keys/test_rsa.pem"); + const N_B64URL: &str = "qtQHkWpyd489-_bWjRtrvlQX9CwiQreOsi6kNeeySznI8u-8sxyuO3spW1r2pRmu-rc4jnD9vY6eTGZ3WFNIMxe1geXsF_3nQc5fcNJUUZj19BZE4Ud3dCmUQ4ezkslTvBj8RgD-iBJL7BT7YpxpPgvmqQy_9IgYUkDW4I9_e6kME5kVpySvpRznlk73PfAaDkHWmUTN0j2WcxkW09SGJ_f-tStaYXtc4uH5J-PWMRjwsfL66A_sxLxAwUODJ0VUbeDxVFHGJa0L-58_6GYDTqeel1vH4XjezDL8lf53YRyva3aFxGrC_JeLuIUaJOJX1hXWQb2DruB4hVcQX9afrQ"; + const E_B64URL: &str = "AQAB"; + const KID: &str = "test-kid"; + const ISS_GITHUB_ACTIONS: &str = "https://token.actions.githubusercontent.com"; + + fn now_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() + } + + fn test_jwks() -> Jwks { + Jwks { + keys: vec![Jwk { + kty: JwkType::Rsa, + alg: Some("RS256".into()), + kid: Some(KID.into()), + params: JwkParams::Rsa(JwkParamsRsa { + n: N_B64URL.into(), + e: E_B64URL.into(), + }), + }], + } + } + + fn test_config() -> OpenIdAutomationProviderConfig { + let mut repositories: OpenIdAutomationRepositories = HashMap::new(); + + repositories.insert( + RepositoryKey { + owner: "octo-org".to_string(), + name: "octo-repo".to_string(), + }, + OpenIdAutomationRepositoryConfig { + branches: Some(vec!["main".to_string(), "develop".to_string()]), + }, + ); + + OpenIdAutomationProviderConfig { + repositories, + controller: None, + } + } + + fn create_token(claims: &AutomationClaims) -> String { + let mut header = Header::new(Algorithm::RS256); + header.kid = Some(KID.into()); + header.typ = Some("JWT".into()); + + let key = EncodingKey::from_rsa_pem(TEST_RSA_PEM.as_bytes()).unwrap(); + encode(&header, claims, &key).unwrap() + } + + #[test] + fn verifies_valid_automation_credentials() { + let now = now_secs(); + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), + aud: "https://github.com/octo-org".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: None, + jti: Some("example-id".into()), + repository: Some("octo-org/octo-repo".into()), + repository_owner: Some("octo-org".into()), + r#ref: Some("refs/heads/main".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + let config = test_config(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_ok()); + let (credential, provider) = result.unwrap(); + assert_eq!(provider, OpenIdAutomationProvider::GitHub); + assert_eq!(credential.repository.as_deref(), Some("octo-org/octo-repo")); + assert_eq!(credential.r#ref.as_deref(), Some("refs/heads/main")); + } + + #[test] + fn rejects_unauthorized_repository() { + let now = now_secs(); + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:other-org/other-repo:ref:refs/heads/main".into(), + aud: "https://github.com/other-org".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: None, + jti: Some("example-id".into()), + repository: Some("other-org/other-repo".into()), + repository_owner: Some("other-org".into()), + r#ref: Some("refs/heads/main".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + let config = test_config(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "repository_unauthorized" + )); + } + + #[test] + fn rejects_unauthorized_branch() { + let now = now_secs(); + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:octo-org/octo-repo:ref:refs/heads/feature".into(), + aud: "https://github.com/octo-org".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: None, + jti: Some("example-id".into()), + repository: Some("octo-org/octo-repo".into()), + repository_owner: Some("octo-org".into()), + r#ref: Some("refs/heads/feature".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + let config = test_config(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "branch_unauthorized" + )); + } + + #[test] + fn allows_all_branches_when_not_configured() { + let now = now_secs(); + + let mut repositories: OpenIdAutomationRepositories = HashMap::new(); + repositories.insert( + RepositoryKey { + owner: "octo-org".to_string(), + name: "octo-repo".to_string(), + }, + OpenIdAutomationRepositoryConfig { + branches: None, + }, + ); + + let config = OpenIdAutomationProviderConfig { + repositories, + controller: None, + }; + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:octo-org/octo-repo:ref:refs/heads/any-branch".into(), + aud: "https://github.com/octo-org".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: None, + jti: Some("example-id".into()), + repository: Some("octo-org/octo-repo".into()), + repository_owner: Some("octo-org".into()), + r#ref: Some("refs/heads/any-branch".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_ok()); + } + + #[test] + fn rejects_missing_repository_claim() { + let now = now_secs(); + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), + aud: "https://github.com/octo-org".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + nonce: None, + jti: Some("example-id".into()), + repository: None, // Missing + repository_owner: Some("octo-org".into()), + r#ref: Some("refs/heads/main".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + let config = test_config(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "repository" + )); + } +} From 4d821c5c888194d0f660a97f7da0f5b8e6cc88d9 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 4 Feb 2026 08:48:47 +0100 Subject: [PATCH 40/64] feat(satellite): save automation jti and basic data (#2565) * feat(satellite): save automation jti and basic data * feat(satellite): save automation jti --- .../openid/credentials/automation/types.rs | 6 +- .../openid/credentials/automation/verify.rs | 13 +-- src/libs/auth/src/openid/impls.rs | 8 ++ src/libs/auth/src/openid/jwt/verify.rs | 2 +- src/libs/collections/src/constants/db.rs | 38 ++++++++- .../satellite/src/automation/authenticate.rs | 29 +++---- src/libs/satellite/src/automation/mod.rs | 3 + src/libs/satellite/src/automation/register.rs | 19 +++++ .../satellite/src/automation/token/impls.rs | 32 ++++++++ .../satellite/src/automation/token/mod.rs | 3 + .../src/automation/token/services.rs | 68 ++++++++++++++++ .../satellite/src/automation/token/types.rs | 21 +++++ src/libs/satellite/src/automation/types.rs | 1 + .../src/automation/workflow/impls.rs | 12 +++ .../satellite/src/automation/workflow/mod.rs | 2 + .../src/automation/workflow/types.rs | 22 +++++ src/libs/satellite/src/errors/automation.rs | 2 + src/libs/satellite/src/errors/mod.rs | 1 + src/libs/satellite/src/memory/lifecycle.rs | 4 + src/libs/satellite/src/rules/mod.rs | 1 + src/libs/satellite/src/rules/upgrade.rs | 80 +++++++++++++++++++ 21 files changed, 332 insertions(+), 35 deletions(-) create mode 100644 src/libs/satellite/src/automation/register.rs create mode 100644 src/libs/satellite/src/automation/token/impls.rs create mode 100644 src/libs/satellite/src/automation/token/mod.rs create mode 100644 src/libs/satellite/src/automation/token/services.rs create mode 100644 src/libs/satellite/src/automation/token/types.rs create mode 100644 src/libs/satellite/src/automation/workflow/impls.rs create mode 100644 src/libs/satellite/src/automation/workflow/mod.rs create mode 100644 src/libs/satellite/src/automation/workflow/types.rs create mode 100644 src/libs/satellite/src/errors/automation.rs create mode 100644 src/libs/satellite/src/rules/upgrade.rs diff --git a/src/libs/auth/src/openid/credentials/automation/types.rs b/src/libs/auth/src/openid/credentials/automation/types.rs index dff4ff00b8..9dc9997d0f 100644 --- a/src/libs/auth/src/openid/credentials/automation/types.rs +++ b/src/libs/auth/src/openid/credentials/automation/types.rs @@ -3,9 +3,9 @@ pub mod interface { pub struct OpenIdAutomationCredential { pub iss: String, pub sub: String, + pub jti: Option, // See https://docs.github.com/en/actions/concepts/security/openid-connect#understanding-the-oidc-token - pub jti: Option, // "example-id" pub repository: Option, // "octo-org/octo-repo" pub repository_owner: Option, // "octo-org" pub r#ref: Option, // "refs/heads/main" @@ -27,10 +27,8 @@ pub(crate) mod token { pub exp: Option, pub nbf: Option, pub iat: Option, - - pub nonce: Option, - pub jti: Option, + pub repository: Option, pub repository_owner: Option, pub r#ref: Option, diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index bd8ace69a2..c5a507741b 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -78,13 +78,8 @@ fn verify_openid_credentials( }; let assert_no_replay = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { - // let nonce = build_nonce(salt); - // - // if claims.nonce.as_deref() != Some(nonce.as_str()) { - // return Err(JwtVerifyError::BadClaim("nonce".to_string())); - // } - - // TODO: assert jti + // ⚠️ **Warning:** Replay protection must be enforced later by the consumer of the crate. + // In case of the Satellite, this is asserted via JTI tracking in save_unique_token_jti() function. Ok(()) }; @@ -286,9 +281,7 @@ mod tests { owner: "octo-org".to_string(), name: "octo-repo".to_string(), }, - OpenIdAutomationRepositoryConfig { - branches: None, - }, + OpenIdAutomationRepositoryConfig { branches: None }, ); let config = OpenIdAutomationProviderConfig { diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 07fd38113d..0b9f83073f 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -87,6 +87,14 @@ impl JwtIssuers for OpenIdAutomationProvider { } } +impl Display for OpenIdAutomationProvider { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self { + OpenIdAutomationProvider::GitHub => write!(f, "GitHub"), + } + } +} + impl Versioned for OpenIdCertificate { fn version(&self) -> Option { self.version diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index 7bec4648cb..40753f448c 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -67,7 +67,7 @@ where // 6) Manual checks audience assert_audience(c)?; - // 7) Prevent replace attack + // 7) Prevent replay attack assert_no_replay(c)?; // 8) Assert expiration diff --git a/src/libs/collections/src/constants/db.rs b/src/libs/collections/src/constants/db.rs index 79e29b9f00..22b25a8148 100644 --- a/src/libs/collections/src/constants/db.rs +++ b/src/libs/collections/src/constants/db.rs @@ -8,6 +8,8 @@ pub const COLLECTION_LOG_KEY: &str = "#log"; pub const COLLECTION_USER_USAGE_KEY: &str = "#user-usage"; pub const COLLECTION_USER_WEBAUTHN_KEY: &str = "#user-webauthn"; pub const COLLECTION_USER_WEBAUTHN_INDEX_KEY: &str = "#user-webauthn-index"; +pub const COLLECTION_AUTOMATION_TOKEN_KEY: &str = "#automation-token"; +pub const COLLECTION_AUTOMATION_WORKFLOW_KEY: &str = "#automation-workflow"; const COLLECTION_USER_DEFAULT_RULE: SetRule = SetRule { read: Managed, @@ -76,7 +78,33 @@ pub const COLLECTION_USER_WEBAUTHN_INDEX_DEFAULT_RULE: SetRule = SetRule { rate_config: None, }; -pub const DEFAULT_DB_COLLECTIONS: [(&str, SetRule); 5] = [ +pub const COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE: SetRule = SetRule { + // Created and read through internal hooks. We do not have an assertion at the moment that would + // prevent a controller to set the document themselves. + read: Controllers, + write: Controllers, + memory: Some(Memory::Stable), + mutable_permissions: Some(false), + max_size: None, + max_capacity: None, + max_changes_per_user: None, + version: None, + rate_config: None, +}; + +pub const COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE: SetRule = SetRule { + read: Managed, + write: Managed, + memory: Some(Memory::Stable), + mutable_permissions: Some(false), + max_size: None, + max_capacity: None, + max_changes_per_user: None, + version: None, + rate_config: None, +}; + +pub const DEFAULT_DB_COLLECTIONS: [(&str, SetRule); 7] = [ (COLLECTION_USER_KEY, COLLECTION_USER_DEFAULT_RULE), (COLLECTION_LOG_KEY, COLLECTION_LOG_DEFAULT_RULE), ( @@ -91,4 +119,12 @@ pub const DEFAULT_DB_COLLECTIONS: [(&str, SetRule); 5] = [ COLLECTION_USER_WEBAUTHN_INDEX_KEY, COLLECTION_USER_WEBAUTHN_INDEX_DEFAULT_RULE, ), + ( + COLLECTION_AUTOMATION_TOKEN_KEY, + COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE, + ), + ( + COLLECTION_AUTOMATION_WORKFLOW_KEY, + COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE, + ), ]; diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index a52c8d1ef9..e7ff5b3aa6 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -1,12 +1,10 @@ use crate::auth::strategy_impls::AuthHeap; use crate::automation::automation; +use crate::automation::register::register_controller; +use crate::automation::token::services::save_unique_token_jti; use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; -use crate::controllers::store::set_controllers; -use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PreparedAutomation}; +use junobuild_auth::automation::types::OpenIdPrepareAutomationArgs; use junobuild_auth::state::get_automation_providers; -use junobuild_shared::types::interface::SetController; -use junobuild_shared::types::state::ControllerId; -use std::collections::HashMap; pub async fn openid_authenticate_automation( args: &OpenIdPrepareAutomationArgs, @@ -14,13 +12,18 @@ pub async fn openid_authenticate_automation( ) -> Result { let providers = get_automation_providers(&AuthHeap)?; - // TODO: rate? + // TODO: rate_config of collection? let prepared_automation = automation::openid_prepare_automation(args, &providers).await; let result = match prepared_automation { - Ok((automation, _, __)) => { + Ok((automation, provider, credential)) => { + if let Err(err) = save_unique_token_jti(&automation, &provider, &credential) { + return Ok(Err(AuthenticationAutomationError::SaveUniqueJtiToken(err))); + } + register_controller(&automation); + Ok(()) } Err(err) => Err(AuthenticationAutomationError::PrepareAutomation(err)), @@ -28,15 +31,3 @@ pub async fn openid_authenticate_automation( Ok(result) } - -fn register_controller(prepared_automation: &PreparedAutomation) { - let controllers: [ControllerId; 1] = [prepared_automation.controller.id.clone()]; - - let controller: SetController = SetController { - scope: prepared_automation.controller.scope.clone().into(), - metadata: HashMap::default(), // TODO args.metadata.clone(), - expires_at: Some(prepared_automation.controller.expires_at), - }; - - set_controllers(&controllers, &controller); -} diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index b05e7d69fa..8e32b1f325 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,3 +1,6 @@ pub mod authenticate; mod automation; +mod register; +mod token; pub mod types; +mod workflow; diff --git a/src/libs/satellite/src/automation/register.rs b/src/libs/satellite/src/automation/register.rs new file mode 100644 index 0000000000..b0aea217a5 --- /dev/null +++ b/src/libs/satellite/src/automation/register.rs @@ -0,0 +1,19 @@ +use crate::controllers::store::set_controllers; +use junobuild_auth::automation::types::PreparedAutomation; +use junobuild_shared::types::interface::SetController; +use junobuild_shared::types::state::ControllerId; +use std::collections::HashMap; + +pub fn register_controller(prepared_automation: &PreparedAutomation) { + let controllers: [ControllerId; 1] = [prepared_automation.controller.id.clone()]; + + // TODO: jti in metadata? to know the source? + let controller: SetController = SetController { + scope: prepared_automation.controller.scope.clone().into(), + metadata: HashMap::default(), // TODO args.metadata.clone(), + expires_at: Some(prepared_automation.controller.expires_at), + // TODO: type or metadata + }; + + set_controllers(&controllers, &controller); +} diff --git a/src/libs/satellite/src/automation/token/impls.rs b/src/libs/satellite/src/automation/token/impls.rs new file mode 100644 index 0000000000..f6babcd13b --- /dev/null +++ b/src/libs/satellite/src/automation/token/impls.rs @@ -0,0 +1,32 @@ +use crate::automation::token::types::state::{AutomationTokenData, AutomationTokenKey}; +use crate::user::core::types::state::UserData; +use crate::{Doc, SetDoc}; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; +use junobuild_utils::encode_doc_data; + +impl AutomationTokenKey { + pub fn create(provider: &OpenIdAutomationProvider, jti: &String) -> Self { + Self { + provider: provider.clone(), + jti: jti.clone(), + } + } + + pub fn to_key(&self) -> String { + format!("{}#{}", self.provider.to_string(), self.jti) + } +} + +impl AutomationTokenData { + pub fn prepare_set_doc(token_data: &AutomationTokenData) -> Result { + let data = encode_doc_data(token_data)?; + + let set_doc = SetDoc { + data, + description: None, + version: None, + }; + + Ok(set_doc) + } +} diff --git a/src/libs/satellite/src/automation/token/mod.rs b/src/libs/satellite/src/automation/token/mod.rs new file mode 100644 index 0000000000..428fd10ac0 --- /dev/null +++ b/src/libs/satellite/src/automation/token/mod.rs @@ -0,0 +1,3 @@ +mod impls; +pub mod services; +mod types; diff --git a/src/libs/satellite/src/automation/token/services.rs b/src/libs/satellite/src/automation/token/services.rs new file mode 100644 index 0000000000..60d46aaa87 --- /dev/null +++ b/src/libs/satellite/src/automation/token/services.rs @@ -0,0 +1,68 @@ +use crate::automation::token::types::state::{AutomationTokenData, AutomationTokenKey}; +use crate::db::internal::unsafe_get_doc; +use crate::db::store::internal_set_doc_store; +use crate::db::types::store::AssertSetDocOptions; +use crate::errors::automation::{ + JUNO_AUTOMATION_ERROR_MISSING_JTI, JUNO_AUTOMATION_ERROR_TOKEN_REUSED, +}; +use crate::rules::store::get_rule_db; +use crate::user::core::types::state::{ProviderData, UserData}; +use junobuild_auth::automation::types::PreparedAutomation; +use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; +use junobuild_collections::constants::db::COLLECTION_AUTOMATION_TOKEN_KEY; +use junobuild_collections::msg::msg_db_collection_not_found; +use junobuild_shared::ic::api::id; +use junobuild_utils::DocDataPrincipal; + +pub fn save_unique_token_jti( + prepared_automation: &PreparedAutomation, + provider: &OpenIdAutomationProvider, + credential: &OpenIdAutomationCredential, +) -> Result<(), String> { + let jti = if let Some(jti) = &credential.jti { + jti + } else { + return Err(JUNO_AUTOMATION_ERROR_MISSING_JTI.to_string()); + }; + + let automation_token_key = AutomationTokenKey::create(provider, &jti).to_key(); + + let automation_token_collection = COLLECTION_AUTOMATION_TOKEN_KEY.to_string(); + + let rule = get_rule_db(&automation_token_collection) + .ok_or_else(|| msg_db_collection_not_found(&automation_token_collection))?; + + let current_jti = unsafe_get_doc( + &automation_token_collection.to_string(), + &automation_token_key, + &rule, + )?; + + if current_jti.is_some() { + return Err(JUNO_AUTOMATION_ERROR_TOKEN_REUSED.to_string()); + } + + // Create metadata. + let automation_token_data: AutomationTokenData = AutomationTokenData { + controller_id: DocDataPrincipal { + value: prepared_automation.controller.id, + }, + }; + + let automation_token_data = AutomationTokenData::prepare_set_doc(&automation_token_data)?; + + let assert_options = AssertSetDocOptions { + with_assert_rate: true, + }; + + internal_set_doc_store( + id(), + automation_token_collection, + automation_token_key, + automation_token_data, + &assert_options, + )?; + + Ok(()) +} diff --git a/src/libs/satellite/src/automation/token/types.rs b/src/libs/satellite/src/automation/token/types.rs new file mode 100644 index 0000000000..ae2ad1be86 --- /dev/null +++ b/src/libs/satellite/src/automation/token/types.rs @@ -0,0 +1,21 @@ +pub mod state { + use candid::Deserialize; + use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; + use junobuild_utils::DocDataPrincipal; + use serde::Serialize; + + /// A unique key for identifying an automation token. + /// Used to prevent replay attack + /// The key will be parsed to `provider#jti`. + #[derive(Serialize, Deserialize)] + pub struct AutomationTokenKey { + pub provider: OpenIdAutomationProvider, + pub jti: String, + } + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase", deny_unknown_fields)] + pub struct AutomationTokenData { + pub controller_id: DocDataPrincipal, + } +} diff --git a/src/libs/satellite/src/automation/types.rs b/src/libs/satellite/src/automation/types.rs index 981b4d1d1d..70dbb0f569 100644 --- a/src/libs/satellite/src/automation/types.rs +++ b/src/libs/satellite/src/automation/types.rs @@ -10,6 +10,7 @@ pub enum AuthenticateAutomationArgs { #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticationAutomationError { PrepareAutomation(PrepareAutomationError), + SaveUniqueJtiToken(String), RegisterController(String), } diff --git a/src/libs/satellite/src/automation/workflow/impls.rs b/src/libs/satellite/src/automation/workflow/impls.rs new file mode 100644 index 0000000000..d7a15e2aff --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/impls.rs @@ -0,0 +1,12 @@ +use crate::automation::workflow::types::state::AutomationWorkflowKey; + +impl AutomationWorkflowKey { + pub fn to_key(&self) -> String { + format!( + "{}#{}#{}", + self.provider.to_string(), + self.repository, + self.run_id + ) + } +} diff --git a/src/libs/satellite/src/automation/workflow/mod.rs b/src/libs/satellite/src/automation/workflow/mod.rs new file mode 100644 index 0000000000..cf99447184 --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/mod.rs @@ -0,0 +1,2 @@ +mod impls; +mod types; diff --git a/src/libs/satellite/src/automation/workflow/types.rs b/src/libs/satellite/src/automation/workflow/types.rs new file mode 100644 index 0000000000..10128c942a --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/types.rs @@ -0,0 +1,22 @@ +pub mod state { + use candid::Deserialize; + use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; + use serde::Serialize; + + /// A unique key for identifying an automation workflow. + /// The key will be parsed to `provider#repository#id`. + #[derive(Serialize, Deserialize)] + pub struct AutomationWorkflowKey { + pub provider: OpenIdAutomationProvider, + pub repository: String, + pub run_id: String, // e.g. run_id for GitHub, pipeline_id for GitLab + } + + #[derive(Serialize, Deserialize)] + #[serde(rename_all = "camelCase", deny_unknown_fields)] + pub struct AutomationWorkflowData { + pub run_number: Option, // The number of times this workflow has been run. + pub run_attempt: Option, // The number of times this workflow run has been retried. + pub r#ref: Option, // (Reference) The git ref that triggered the workflow run. e.g. "refs/heads/main" + } +} diff --git a/src/libs/satellite/src/errors/automation.rs b/src/libs/satellite/src/errors/automation.rs new file mode 100644 index 0000000000..af70ee2f85 --- /dev/null +++ b/src/libs/satellite/src/errors/automation.rs @@ -0,0 +1,2 @@ +pub const JUNO_AUTOMATION_ERROR_MISSING_JTI: &str = "juno.automation.error.missing_jti"; +pub const JUNO_AUTOMATION_ERROR_TOKEN_REUSED: &str = "juno.automation.error.token_reused"; diff --git a/src/libs/satellite/src/errors/mod.rs b/src/libs/satellite/src/errors/mod.rs index 005e7c4c52..9c2c666fce 100644 --- a/src/libs/satellite/src/errors/mod.rs +++ b/src/libs/satellite/src/errors/mod.rs @@ -1,3 +1,4 @@ pub mod auth; +pub mod automation; pub mod db; pub mod user; diff --git a/src/libs/satellite/src/memory/lifecycle.rs b/src/libs/satellite/src/memory/lifecycle.rs index 58d132baa3..a1471bc16c 100644 --- a/src/libs/satellite/src/memory/lifecycle.rs +++ b/src/libs/satellite/src/memory/lifecycle.rs @@ -6,6 +6,7 @@ use crate::memory::internal::{get_memory_for_upgrade, init_stable_state}; use crate::memory::state::STATE; use crate::memory::utils::init_storage_heap_state; use crate::random::init::defer_init_random_seed; +use crate::rules::upgrade::init_automation_collections; use crate::types::state::{HeapState, RuntimeState, State}; use ciborium::{from_reader, into_writer}; use junobuild_shared::memory::upgrade::{read_post_upgrade, write_pre_upgrade}; @@ -61,4 +62,7 @@ pub fn post_upgrade() { invoke_on_post_upgrade_sync(); invoke_on_post_upgrade(); + + // TODO: to be removed - one time upgrade! + init_automation_collections(); } diff --git a/src/libs/satellite/src/rules/mod.rs b/src/libs/satellite/src/rules/mod.rs index 19e827c9ab..4fdf93e1bd 100644 --- a/src/libs/satellite/src/rules/mod.rs +++ b/src/libs/satellite/src/rules/mod.rs @@ -1,3 +1,4 @@ mod internal; pub mod store; pub mod switch_memory; +pub mod upgrade; diff --git a/src/libs/satellite/src/rules/upgrade.rs b/src/libs/satellite/src/rules/upgrade.rs new file mode 100644 index 0000000000..5a00a07bd5 --- /dev/null +++ b/src/libs/satellite/src/rules/upgrade.rs @@ -0,0 +1,80 @@ +use crate::memory::state::STATE; +use ic_cdk::api::time; +use junobuild_collections::constants::db::{ + COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE, COLLECTION_AUTOMATION_TOKEN_KEY, + COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE, COLLECTION_AUTOMATION_WORKFLOW_KEY, +}; +use junobuild_collections::types::rules::Rule; + +// --------------------------------------------------------- +// One time upgrade +// --------------------------------------------------------- + +pub fn init_automation_collections() { + init_automation_token_collection(); + init_automation_workflow_collection(); +} + +fn init_automation_token_collection() { + let col = STATE.with(|state| { + let rules = &state.borrow_mut().heap.db.rules; + rules.get(COLLECTION_AUTOMATION_TOKEN_KEY).cloned() + }); + + if col.is_none() { + STATE.with(|state| { + let rules = &mut state.borrow_mut().heap.db.rules; + + let now = time(); + + let rule = Rule { + read: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.read, + write: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.write, + memory: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.memory, + mutable_permissions: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.mutable_permissions, + max_size: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.max_size, + max_capacity: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.max_capacity, + max_changes_per_user: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.max_changes_per_user, + created_at: now, + updated_at: now, + version: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.version, + rate_config: COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE.rate_config, + }; + + rules.insert(COLLECTION_AUTOMATION_TOKEN_KEY.to_string(), rule.clone()); + }); + } +} + +fn init_automation_workflow_collection() { + let col = STATE.with(|state| { + let rules = &state.borrow_mut().heap.db.rules; + rules.get(COLLECTION_AUTOMATION_WORKFLOW_KEY).cloned() + }); + + if col.is_none() { + STATE.with(|state| { + let rules = &mut state.borrow_mut().heap.db.rules; + + let now = time(); + + let rule = Rule { + read: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.read, + write: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.write, + memory: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.memory, + mutable_permissions: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE + .mutable_permissions, + max_size: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.max_size, + max_capacity: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.max_capacity, + max_changes_per_user: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE + .max_changes_per_user, + created_at: now, + updated_at: now, + version: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.version, + rate_config: COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE.rate_config, + }; + + rules.insert(COLLECTION_AUTOMATION_WORKFLOW_KEY.to_string(), rule.clone()); + }); + } +} From a563c1d90435f96b249c0a28d22927e3caa2358c Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 4 Feb 2026 08:58:12 +0100 Subject: [PATCH 41/64] feat: make custom --- .../src/openid/credentials/automation/verify.rs | 17 +++++++++++++---- .../src/openid/credentials/delegation/verify.rs | 3 +++ src/libs/auth/src/openid/jwt/verify.rs | 10 +++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index c5a507741b..e563e4a7bc 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -14,6 +14,16 @@ use crate::strategies::AuthHeapStrategy; type VerifyOpenIdAutomationCredentialsResult = Result<(OpenIdAutomationCredential, OpenIdAutomationProvider), VerifyOpenidCredentialsError>; +/// Verifies automation OIDC credentials (e.g. GitHub Actions) and returns the credential. +/// +/// ⚠️ **Warning:** This function does NOT enforce replay protection via JTI tracking. +/// +/// The caller MUST implement a replay protection. For example: +/// - Checking if the `jti` claim has been used before +/// - Storing the `jti` after successful verification +/// - Rejecting tokens with duplicate `jti` values +/// +/// In the Satellite implementation, this is handled by `save_unique_token_jti()`. pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, providers: &OpenIdAutomationProviders, @@ -77,9 +87,8 @@ fn verify_openid_credentials( Ok(()) }; - let assert_no_replay = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { - // ⚠️ **Warning:** Replay protection must be enforced later by the consumer of the crate. - // In case of the Satellite, this is asserted via JTI tracking in save_unique_token_jti() function. + let assert_custom = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + // No custom assertion for automation. Replay attack protection must notably be implemented by consumer. Ok(()) }; @@ -89,7 +98,7 @@ fn verify_openid_credentials( provider.issuers(), &jwks.keys, &assert_audience, - &assert_no_replay, + &assert_custom, ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index fd65d9ca58..dd655553a5 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -15,6 +15,9 @@ use crate::strategies::AuthHeapStrategy; type VerifyOpenIdDelegationCredentialsResult = Result<(OpenIdDelegationCredential, OpenIdDelegationProvider), VerifyOpenidCredentialsError>; +/// Verifies delegation OIDC credentials (e.g. Google, GitHub) and returns the credential. +/// +/// Replay protection is enforced via nonce validation using the provided salt and caller(). pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, salt: &Salt, diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index 40753f448c..b402c27fac 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -9,17 +9,17 @@ fn pick_key<'a>(kid: &str, jwks: &'a [Jwk]) -> Option<&'a Jwk> { jwks.iter().find(|j| j.kid.as_deref() == Some(kid)) } -pub fn verify_openid_jwt( +pub fn verify_openid_jwt( jwt: &str, issuers: &[&str], jwks: &[Jwk], assert_audience: Aud, - assert_no_replay: Replay, + assert_custom: Custom, ) -> Result, JwtVerifyError> where Claims: DeserializeOwned + JwtClaims, Aud: FnOnce(&Claims) -> Result<(), JwtVerifyError>, - Replay: FnOnce(&Claims) -> Result<(), JwtVerifyError>, + Custom: FnOnce(&Claims) -> Result<(), JwtVerifyError>, { // 1) Read header to get `kid` let header = decode_jwt_header(jwt).map_err(JwtVerifyError::from)?; @@ -67,8 +67,8 @@ where // 6) Manual checks audience assert_audience(c)?; - // 7) Prevent replay attack - assert_no_replay(c)?; + // 7) Assert custom fields such as the nonce for delegation to prevent replay attack + assert_custom(c)?; // 8) Assert expiration let now_ns = now_ns(); From 83e4514616e147dcf19f6216d2347942d3406e6e Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 4 Feb 2026 09:28:23 +0100 Subject: [PATCH 42/64] feat: save workflow metadata --- .../satellite/src/automation/authenticate.rs | 7 ++ .../satellite/src/automation/token/impls.rs | 7 +- .../src/automation/token/services.rs | 10 ++- src/libs/satellite/src/automation/types.rs | 1 + .../src/automation/workflow/impls.rs | 34 ++++++++- .../satellite/src/automation/workflow/mod.rs | 1 + .../src/automation/workflow/services.rs | 74 +++++++++++++++++++ .../src/automation/workflow/types.rs | 4 +- src/libs/satellite/src/errors/automation.rs | 10 ++- 9 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 src/libs/satellite/src/automation/workflow/services.rs diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index e7ff5b3aa6..de108a9e82 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -3,6 +3,7 @@ use crate::automation::automation; use crate::automation::register::register_controller; use crate::automation::token::services::save_unique_token_jti; use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; +use crate::automation::workflow::services::save_workflow_metadata; use junobuild_auth::automation::types::OpenIdPrepareAutomationArgs; use junobuild_auth::state::get_automation_providers; @@ -22,6 +23,12 @@ pub async fn openid_authenticate_automation( return Ok(Err(AuthenticationAutomationError::SaveUniqueJtiToken(err))); } + if let Err(err) = save_workflow_metadata(&provider, &credential) { + return Ok(Err(AuthenticationAutomationError::SaveWorkflowMetadata( + err, + ))); + } + register_controller(&automation); Ok(()) diff --git a/src/libs/satellite/src/automation/token/impls.rs b/src/libs/satellite/src/automation/token/impls.rs index f6babcd13b..b7da8b572a 100644 --- a/src/libs/satellite/src/automation/token/impls.rs +++ b/src/libs/satellite/src/automation/token/impls.rs @@ -18,13 +18,16 @@ impl AutomationTokenKey { } impl AutomationTokenData { - pub fn prepare_set_doc(token_data: &AutomationTokenData) -> Result { + pub fn prepare_set_doc( + token_data: &AutomationTokenData, + current_doc: &Option, + ) -> Result { let data = encode_doc_data(token_data)?; let set_doc = SetDoc { data, description: None, - version: None, + version: current_doc.as_ref().and_then(|d| d.version), }; Ok(set_doc) diff --git a/src/libs/satellite/src/automation/token/services.rs b/src/libs/satellite/src/automation/token/services.rs index 60d46aaa87..409e113aa9 100644 --- a/src/libs/satellite/src/automation/token/services.rs +++ b/src/libs/satellite/src/automation/token/services.rs @@ -3,7 +3,7 @@ use crate::db::internal::unsafe_get_doc; use crate::db::store::internal_set_doc_store; use crate::db::types::store::AssertSetDocOptions; use crate::errors::automation::{ - JUNO_AUTOMATION_ERROR_MISSING_JTI, JUNO_AUTOMATION_ERROR_TOKEN_REUSED, + JUNO_AUTOMATION_TOKEN_ERROR_MISSING_JTI, JUNO_AUTOMATION_TOKEN_ERROR_TOKEN_REUSED, }; use crate::rules::store::get_rule_db; use crate::user::core::types::state::{ProviderData, UserData}; @@ -23,7 +23,7 @@ pub fn save_unique_token_jti( let jti = if let Some(jti) = &credential.jti { jti } else { - return Err(JUNO_AUTOMATION_ERROR_MISSING_JTI.to_string()); + return Err(JUNO_AUTOMATION_TOKEN_ERROR_MISSING_JTI.to_string()); }; let automation_token_key = AutomationTokenKey::create(provider, &jti).to_key(); @@ -39,8 +39,9 @@ pub fn save_unique_token_jti( &rule, )?; + // ⚠️ Assertion to prevent replay attack. if current_jti.is_some() { - return Err(JUNO_AUTOMATION_ERROR_TOKEN_REUSED.to_string()); + return Err(JUNO_AUTOMATION_TOKEN_ERROR_TOKEN_REUSED.to_string()); } // Create metadata. @@ -50,7 +51,8 @@ pub fn save_unique_token_jti( }, }; - let automation_token_data = AutomationTokenData::prepare_set_doc(&automation_token_data)?; + let automation_token_data = + AutomationTokenData::prepare_set_doc(&automation_token_data, &None)?; let assert_options = AssertSetDocOptions { with_assert_rate: true, diff --git a/src/libs/satellite/src/automation/types.rs b/src/libs/satellite/src/automation/types.rs index 70dbb0f569..ef33018325 100644 --- a/src/libs/satellite/src/automation/types.rs +++ b/src/libs/satellite/src/automation/types.rs @@ -11,6 +11,7 @@ pub enum AuthenticateAutomationArgs { pub enum AuthenticationAutomationError { PrepareAutomation(PrepareAutomationError), SaveUniqueJtiToken(String), + SaveWorkflowMetadata(String), RegisterController(String), } diff --git a/src/libs/satellite/src/automation/workflow/impls.rs b/src/libs/satellite/src/automation/workflow/impls.rs index d7a15e2aff..f84c41c980 100644 --- a/src/libs/satellite/src/automation/workflow/impls.rs +++ b/src/libs/satellite/src/automation/workflow/impls.rs @@ -1,6 +1,21 @@ -use crate::automation::workflow::types::state::AutomationWorkflowKey; +use crate::automation::workflow::types::state::{AutomationWorkflowData, AutomationWorkflowKey}; +use crate::{Doc, SetDoc}; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; +use junobuild_utils::encode_doc_data; impl AutomationWorkflowKey { + pub fn create( + provider: &OpenIdAutomationProvider, + repository: &String, + run_id: &String, + ) -> Self { + Self { + provider: provider.clone(), + repository: repository.clone(), + run_id: run_id.clone(), + } + } + pub fn to_key(&self) -> String { format!( "{}#{}#{}", @@ -10,3 +25,20 @@ impl AutomationWorkflowKey { ) } } + +impl AutomationWorkflowData { + pub fn prepare_set_doc( + workflow_data: &AutomationWorkflowData, + current_doc: &Option, + ) -> Result { + let data = encode_doc_data(workflow_data)?; + + let set_doc = SetDoc { + data, + description: None, + version: current_doc.as_ref().and_then(|d| d.version), + }; + + Ok(set_doc) + } +} diff --git a/src/libs/satellite/src/automation/workflow/mod.rs b/src/libs/satellite/src/automation/workflow/mod.rs index cf99447184..428fd10ac0 100644 --- a/src/libs/satellite/src/automation/workflow/mod.rs +++ b/src/libs/satellite/src/automation/workflow/mod.rs @@ -1,2 +1,3 @@ mod impls; +pub mod services; mod types; diff --git a/src/libs/satellite/src/automation/workflow/services.rs b/src/libs/satellite/src/automation/workflow/services.rs new file mode 100644 index 0000000000..2a1f3967d3 --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/services.rs @@ -0,0 +1,74 @@ +use crate::automation::workflow::types::state::{AutomationWorkflowData, AutomationWorkflowKey}; +use crate::db::internal::unsafe_get_doc; +use crate::db::store::internal_set_doc_store; +use crate::db::types::store::AssertSetDocOptions; +use crate::errors::automation::{ + JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY, + JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID, +}; +use crate::rules::store::get_rule_db; +use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; +use junobuild_collections::constants::db::COLLECTION_AUTOMATION_WORKFLOW_KEY; +use junobuild_collections::msg::msg_db_collection_not_found; +use junobuild_shared::ic::api::id; + +pub fn save_workflow_metadata( + provider: &OpenIdAutomationProvider, + credential: &OpenIdAutomationCredential, +) -> Result<(), String> { + let repository = if let Some(repository) = &credential.repository { + repository + } else { + return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY.to_string()); + }; + + let run_id = if let Some(run_id) = &credential.run_id { + run_id + } else { + return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID.to_string()); + }; + + let automation_workflow_key = + AutomationWorkflowKey::create(provider, &repository, &run_id).to_key(); + + let automation_workflow_collection = COLLECTION_AUTOMATION_WORKFLOW_KEY.to_string(); + + let rule = get_rule_db(&automation_workflow_collection) + .ok_or_else(|| msg_db_collection_not_found(&automation_workflow_collection))?; + + let current_automation_workflow = unsafe_get_doc( + &automation_workflow_collection.to_string(), + &automation_workflow_key, + &rule, + )?; + + // Create or update metadata. Since we are "only" saving the latest information, we always + // update the fields. + let automation_workflow_data: AutomationWorkflowData = AutomationWorkflowData { + run_number: credential.run_number.clone(), + run_attempt: credential.run_attempt.clone(), + r#ref: credential.r#ref.clone(), + }; + + let automation_workflow_data = AutomationWorkflowData::prepare_set_doc( + &automation_workflow_data, + ¤t_automation_workflow, + )?; + + let assert_options = AssertSetDocOptions { + // We disable the assertion for the rate because it has been asserted + // before when saving the jti. + with_assert_rate: false, + }; + + internal_set_doc_store( + id(), + automation_workflow_collection, + automation_workflow_key, + automation_workflow_data, + &assert_options, + )?; + + Ok(()) +} diff --git a/src/libs/satellite/src/automation/workflow/types.rs b/src/libs/satellite/src/automation/workflow/types.rs index 10128c942a..4f45c15da1 100644 --- a/src/libs/satellite/src/automation/workflow/types.rs +++ b/src/libs/satellite/src/automation/workflow/types.rs @@ -12,11 +12,13 @@ pub mod state { pub run_id: String, // e.g. run_id for GitHub, pipeline_id for GitLab } + /// Deployment workflow metadata. + /// Stores the latest state if a workflow has multiple attempts. #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct AutomationWorkflowData { pub run_number: Option, // The number of times this workflow has been run. pub run_attempt: Option, // The number of times this workflow run has been retried. - pub r#ref: Option, // (Reference) The git ref that triggered the workflow run. e.g. "refs/heads/main" + pub r#ref: Option, // (Reference) The latest git ref that triggered the workflow run. e.g. "refs/heads/main" } } diff --git a/src/libs/satellite/src/errors/automation.rs b/src/libs/satellite/src/errors/automation.rs index af70ee2f85..3a93d247ff 100644 --- a/src/libs/satellite/src/errors/automation.rs +++ b/src/libs/satellite/src/errors/automation.rs @@ -1,2 +1,8 @@ -pub const JUNO_AUTOMATION_ERROR_MISSING_JTI: &str = "juno.automation.error.missing_jti"; -pub const JUNO_AUTOMATION_ERROR_TOKEN_REUSED: &str = "juno.automation.error.token_reused"; +pub const JUNO_AUTOMATION_TOKEN_ERROR_MISSING_JTI: &str = "juno.automation.token.error.missing_jti"; +pub const JUNO_AUTOMATION_TOKEN_ERROR_TOKEN_REUSED: &str = + "juno.automation.token.error.token_reused"; + +pub const JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY: &str = + "juno.automation.workflow.error.missing_repository"; +pub const JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID: &str = + "juno.automation.workflow.error.missing_run_id"; From 17259a03228613f80f4f0cb16f9987fccfb287cb Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 4 Feb 2026 09:55:22 +0100 Subject: [PATCH 43/64] feat: assert write automation --- src/libs/collections/src/constants/db.rs | 8 +++---- .../satellite/src/automation/authenticate.rs | 4 ++-- src/libs/satellite/src/automation/mod.rs | 3 +++ .../satellite/src/automation/token/assert.rs | 21 +++++++++++++++++++ .../satellite/src/automation/token/mod.rs | 5 ++++- .../src/automation/workflow/assert.rs | 21 +++++++++++++++++++ .../satellite/src/automation/workflow/mod.rs | 5 ++++- src/libs/satellite/src/db/assert.rs | 8 +++++++ src/libs/satellite/src/errors/automation.rs | 2 ++ 9 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 src/libs/satellite/src/automation/token/assert.rs create mode 100644 src/libs/satellite/src/automation/workflow/assert.rs diff --git a/src/libs/collections/src/constants/db.rs b/src/libs/collections/src/constants/db.rs index 22b25a8148..87296001ae 100644 --- a/src/libs/collections/src/constants/db.rs +++ b/src/libs/collections/src/constants/db.rs @@ -79,8 +79,7 @@ pub const COLLECTION_USER_WEBAUTHN_INDEX_DEFAULT_RULE: SetRule = SetRule { }; pub const COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE: SetRule = SetRule { - // Created and read through internal hooks. We do not have an assertion at the moment that would - // prevent a controller to set the document themselves. + // Created and read through internal hooks. Write is restricted to Satellites themselves. read: Controllers, write: Controllers, memory: Some(Memory::Stable), @@ -93,8 +92,9 @@ pub const COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE: SetRule = SetRule { }; pub const COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE: SetRule = SetRule { - read: Managed, - write: Managed, + // Created and read through internal hooks. Write is restricted to Satellites themselves. + read: Controllers, + write: Controllers, memory: Some(Memory::Stable), mutable_permissions: Some(false), max_size: None, diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index de108a9e82..78e854c7a1 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -1,9 +1,9 @@ use crate::auth::strategy_impls::AuthHeap; use crate::automation::automation; use crate::automation::register::register_controller; -use crate::automation::token::services::save_unique_token_jti; +use crate::automation::token::save_unique_token_jti; use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; -use crate::automation::workflow::services::save_workflow_metadata; +use crate::automation::workflow::save_workflow_metadata; use junobuild_auth::automation::types::OpenIdPrepareAutomationArgs; use junobuild_auth::state::get_automation_providers; diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index 8e32b1f325..b52d24c0a2 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -4,3 +4,6 @@ mod register; mod token; pub mod types; mod workflow; + +pub use token::assert::*; +pub use workflow::assert::*; diff --git a/src/libs/satellite/src/automation/token/assert.rs b/src/libs/satellite/src/automation/token/assert.rs new file mode 100644 index 0000000000..4f6c84ff57 --- /dev/null +++ b/src/libs/satellite/src/automation/token/assert.rs @@ -0,0 +1,21 @@ +use crate::errors::automation::JUNO_DATASTORE_ERROR_AUTOMATION_CALLER; +use candid::Principal; +use junobuild_collections::constants::db::COLLECTION_AUTOMATION_TOKEN_KEY; +use junobuild_collections::types::core::CollectionKey; +use junobuild_shared::ic::api::id; +use junobuild_shared::utils::principal_not_equal; + +pub fn assert_automation_token_caller( + caller: Principal, + collection: &CollectionKey, +) -> Result<(), String> { + if collection != COLLECTION_AUTOMATION_TOKEN_KEY { + return Ok(()); + } + + if principal_not_equal(id(), caller) { + return Err(JUNO_DATASTORE_ERROR_AUTOMATION_CALLER.to_string()); + } + + Ok(()) +} diff --git a/src/libs/satellite/src/automation/token/mod.rs b/src/libs/satellite/src/automation/token/mod.rs index 428fd10ac0..528a444196 100644 --- a/src/libs/satellite/src/automation/token/mod.rs +++ b/src/libs/satellite/src/automation/token/mod.rs @@ -1,3 +1,6 @@ +pub mod assert; mod impls; -pub mod services; +mod services; mod types; + +pub use services::*; diff --git a/src/libs/satellite/src/automation/workflow/assert.rs b/src/libs/satellite/src/automation/workflow/assert.rs new file mode 100644 index 0000000000..ef030f59f6 --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/assert.rs @@ -0,0 +1,21 @@ +use crate::errors::automation::JUNO_DATASTORE_ERROR_AUTOMATION_CALLER; +use candid::Principal; +use junobuild_collections::constants::db::COLLECTION_AUTOMATION_WORKFLOW_KEY; +use junobuild_collections::types::core::CollectionKey; +use junobuild_shared::ic::api::id; +use junobuild_shared::utils::principal_not_equal; + +pub fn assert_automation_workflow_caller( + caller: Principal, + collection: &CollectionKey, +) -> Result<(), String> { + if collection != COLLECTION_AUTOMATION_WORKFLOW_KEY { + return Ok(()); + } + + if principal_not_equal(id(), caller) { + return Err(JUNO_DATASTORE_ERROR_AUTOMATION_CALLER.to_string()); + } + + Ok(()) +} diff --git a/src/libs/satellite/src/automation/workflow/mod.rs b/src/libs/satellite/src/automation/workflow/mod.rs index 428fd10ac0..528a444196 100644 --- a/src/libs/satellite/src/automation/workflow/mod.rs +++ b/src/libs/satellite/src/automation/workflow/mod.rs @@ -1,3 +1,6 @@ +pub mod assert; mod impls; -pub mod services; +mod services; mod types; + +pub use services::*; diff --git a/src/libs/satellite/src/db/assert.rs b/src/libs/satellite/src/db/assert.rs index c041f2b4b8..7d92c59b79 100644 --- a/src/libs/satellite/src/db/assert.rs +++ b/src/libs/satellite/src/db/assert.rs @@ -1,4 +1,5 @@ use crate::auth::assert::assert_caller_is_allowed; +use crate::automation::{assert_automation_token_caller, assert_automation_workflow_caller}; use crate::db::runtime::increment_and_assert_rate; use crate::db::types::config::DbConfig; use crate::db::types::interface::SetDbConfig; @@ -84,6 +85,10 @@ pub fn assert_set_doc( assert_user_webauthn_collection_data(caller, collection, value)?; assert_user_webauthn_collection_write_permission(collection, current_doc)?; + // Note: we do not assert the format of the automation keys or data since only the Satellite can write. + assert_automation_token_caller(caller, collection)?; + assert_automation_workflow_caller(caller, collection)?; + assert_write_permission(caller, controllers, current_doc, &rule.write)?; assert_memory_size(config)?; @@ -133,6 +138,9 @@ pub fn assert_delete_doc( assert_write_version(current_doc, value.version)?; + assert_automation_token_caller(caller, collection)?; + assert_automation_workflow_caller(caller, collection)?; + invoke_assert_delete_doc( &caller, &DocContext { diff --git a/src/libs/satellite/src/errors/automation.rs b/src/libs/satellite/src/errors/automation.rs index 3a93d247ff..cb3d42db09 100644 --- a/src/libs/satellite/src/errors/automation.rs +++ b/src/libs/satellite/src/errors/automation.rs @@ -6,3 +6,5 @@ pub const JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY: &str = "juno.automation.workflow.error.missing_repository"; pub const JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID: &str = "juno.automation.workflow.error.missing_run_id"; + +pub const JUNO_DATASTORE_ERROR_AUTOMATION_CALLER: &str = "juno.datastore.error.automation.caller"; From 8a72073bcb70c64d905b3e4b734f64ba17660431 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Wed, 4 Feb 2026 09:55:44 +0100 Subject: [PATCH 44/64] chore: fmt --- src/libs/satellite/src/automation/token/impls.rs | 1 - src/libs/satellite/src/automation/token/services.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/satellite/src/automation/token/impls.rs b/src/libs/satellite/src/automation/token/impls.rs index b7da8b572a..584a3f6c2b 100644 --- a/src/libs/satellite/src/automation/token/impls.rs +++ b/src/libs/satellite/src/automation/token/impls.rs @@ -1,5 +1,4 @@ use crate::automation::token::types::state::{AutomationTokenData, AutomationTokenKey}; -use crate::user::core::types::state::UserData; use crate::{Doc, SetDoc}; use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; use junobuild_utils::encode_doc_data; diff --git a/src/libs/satellite/src/automation/token/services.rs b/src/libs/satellite/src/automation/token/services.rs index 409e113aa9..e164585d88 100644 --- a/src/libs/satellite/src/automation/token/services.rs +++ b/src/libs/satellite/src/automation/token/services.rs @@ -6,7 +6,6 @@ use crate::errors::automation::{ JUNO_AUTOMATION_TOKEN_ERROR_MISSING_JTI, JUNO_AUTOMATION_TOKEN_ERROR_TOKEN_REUSED, }; use crate::rules::store::get_rule_db; -use crate::user::core::types::state::{ProviderData, UserData}; use junobuild_auth::automation::types::PreparedAutomation; use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; From ef9bd7b960801c2eb804ee03cd68e937ee8693a0 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 07:54:40 +0100 Subject: [PATCH 45/64] feat: register controller --- .../satellite/src/automation/authenticate.rs | 6 ++-- .../src/automation/controllers/mod.rs | 1 + .../src/automation/controllers/register.rs | 31 +++++++++++++++++++ src/libs/satellite/src/automation/mod.rs | 2 +- src/libs/satellite/src/automation/register.rs | 19 ------------ .../satellite/src/automation/workflow/mod.rs | 2 ++ .../src/automation/workflow/services.rs | 22 ++----------- .../src/automation/workflow/utils.rs | 28 +++++++++++++++++ 8 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 src/libs/satellite/src/automation/controllers/mod.rs create mode 100644 src/libs/satellite/src/automation/controllers/register.rs delete mode 100644 src/libs/satellite/src/automation/register.rs create mode 100644 src/libs/satellite/src/automation/workflow/utils.rs diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index 78e854c7a1..38a80f8fcb 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -1,6 +1,6 @@ use crate::auth::strategy_impls::AuthHeap; use crate::automation::automation; -use crate::automation::register::register_controller; +use crate::automation::controllers::register::register_controller; use crate::automation::token::save_unique_token_jti; use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; use crate::automation::workflow::save_workflow_metadata; @@ -29,7 +29,9 @@ pub async fn openid_authenticate_automation( ))); } - register_controller(&automation); + if let Err(err) = register_controller(&automation, &provider, &credential) { + return Ok(Err(AuthenticationAutomationError::RegisterController(err))); + } Ok(()) } diff --git a/src/libs/satellite/src/automation/controllers/mod.rs b/src/libs/satellite/src/automation/controllers/mod.rs new file mode 100644 index 0000000000..f862bee245 --- /dev/null +++ b/src/libs/satellite/src/automation/controllers/mod.rs @@ -0,0 +1 @@ +pub mod register; diff --git a/src/libs/satellite/src/automation/controllers/register.rs b/src/libs/satellite/src/automation/controllers/register.rs new file mode 100644 index 0000000000..753e3413f0 --- /dev/null +++ b/src/libs/satellite/src/automation/controllers/register.rs @@ -0,0 +1,31 @@ +use crate::automation::workflow::build_automation_workflow_key; +use crate::controllers::store::set_controllers; +use junobuild_auth::automation::types::PreparedAutomation; +use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; +use junobuild_shared::types::interface::SetController; +use junobuild_shared::types::state::{ControllerId, ControllerKind, Metadata}; + +pub fn register_controller( + automation: &PreparedAutomation, + provider: &OpenIdAutomationProvider, + credential: &OpenIdAutomationCredential, +) -> Result<(), String> { + let controllers: [ControllerId; 1] = [automation.controller.id.clone()]; + + let automation_workflow_key = build_automation_workflow_key(provider, credential)?; + + let mut metadata: Metadata = Default::default(); + metadata.insert("workflow_key".to_string(), automation_workflow_key.to_key()); + + let controller: SetController = SetController { + scope: automation.controller.scope.clone().into(), + metadata, + expires_at: Some(automation.controller.expires_at), + kind: Some(ControllerKind::Automation), + }; + + set_controllers(&controllers, &controller); + + Ok(()) +} diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index b52d24c0a2..adeb56b732 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,6 +1,6 @@ pub mod authenticate; mod automation; -mod register; +mod controllers; mod token; pub mod types; mod workflow; diff --git a/src/libs/satellite/src/automation/register.rs b/src/libs/satellite/src/automation/register.rs deleted file mode 100644 index b0aea217a5..0000000000 --- a/src/libs/satellite/src/automation/register.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::controllers::store::set_controllers; -use junobuild_auth::automation::types::PreparedAutomation; -use junobuild_shared::types::interface::SetController; -use junobuild_shared::types::state::ControllerId; -use std::collections::HashMap; - -pub fn register_controller(prepared_automation: &PreparedAutomation) { - let controllers: [ControllerId; 1] = [prepared_automation.controller.id.clone()]; - - // TODO: jti in metadata? to know the source? - let controller: SetController = SetController { - scope: prepared_automation.controller.scope.clone().into(), - metadata: HashMap::default(), // TODO args.metadata.clone(), - expires_at: Some(prepared_automation.controller.expires_at), - // TODO: type or metadata - }; - - set_controllers(&controllers, &controller); -} diff --git a/src/libs/satellite/src/automation/workflow/mod.rs b/src/libs/satellite/src/automation/workflow/mod.rs index 528a444196..551e81b6bc 100644 --- a/src/libs/satellite/src/automation/workflow/mod.rs +++ b/src/libs/satellite/src/automation/workflow/mod.rs @@ -2,5 +2,7 @@ pub mod assert; mod impls; mod services; mod types; +mod utils; pub use services::*; +pub use utils::*; diff --git a/src/libs/satellite/src/automation/workflow/services.rs b/src/libs/satellite/src/automation/workflow/services.rs index 2a1f3967d3..e42802aec9 100644 --- a/src/libs/satellite/src/automation/workflow/services.rs +++ b/src/libs/satellite/src/automation/workflow/services.rs @@ -1,11 +1,8 @@ -use crate::automation::workflow::types::state::{AutomationWorkflowData, AutomationWorkflowKey}; +use crate::automation::workflow::types::state::AutomationWorkflowData; +use crate::automation::workflow::utils::build_automation_workflow_key; use crate::db::internal::unsafe_get_doc; use crate::db::store::internal_set_doc_store; use crate::db::types::store::AssertSetDocOptions; -use crate::errors::automation::{ - JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY, - JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID, -}; use crate::rules::store::get_rule_db; use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; @@ -17,20 +14,7 @@ pub fn save_workflow_metadata( provider: &OpenIdAutomationProvider, credential: &OpenIdAutomationCredential, ) -> Result<(), String> { - let repository = if let Some(repository) = &credential.repository { - repository - } else { - return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY.to_string()); - }; - - let run_id = if let Some(run_id) = &credential.run_id { - run_id - } else { - return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID.to_string()); - }; - - let automation_workflow_key = - AutomationWorkflowKey::create(provider, &repository, &run_id).to_key(); + let automation_workflow_key = build_automation_workflow_key(provider, credential)?.to_key(); let automation_workflow_collection = COLLECTION_AUTOMATION_WORKFLOW_KEY.to_string(); diff --git a/src/libs/satellite/src/automation/workflow/utils.rs b/src/libs/satellite/src/automation/workflow/utils.rs new file mode 100644 index 0000000000..32bc9bce71 --- /dev/null +++ b/src/libs/satellite/src/automation/workflow/utils.rs @@ -0,0 +1,28 @@ +use crate::automation::workflow::types::state::AutomationWorkflowKey; +use crate::errors::automation::{ + JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY, + JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID, +}; +use junobuild_auth::openid::credentials::automation::types::interface::OpenIdAutomationCredential; +use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; + +pub fn build_automation_workflow_key( + provider: &OpenIdAutomationProvider, + credential: &OpenIdAutomationCredential, +) -> Result { + let repository = if let Some(repository) = &credential.repository { + repository + } else { + return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_REPOSITORY.to_string()); + }; + + let run_id = if let Some(run_id) = &credential.run_id { + run_id + } else { + return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID.to_string()); + }; + + let automation_workflow_key = AutomationWorkflowKey::create(provider, &repository, &run_id); + + Ok(automation_workflow_key) +} From 0a37c628aeb1ece20c1c72024842c71f8f2d0d99 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 07:56:56 +0100 Subject: [PATCH 46/64] test: remove nonce (there is none here) --- src/libs/auth/src/openid/credentials/automation/verify.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index e563e4a7bc..9d9d16c3c6 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -186,7 +186,6 @@ mod tests { iat: Some(now), exp: Some(now + 600), nbf: None, - nonce: None, jti: Some("example-id".into()), repository: Some("octo-org/octo-repo".into()), repository_owner: Some("octo-org".into()), @@ -221,7 +220,6 @@ mod tests { iat: Some(now), exp: Some(now + 600), nbf: None, - nonce: None, jti: Some("example-id".into()), repository: Some("other-org/other-repo".into()), repository_owner: Some("other-org".into()), @@ -256,7 +254,6 @@ mod tests { iat: Some(now), exp: Some(now + 600), nbf: None, - nonce: None, jti: Some("example-id".into()), repository: Some("octo-org/octo-repo".into()), repository_owner: Some("octo-org".into()), @@ -305,7 +302,6 @@ mod tests { iat: Some(now), exp: Some(now + 600), nbf: None, - nonce: None, jti: Some("example-id".into()), repository: Some("octo-org/octo-repo".into()), repository_owner: Some("octo-org".into()), @@ -335,7 +331,6 @@ mod tests { iat: Some(now), exp: Some(now + 600), nbf: None, - nonce: None, jti: Some("example-id".into()), repository: None, // Missing repository_owner: Some("octo-org".into()), From 8d452515a421b517ef750458cc1f12b867d8a723 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 12:04:13 +0100 Subject: [PATCH 47/64] feat: generate did --- src/declarations/satellite/satellite.did.d.ts | 50 ++++++++++--------- .../satellite.factory.certified.did.js | 33 ++++++------ .../satellite/satellite.factory.did.js | 33 ++++++------ .../satellite/satellite.factory.did.mjs | 33 ++++++------ src/declarations/sputnik/sputnik.did.d.ts | 50 ++++++++++--------- .../sputnik/sputnik.factory.certified.did.js | 33 ++++++------ .../sputnik/sputnik.factory.did.js | 33 ++++++------ src/libs/satellite/satellite.did | 46 ++++++++--------- src/satellite/satellite.did | 46 ++++++++--------- src/sputnik/sputnik.did | 46 ++++++++--------- .../test_satellite/test_satellite.did.d.ts | 50 ++++++++++--------- .../test_satellite.factory.certified.did.js | 33 ++++++------ .../test_satellite.factory.did.js | 33 ++++++------ .../test_satellite/test_satellite.did | 46 ++++++++--------- 14 files changed, 275 insertions(+), 290 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 0ee8bfc913..55516847dd 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -34,18 +34,25 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } -export type AuthenticateControllerArgs = { - OpenId: OpenIdAuthenticateControllerArgs; +export type AuthenticateAutomationArgs = { + OpenId: OpenIdPrepareAutomationArgs; }; -export type AuthenticateControllerResultResponse = +export type AuthenticateAutomationResultResponse = | { Ok: null } - | { Err: AuthenticationControllerError }; + | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; delegation: PreparedDelegation; } export type AuthenticationArgs = { OpenId: OpenIdPrepareDelegationArgs }; +export type AuthenticationAutomationError = + | { + PrepareAutomation: PrepareAutomationError; + } + | { RegisterController: string } + | { SaveWorkflowMetadata: string } + | { SaveUniqueJtiToken: string }; export interface AuthenticationConfig { updated_at: [] | [bigint]; openid: [] | [AuthenticationConfigOpenId]; @@ -62,9 +69,6 @@ export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdDelegationProvider, OpenIdAuthProviderConfig]>; } -export type AuthenticationControllerError = - | { RegisterController: string } - | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -73,7 +77,6 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } -export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -279,13 +282,6 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } -export interface OpenIdAuthenticateControllerArgs { - jwt: string; - metadata: Array<[string, string]>; - scope: AutomationScope; - max_time_to_live: [] | [bigint]; - controller_id: Principal; -} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -293,6 +289,10 @@ export interface OpenIdGetDelegationArgs { salt: Uint8Array; expiration: bigint; } +export interface OpenIdPrepareAutomationArgs { + jwt: string; + controller_id: Principal; +} export interface OpenIdPrepareDelegationArgs { jwt: string; session_key: Uint8Array; @@ -303,6 +303,14 @@ export type Permission = | { Private: null } | { Public: null } | { Managed: null }; +export type PrepareAutomationError = + | { + JwtFindProvider: JwtFindProviderError; + } + | { InvalidController: string } + | { GetCachedJwks: null } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -458,17 +466,11 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } -export type VerifyOpenidAutomationCredentialsError = - | { - GetCachedJwks: null; - } - | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; - authenticate_controller: ActorMethod< - [AuthenticateControllerArgs], - AuthenticateControllerResultResponse + authenticate_automation: ActorMethod< + [AuthenticateAutomationArgs], + AuthenticateAutomationResultResponse >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 1d81905177..0c6ce3f077 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -479,9 +476,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 0f3deb06b6..c757a1fbfc 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -479,9 +476,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 0f3deb06b6..c757a1fbfc 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -479,9 +476,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index 0ee8bfc913..55516847dd 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -34,18 +34,25 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } -export type AuthenticateControllerArgs = { - OpenId: OpenIdAuthenticateControllerArgs; +export type AuthenticateAutomationArgs = { + OpenId: OpenIdPrepareAutomationArgs; }; -export type AuthenticateControllerResultResponse = +export type AuthenticateAutomationResultResponse = | { Ok: null } - | { Err: AuthenticationControllerError }; + | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; delegation: PreparedDelegation; } export type AuthenticationArgs = { OpenId: OpenIdPrepareDelegationArgs }; +export type AuthenticationAutomationError = + | { + PrepareAutomation: PrepareAutomationError; + } + | { RegisterController: string } + | { SaveWorkflowMetadata: string } + | { SaveUniqueJtiToken: string }; export interface AuthenticationConfig { updated_at: [] | [bigint]; openid: [] | [AuthenticationConfigOpenId]; @@ -62,9 +69,6 @@ export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdDelegationProvider, OpenIdAuthProviderConfig]>; } -export type AuthenticationControllerError = - | { RegisterController: string } - | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -73,7 +77,6 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } -export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -279,13 +282,6 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } -export interface OpenIdAuthenticateControllerArgs { - jwt: string; - metadata: Array<[string, string]>; - scope: AutomationScope; - max_time_to_live: [] | [bigint]; - controller_id: Principal; -} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -293,6 +289,10 @@ export interface OpenIdGetDelegationArgs { salt: Uint8Array; expiration: bigint; } +export interface OpenIdPrepareAutomationArgs { + jwt: string; + controller_id: Principal; +} export interface OpenIdPrepareDelegationArgs { jwt: string; session_key: Uint8Array; @@ -303,6 +303,14 @@ export type Permission = | { Private: null } | { Public: null } | { Managed: null }; +export type PrepareAutomationError = + | { + JwtFindProvider: JwtFindProviderError; + } + | { InvalidController: string } + | { GetCachedJwks: null } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -458,17 +466,11 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } -export type VerifyOpenidAutomationCredentialsError = - | { - GetCachedJwks: null; - } - | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; - authenticate_controller: ActorMethod< - [AuthenticateControllerArgs], - AuthenticateControllerResultResponse + authenticate_automation: ActorMethod< + [AuthenticateAutomationArgs], + AuthenticateAutomationResultResponse >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index 1d81905177..0c6ce3f077 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -479,9 +476,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index 0f3deb06b6..c757a1fbfc 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -479,9 +476,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index d4a45fda14..513e374e96 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -20,12 +20,12 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; -type AuthenticateControllerArgs = variant { - OpenId : OpenIdAuthenticateControllerArgs; +type AuthenticateAutomationArgs = variant { + OpenId : OpenIdPrepareAutomationArgs; }; -type AuthenticateControllerResultResponse = variant { +type AuthenticateAutomationResultResponse = variant { Ok; - Err : AuthenticationControllerError; + Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { Ok : Authentication; @@ -33,6 +33,12 @@ type AuthenticateResultResponse = variant { }; type Authentication = record { doc : Doc; delegation : PreparedDelegation }; type AuthenticationArgs = variant { OpenId : OpenIdPrepareDelegationArgs }; +type AuthenticationAutomationError = variant { + PrepareAutomation : PrepareAutomationError; + RegisterController : text; + SaveWorkflowMetadata : text; + SaveUniqueJtiToken : text; +}; type AuthenticationConfig = record { updated_at : opt nat64; openid : opt AuthenticationConfigOpenId; @@ -49,16 +55,11 @@ type AuthenticationConfigOpenId = record { observatory_id : opt principal; providers : vec record { OpenIdDelegationProvider; OpenIdAuthProviderConfig }; }; -type AuthenticationControllerError = variant { - RegisterController : text; - VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; -}; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; -type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -232,13 +233,6 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; -type OpenIdAuthenticateControllerArgs = record { - jwt : text; - metadata : vec record { text; text }; - scope : AutomationScope; - max_time_to_live : opt nat64; - controller_id : principal; -}; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -246,12 +240,23 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; +type OpenIdPrepareAutomationArgs = record { + jwt : text; + controller_id : principal; +}; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; salt : blob; }; type Permission = variant { Controllers; Private; Public; Managed }; +type PrepareAutomationError = variant { + JwtFindProvider : JwtFindProviderError; + InvalidController : text; + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; GetCachedJwks; @@ -393,15 +398,10 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; -type VerifyOpenidAutomationCredentialsError = variant { - GetCachedJwks; - JwtVerify : JwtVerifyError; - GetOrFetchJwks : GetOrRefreshJwksError; -}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); - authenticate_controller : (AuthenticateControllerArgs) -> ( - AuthenticateControllerResultResponse, + authenticate_automation : (AuthenticateAutomationArgs) -> ( + AuthenticateAutomationResultResponse, ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index fa0638de7b..79b94fc603 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -22,12 +22,12 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; -type AuthenticateControllerArgs = variant { - OpenId : OpenIdAuthenticateControllerArgs; +type AuthenticateAutomationArgs = variant { + OpenId : OpenIdPrepareAutomationArgs; }; -type AuthenticateControllerResultResponse = variant { +type AuthenticateAutomationResultResponse = variant { Ok; - Err : AuthenticationControllerError; + Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { Ok : Authentication; @@ -35,6 +35,12 @@ type AuthenticateResultResponse = variant { }; type Authentication = record { doc : Doc; delegation : PreparedDelegation }; type AuthenticationArgs = variant { OpenId : OpenIdPrepareDelegationArgs }; +type AuthenticationAutomationError = variant { + PrepareAutomation : PrepareAutomationError; + RegisterController : text; + SaveWorkflowMetadata : text; + SaveUniqueJtiToken : text; +}; type AuthenticationConfig = record { updated_at : opt nat64; openid : opt AuthenticationConfigOpenId; @@ -51,16 +57,11 @@ type AuthenticationConfigOpenId = record { observatory_id : opt principal; providers : vec record { OpenIdDelegationProvider; OpenIdAuthProviderConfig }; }; -type AuthenticationControllerError = variant { - RegisterController : text; - VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; -}; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; -type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -234,13 +235,6 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; -type OpenIdAuthenticateControllerArgs = record { - jwt : text; - metadata : vec record { text; text }; - scope : AutomationScope; - max_time_to_live : opt nat64; - controller_id : principal; -}; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -248,12 +242,23 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; +type OpenIdPrepareAutomationArgs = record { + jwt : text; + controller_id : principal; +}; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; salt : blob; }; type Permission = variant { Controllers; Private; Public; Managed }; +type PrepareAutomationError = variant { + JwtFindProvider : JwtFindProviderError; + InvalidController : text; + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; GetCachedJwks; @@ -395,15 +400,10 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; -type VerifyOpenidAutomationCredentialsError = variant { - GetCachedJwks; - JwtVerify : JwtVerifyError; - GetOrFetchJwks : GetOrRefreshJwksError; -}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); - authenticate_controller : (AuthenticateControllerArgs) -> ( - AuthenticateControllerResultResponse, + authenticate_automation : (AuthenticateAutomationArgs) -> ( + AuthenticateAutomationResultResponse, ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 1e0c420adb..32d4dad4a4 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -22,12 +22,12 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; -type AuthenticateControllerArgs = variant { - OpenId : OpenIdAuthenticateControllerArgs; +type AuthenticateAutomationArgs = variant { + OpenId : OpenIdPrepareAutomationArgs; }; -type AuthenticateControllerResultResponse = variant { +type AuthenticateAutomationResultResponse = variant { Ok; - Err : AuthenticationControllerError; + Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { Ok : Authentication; @@ -35,6 +35,12 @@ type AuthenticateResultResponse = variant { }; type Authentication = record { doc : Doc; delegation : PreparedDelegation }; type AuthenticationArgs = variant { OpenId : OpenIdPrepareDelegationArgs }; +type AuthenticationAutomationError = variant { + PrepareAutomation : PrepareAutomationError; + RegisterController : text; + SaveWorkflowMetadata : text; + SaveUniqueJtiToken : text; +}; type AuthenticationConfig = record { updated_at : opt nat64; openid : opt AuthenticationConfigOpenId; @@ -51,16 +57,11 @@ type AuthenticationConfigOpenId = record { observatory_id : opt principal; providers : vec record { OpenIdDelegationProvider; OpenIdAuthProviderConfig }; }; -type AuthenticationControllerError = variant { - RegisterController : text; - VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; -}; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; -type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -234,13 +235,6 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; -type OpenIdAuthenticateControllerArgs = record { - jwt : text; - metadata : vec record { text; text }; - scope : AutomationScope; - max_time_to_live : opt nat64; - controller_id : principal; -}; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -248,12 +242,23 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; +type OpenIdPrepareAutomationArgs = record { + jwt : text; + controller_id : principal; +}; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; salt : blob; }; type Permission = variant { Controllers; Private; Public; Managed }; +type PrepareAutomationError = variant { + JwtFindProvider : JwtFindProviderError; + InvalidController : text; + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; GetCachedJwks; @@ -395,15 +400,10 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; -type VerifyOpenidAutomationCredentialsError = variant { - GetCachedJwks; - JwtVerify : JwtVerifyError; - GetOrFetchJwks : GetOrRefreshJwksError; -}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); - authenticate_controller : (AuthenticateControllerArgs) -> ( - AuthenticateControllerResultResponse, + authenticate_automation : (AuthenticateAutomationArgs) -> ( + AuthenticateAutomationResultResponse, ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 0248c33ef6..7555fba72f 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -34,18 +34,25 @@ export interface AssetNoContent { export interface AssetsUpgradeOptions { clear_existing_assets: [] | [boolean]; } -export type AuthenticateControllerArgs = { - OpenId: OpenIdAuthenticateControllerArgs; +export type AuthenticateAutomationArgs = { + OpenId: OpenIdPrepareAutomationArgs; }; -export type AuthenticateControllerResultResponse = +export type AuthenticateAutomationResultResponse = | { Ok: null } - | { Err: AuthenticationControllerError }; + | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { doc: Doc; delegation: PreparedDelegation; } export type AuthenticationArgs = { OpenId: OpenIdPrepareDelegationArgs }; +export type AuthenticationAutomationError = + | { + PrepareAutomation: PrepareAutomationError; + } + | { RegisterController: string } + | { SaveWorkflowMetadata: string } + | { SaveUniqueJtiToken: string }; export interface AuthenticationConfig { updated_at: [] | [bigint]; openid: [] | [AuthenticationConfigOpenId]; @@ -62,9 +69,6 @@ export interface AuthenticationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdDelegationProvider, OpenIdAuthProviderConfig]>; } -export type AuthenticationControllerError = - | { RegisterController: string } - | { VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError }; export type AuthenticationError = | { PrepareDelegation: PrepareDelegationError; @@ -73,7 +77,6 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } -export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -279,13 +282,6 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } -export interface OpenIdAuthenticateControllerArgs { - jwt: string; - metadata: Array<[string, string]>; - scope: AutomationScope; - max_time_to_live: [] | [bigint]; - controller_id: Principal; -} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -293,6 +289,10 @@ export interface OpenIdGetDelegationArgs { salt: Uint8Array; expiration: bigint; } +export interface OpenIdPrepareAutomationArgs { + jwt: string; + controller_id: Principal; +} export interface OpenIdPrepareDelegationArgs { jwt: string; session_key: Uint8Array; @@ -303,6 +303,14 @@ export type Permission = | { Private: null } | { Public: null } | { Managed: null }; +export type PrepareAutomationError = + | { + JwtFindProvider: JwtFindProviderError; + } + | { InvalidController: string } + | { GetCachedJwks: null } + | { JwtVerify: JwtVerifyError } + | { GetOrFetchJwks: GetOrRefreshJwksError }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -459,17 +467,11 @@ export interface UploadChunk { export interface UploadChunkResult { chunk_id: bigint; } -export type VerifyOpenidAutomationCredentialsError = - | { - GetCachedJwks: null; - } - | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; export interface _SERVICE { authenticate: ActorMethod<[AuthenticationArgs], AuthenticateResultResponse>; - authenticate_controller: ActorMethod< - [AuthenticateControllerArgs], - AuthenticateControllerResultResponse + authenticate_automation: ActorMethod< + [AuthenticateAutomationArgs], + AuthenticateAutomationResultResponse >; commit_asset_upload: ActorMethod<[CommitBatch], undefined>; commit_proposal: ActorMethod<[CommitProposal], null>; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index 4827d8f258..76d1444ca8 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -480,9 +477,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index ccd764a437..2a4b4a4dbe 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -75,32 +75,29 @@ export const idlFactory = ({ IDL }) => { Ok: Authentication, Err: AuthenticationError }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); - const OpenIdAuthenticateControllerArgs = IDL.Record({ + const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), - scope: AutomationScope, - max_time_to_live: IDL.Opt(IDL.Nat64), controller_id: IDL.Principal }); - const AuthenticateControllerArgs = IDL.Variant({ - OpenId: OpenIdAuthenticateControllerArgs + const AuthenticateAutomationArgs = IDL.Variant({ + OpenId: OpenIdPrepareAutomationArgs }); - const VerifyOpenidAutomationCredentialsError = IDL.Variant({ + const PrepareAutomationError = IDL.Variant({ + JwtFindProvider: JwtFindProviderError, + InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, GetOrFetchJwks: GetOrRefreshJwksError }); - const AuthenticationControllerError = IDL.Variant({ + const AuthenticationAutomationError = IDL.Variant({ + PrepareAutomation: PrepareAutomationError, RegisterController: IDL.Text, - VerifyOpenIdCredentials: VerifyOpenidAutomationCredentialsError + SaveWorkflowMetadata: IDL.Text, + SaveUniqueJtiToken: IDL.Text }); - const AuthenticateControllerResultResponse = IDL.Variant({ + const AuthenticateAutomationResultResponse = IDL.Variant({ Ok: IDL.Null, - Err: AuthenticationControllerError + Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ batch_id: IDL.Nat, @@ -480,9 +477,9 @@ export const idlFactory = ({ IDL }) => { return IDL.Service({ authenticate: IDL.Func([AuthenticationArgs], [AuthenticateResultResponse], []), - authenticate_controller: IDL.Func( - [AuthenticateControllerArgs], - [AuthenticateControllerResultResponse], + authenticate_automation: IDL.Func( + [AuthenticateAutomationArgs], + [AuthenticateAutomationResultResponse], [] ), commit_asset_upload: IDL.Func([CommitBatch], [], []), diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index a7874deec0..fb6db0b21e 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -22,12 +22,12 @@ type AssetNoContent = record { version : opt nat64; }; type AssetsUpgradeOptions = record { clear_existing_assets : opt bool }; -type AuthenticateControllerArgs = variant { - OpenId : OpenIdAuthenticateControllerArgs; +type AuthenticateAutomationArgs = variant { + OpenId : OpenIdPrepareAutomationArgs; }; -type AuthenticateControllerResultResponse = variant { +type AuthenticateAutomationResultResponse = variant { Ok; - Err : AuthenticationControllerError; + Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { Ok : Authentication; @@ -35,6 +35,12 @@ type AuthenticateResultResponse = variant { }; type Authentication = record { doc : Doc; delegation : PreparedDelegation }; type AuthenticationArgs = variant { OpenId : OpenIdPrepareDelegationArgs }; +type AuthenticationAutomationError = variant { + PrepareAutomation : PrepareAutomationError; + RegisterController : text; + SaveWorkflowMetadata : text; + SaveUniqueJtiToken : text; +}; type AuthenticationConfig = record { updated_at : opt nat64; openid : opt AuthenticationConfigOpenId; @@ -51,16 +57,11 @@ type AuthenticationConfigOpenId = record { observatory_id : opt principal; providers : vec record { OpenIdDelegationProvider; OpenIdAuthProviderConfig }; }; -type AuthenticationControllerError = variant { - RegisterController : text; - VerifyOpenIdCredentials : VerifyOpenidAutomationCredentialsError; -}; type AuthenticationError = variant { PrepareDelegation : PrepareDelegationError; RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; -type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -234,13 +235,6 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; -type OpenIdAuthenticateControllerArgs = record { - jwt : text; - metadata : vec record { text; text }; - scope : AutomationScope; - max_time_to_live : opt nat64; - controller_id : principal; -}; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -248,12 +242,23 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; +type OpenIdPrepareAutomationArgs = record { + jwt : text; + controller_id : principal; +}; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; salt : blob; }; type Permission = variant { Controllers; Private; Public; Managed }; +type PrepareAutomationError = variant { + JwtFindProvider : JwtFindProviderError; + InvalidController : text; + GetCachedJwks; + JwtVerify : JwtVerifyError; + GetOrFetchJwks : GetOrRefreshJwksError; +}; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; GetCachedJwks; @@ -395,15 +400,10 @@ type UploadChunk = record { order_id : opt nat; }; type UploadChunkResult = record { chunk_id : nat }; -type VerifyOpenidAutomationCredentialsError = variant { - GetCachedJwks; - JwtVerify : JwtVerifyError; - GetOrFetchJwks : GetOrRefreshJwksError; -}; service : (InitSatelliteArgs) -> { authenticate : (AuthenticationArgs) -> (AuthenticateResultResponse); - authenticate_controller : (AuthenticateControllerArgs) -> ( - AuthenticateControllerResultResponse, + authenticate_automation : (AuthenticateAutomationArgs) -> ( + AuthenticateAutomationResultResponse, ); commit_asset_upload : (CommitBatch) -> (); commit_proposal : (CommitProposal) -> (null); From b32d1697e885b9c7527d476c9fd8d0a324026b92 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 12:06:57 +0100 Subject: [PATCH 48/64] feat: no rate config jti is unique we can always add one --- src/libs/satellite/src/automation/authenticate.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index 38a80f8fcb..d917d4cea1 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -9,12 +9,9 @@ use junobuild_auth::state::get_automation_providers; pub async fn openid_authenticate_automation( args: &OpenIdPrepareAutomationArgs, - // TODO: Result> ) -> Result { let providers = get_automation_providers(&AuthHeap)?; - // TODO: rate_config of collection? - let prepared_automation = automation::openid_prepare_automation(args, &providers).await; let result = match prepared_automation { From bf146869727b5da736935dbe53b69504b3ce10f7 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 12:45:55 +0100 Subject: [PATCH 49/64] feat: controllers assertion --- src/libs/auth/src/automation/prepare.rs | 28 +++++++++++++++---- src/libs/auth/src/automation/types.rs | 2 ++ src/libs/auth/src/state/heap.rs | 1 + src/libs/auth/src/strategies.rs | 5 ++++ .../satellite/src/automation/automation.rs | 8 +++++- src/libs/satellite/src/automation/mod.rs | 1 + .../src/automation/strategy_impls.rs | 11 ++++++++ .../src/controllers/segment.rs | 5 +++- 8 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 src/libs/satellite/src/automation/strategy_impls.rs diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs index cfb69840aa..8d7002c604 100644 --- a/src/libs/auth/src/automation/prepare.rs +++ b/src/libs/auth/src/automation/prepare.rs @@ -5,25 +5,41 @@ use crate::automation::types::{ use crate::automation::utils::duration::build_expiration; use crate::automation::utils::scope::build_scope; use crate::openid::types::provider::OpenIdAutomationProvider; -use crate::strategies::AuthHeapStrategy; -use junobuild_shared::segments::controllers::assert_controllers; +use crate::strategies::{AuthAutomationStrategy, AuthHeapStrategy}; +use junobuild_shared::segments::controllers::{ + assert_controllers, assert_max_number_of_controllers, +}; use junobuild_shared::types::state::ControllerId; pub fn openid_prepare_automation( controller_id: &ControllerId, provider: &OpenIdAutomationProvider, auth_heap: &impl AuthHeapStrategy, + auth_automation: &impl AuthAutomationStrategy, ) -> PrepareAutomationResult { - let controllers: [ControllerId; 1] = [controller_id.clone()]; + let existing_controllers = auth_automation.get_controllers(); - assert_controllers(&controllers).map_err(PrepareAutomationError::InvalidController)?; + if existing_controllers.contains_key(controller_id) { + return Err(PrepareAutomationError::ControllerAlreadyExists); + } - // TODO: Assert do not exist + let submitted_controllers: [ControllerId; 1] = [controller_id.clone()]; - let expires_at = build_expiration(provider, auth_heap); + assert_controllers(&submitted_controllers) + .map_err(PrepareAutomationError::InvalidController)?; let scope = build_scope(provider, auth_heap); + assert_max_number_of_controllers( + &existing_controllers, + &submitted_controllers, + &scope.clone().into(), + None, + ) + .map_err(PrepareAutomationError::TooManyControllers)?; + + let expires_at = build_expiration(provider, auth_heap); + let controller: PreparedControllerAutomation = PreparedControllerAutomation { id: controller_id.clone(), expires_at, diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index f78c00889b..34c4c9dcf1 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -35,7 +35,9 @@ pub enum AutomationScope { #[derive(CandidType, Serialize, Deserialize, Debug)] pub enum PrepareAutomationError { + ControllerAlreadyExists, InvalidController(String), + TooManyControllers(String), GetOrFetchJwks(GetOrRefreshJwksError), GetCachedJwks, JwtFindProvider(JwtFindProviderError), diff --git a/src/libs/auth/src/state/heap.rs b/src/libs/auth/src/state/heap.rs index cf26ac3164..d3ee135103 100644 --- a/src/libs/auth/src/state/heap.rs +++ b/src/libs/auth/src/state/heap.rs @@ -5,6 +5,7 @@ use crate::state::types::state::Salt; use crate::state::types::state::{AuthenticationHeapState, OpenIdCachedCertificate, OpenIdState}; use crate::strategies::AuthHeapStrategy; use std::collections::hash_map::Entry; + // --------------------------------------------------------- // Config // --------------------------------------------------------- diff --git a/src/libs/auth/src/strategies.rs b/src/libs/auth/src/strategies.rs index 62e58a9768..a043e3e478 100644 --- a/src/libs/auth/src/strategies.rs +++ b/src/libs/auth/src/strategies.rs @@ -1,5 +1,6 @@ use crate::state::types::state::AuthenticationHeapState; use ic_certification::Hash; +use junobuild_shared::types::state::Controllers; pub trait AuthHeapStrategy { fn with_auth_state(&self, f: impl FnOnce(&Option) -> R) -> R; @@ -15,3 +16,7 @@ pub trait AuthCertificateStrategy { fn get_asset_hashes_root_hash(&self) -> Hash; } + +pub trait AuthAutomationStrategy { + fn get_controllers(&self) -> Controllers; +} diff --git a/src/libs/satellite/src/automation/automation.rs b/src/libs/satellite/src/automation/automation.rs index 699955de1f..ce4ce3ba36 100644 --- a/src/libs/satellite/src/automation/automation.rs +++ b/src/libs/satellite/src/automation/automation.rs @@ -1,4 +1,5 @@ use crate::auth::strategy_impls::AuthHeap; +use crate::automation::strategy_impls::AuthAutomation; use junobuild_auth::automation; use junobuild_auth::automation::types::{ OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, @@ -31,7 +32,12 @@ pub async fn openid_prepare_automation( Err(err) => return Err(PrepareAutomationError::from(err)), }; - let result = automation::openid_prepare_automation(&args.controller_id, &provider, &AuthHeap); + let result = automation::openid_prepare_automation( + &args.controller_id, + &provider, + &AuthHeap, + &AuthAutomation, + ); result.map(|prepared_automation| (prepared_automation, provider, credential)) } diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index adeb56b732..8d0c64110f 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,6 +1,7 @@ pub mod authenticate; mod automation; mod controllers; +mod strategy_impls; mod token; pub mod types; mod workflow; diff --git a/src/libs/satellite/src/automation/strategy_impls.rs b/src/libs/satellite/src/automation/strategy_impls.rs new file mode 100644 index 0000000000..6f16284c64 --- /dev/null +++ b/src/libs/satellite/src/automation/strategy_impls.rs @@ -0,0 +1,11 @@ +use crate::get_controllers; +use junobuild_auth::strategies::AuthAutomationStrategy; +use junobuild_shared::types::state::Controllers; + +pub struct AuthAutomation; + +impl AuthAutomationStrategy for AuthAutomation { + fn get_controllers(&self) -> Controllers { + get_controllers() + } +} diff --git a/src/mission_control/src/controllers/segment.rs b/src/mission_control/src/controllers/segment.rs index a8baae5c45..25b0c0880a 100644 --- a/src/mission_control/src/controllers/segment.rs +++ b/src/mission_control/src/controllers/segment.rs @@ -2,7 +2,10 @@ use candid::Principal; use ic_cdk::call::Call; use junobuild_shared::ic::DecodeCandid; use junobuild_shared::mgmt::ic::update_canister_controllers; -use junobuild_shared::segments::controllers::{assert_controller_expiration, assert_controllers, assert_max_number_of_controllers, filter_admin_controllers, into_controller_ids}; +use junobuild_shared::segments::controllers::{ + assert_controller_expiration, assert_controllers, assert_max_number_of_controllers, + filter_admin_controllers, into_controller_ids, +}; use junobuild_shared::types::interface::{ DeleteControllersArgs, SetController, SetControllersArgs, }; From 02f0b7a3996f224f5bb5c9c3d6b0601052460a0f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 13:23:52 +0100 Subject: [PATCH 50/64] chore: clippy --- src/libs/auth/src/automation/prepare.rs | 4 ++-- src/libs/auth/src/automation/types.rs | 3 --- .../src/openid/credentials/automation/verify.rs | 8 ++++---- .../satellite/src/automation/authenticate.rs | 4 ++-- .../src/automation/controllers/register.rs | 2 +- src/libs/satellite/src/automation/mod.rs | 2 +- .../automation/{automation.rs => prepare.rs} | 0 .../satellite/src/automation/token/impls.rs | 6 +++--- .../satellite/src/automation/token/services.rs | 2 +- .../satellite/src/automation/workflow/impls.rs | 17 ++++------------- .../satellite/src/automation/workflow/utils.rs | 2 +- src/mission_control/src/controllers/segment.rs | 5 ++--- src/observatory/src/openid/scheduler.rs | 2 +- 13 files changed, 22 insertions(+), 35 deletions(-) rename src/libs/satellite/src/automation/{automation.rs => prepare.rs} (100%) diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs index 8d7002c604..d9b72a6c3c 100644 --- a/src/libs/auth/src/automation/prepare.rs +++ b/src/libs/auth/src/automation/prepare.rs @@ -23,7 +23,7 @@ pub fn openid_prepare_automation( return Err(PrepareAutomationError::ControllerAlreadyExists); } - let submitted_controllers: [ControllerId; 1] = [controller_id.clone()]; + let submitted_controllers: [ControllerId; 1] = [*controller_id]; assert_controllers(&submitted_controllers) .map_err(PrepareAutomationError::InvalidController)?; @@ -41,7 +41,7 @@ pub fn openid_prepare_automation( let expires_at = build_expiration(provider, auth_heap); let controller: PreparedControllerAutomation = PreparedControllerAutomation { - id: controller_id.clone(), + id: *controller_id, expires_at, scope, }; diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index 34c4c9dcf1..071816b97a 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -1,9 +1,6 @@ -use crate::delegation::types::SessionKey; use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; -use crate::state::types::state::Salt; use candid::{CandidType, Deserialize}; -use junobuild_shared::types::interface::SetController; use junobuild_shared::types::state::ControllerId; use serde::Serialize; diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 9d9d16c3c6..165df19d63 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -38,7 +38,7 @@ pub async fn verify_openid_credentials_with_jwks_renewal( .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &automation_provider, &config) + verify_openid_credentials(jwt, &jwks, &automation_provider, config) } fn verify_openid_credentials( @@ -87,7 +87,7 @@ fn verify_openid_credentials( Ok(()) }; - let assert_custom = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + let assert_custom = |_claims: &AutomationClaims| -> Result<(), JwtVerifyError> { // No custom assertion for automation. Replay attack protection must notably be implemented by consumer. Ok(()) @@ -97,8 +97,8 @@ fn verify_openid_credentials( jwt, provider.issuers(), &jwks.keys, - &assert_audience, - &assert_custom, + assert_audience, + assert_custom, ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index d917d4cea1..b3e7a62fe4 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -1,6 +1,6 @@ use crate::auth::strategy_impls::AuthHeap; -use crate::automation::automation; use crate::automation::controllers::register::register_controller; +use crate::automation::prepare; use crate::automation::token::save_unique_token_jti; use crate::automation::types::{AuthenticateAutomationResult, AuthenticationAutomationError}; use crate::automation::workflow::save_workflow_metadata; @@ -12,7 +12,7 @@ pub async fn openid_authenticate_automation( ) -> Result { let providers = get_automation_providers(&AuthHeap)?; - let prepared_automation = automation::openid_prepare_automation(args, &providers).await; + let prepared_automation = prepare::openid_prepare_automation(args, &providers).await; let result = match prepared_automation { Ok((automation, provider, credential)) => { diff --git a/src/libs/satellite/src/automation/controllers/register.rs b/src/libs/satellite/src/automation/controllers/register.rs index 753e3413f0..47a38bf641 100644 --- a/src/libs/satellite/src/automation/controllers/register.rs +++ b/src/libs/satellite/src/automation/controllers/register.rs @@ -11,7 +11,7 @@ pub fn register_controller( provider: &OpenIdAutomationProvider, credential: &OpenIdAutomationCredential, ) -> Result<(), String> { - let controllers: [ControllerId; 1] = [automation.controller.id.clone()]; + let controllers: [ControllerId; 1] = [automation.controller.id]; let automation_workflow_key = build_automation_workflow_key(provider, credential)?; diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index 8d0c64110f..d2cb1026c2 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,6 +1,6 @@ pub mod authenticate; -mod automation; mod controllers; +mod prepare; mod strategy_impls; mod token; pub mod types; diff --git a/src/libs/satellite/src/automation/automation.rs b/src/libs/satellite/src/automation/prepare.rs similarity index 100% rename from src/libs/satellite/src/automation/automation.rs rename to src/libs/satellite/src/automation/prepare.rs diff --git a/src/libs/satellite/src/automation/token/impls.rs b/src/libs/satellite/src/automation/token/impls.rs index 584a3f6c2b..923aafa618 100644 --- a/src/libs/satellite/src/automation/token/impls.rs +++ b/src/libs/satellite/src/automation/token/impls.rs @@ -4,15 +4,15 @@ use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; use junobuild_utils::encode_doc_data; impl AutomationTokenKey { - pub fn create(provider: &OpenIdAutomationProvider, jti: &String) -> Self { + pub fn create(provider: &OpenIdAutomationProvider, jti: &str) -> Self { Self { provider: provider.clone(), - jti: jti.clone(), + jti: jti.to_owned(), } } pub fn to_key(&self) -> String { - format!("{}#{}", self.provider.to_string(), self.jti) + format!("{}#{}", self.provider, self.jti) } } diff --git a/src/libs/satellite/src/automation/token/services.rs b/src/libs/satellite/src/automation/token/services.rs index e164585d88..a01541fb78 100644 --- a/src/libs/satellite/src/automation/token/services.rs +++ b/src/libs/satellite/src/automation/token/services.rs @@ -25,7 +25,7 @@ pub fn save_unique_token_jti( return Err(JUNO_AUTOMATION_TOKEN_ERROR_MISSING_JTI.to_string()); }; - let automation_token_key = AutomationTokenKey::create(provider, &jti).to_key(); + let automation_token_key = AutomationTokenKey::create(provider, jti).to_key(); let automation_token_collection = COLLECTION_AUTOMATION_TOKEN_KEY.to_string(); diff --git a/src/libs/satellite/src/automation/workflow/impls.rs b/src/libs/satellite/src/automation/workflow/impls.rs index f84c41c980..714a4872a7 100644 --- a/src/libs/satellite/src/automation/workflow/impls.rs +++ b/src/libs/satellite/src/automation/workflow/impls.rs @@ -4,25 +4,16 @@ use junobuild_auth::openid::types::provider::OpenIdAutomationProvider; use junobuild_utils::encode_doc_data; impl AutomationWorkflowKey { - pub fn create( - provider: &OpenIdAutomationProvider, - repository: &String, - run_id: &String, - ) -> Self { + pub fn create(provider: &OpenIdAutomationProvider, repository: &str, run_id: &str) -> Self { Self { provider: provider.clone(), - repository: repository.clone(), - run_id: run_id.clone(), + repository: repository.to_owned(), + run_id: run_id.to_owned(), } } pub fn to_key(&self) -> String { - format!( - "{}#{}#{}", - self.provider.to_string(), - self.repository, - self.run_id - ) + format!("{}#{}#{}", self.provider, self.repository, self.run_id) } } diff --git a/src/libs/satellite/src/automation/workflow/utils.rs b/src/libs/satellite/src/automation/workflow/utils.rs index 32bc9bce71..ed50122497 100644 --- a/src/libs/satellite/src/automation/workflow/utils.rs +++ b/src/libs/satellite/src/automation/workflow/utils.rs @@ -22,7 +22,7 @@ pub fn build_automation_workflow_key( return Err(JUNO_AUTOMATION_WORKFLOW_ERROR_MISSING_RUN_ID.to_string()); }; - let automation_workflow_key = AutomationWorkflowKey::create(provider, &repository, &run_id); + let automation_workflow_key = AutomationWorkflowKey::create(provider, repository, run_id); Ok(automation_workflow_key) } diff --git a/src/mission_control/src/controllers/segment.rs b/src/mission_control/src/controllers/segment.rs index 25b0c0880a..5d39f97f15 100644 --- a/src/mission_control/src/controllers/segment.rs +++ b/src/mission_control/src/controllers/segment.rs @@ -3,8 +3,7 @@ use ic_cdk::call::Call; use junobuild_shared::ic::DecodeCandid; use junobuild_shared::mgmt::ic::update_canister_controllers; use junobuild_shared::segments::controllers::{ - assert_controller_expiration, assert_controllers, assert_max_number_of_controllers, - filter_admin_controllers, into_controller_ids, + assert_controller_expiration, assert_controllers, filter_admin_controllers, into_controller_ids, }; use junobuild_shared::types::interface::{ DeleteControllersArgs, SetController, SetControllersArgs, @@ -18,7 +17,7 @@ pub async fn set_segment_controllers( ) -> Result<(), String> { assert_controllers(controllers)?; - assert_controller_expiration(&controller)?; + assert_controller_expiration(controller)?; let satellite_admin_controllers = set_controllers(segment_id, controllers, controller).await?; diff --git a/src/observatory/src/openid/scheduler.rs b/src/observatory/src/openid/scheduler.rs index 06f63c206f..979bae3b08 100644 --- a/src/observatory/src/openid/scheduler.rs +++ b/src/observatory/src/openid/scheduler.rs @@ -15,7 +15,7 @@ pub fn defer_restart_monitoring() { OpenIdProvider::GitHubActions, ] .into_iter() - .filter(|provider| is_scheduler_enabled(provider)) + .filter(is_scheduler_enabled) .count(); if enabled_count == 0 { From e52366365de34df85736ea1ba054bb6cfc58ee74 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 13:45:21 +0100 Subject: [PATCH 51/64] chore: remove moved mod --- src/libs/auth/src/state/store.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/auth/src/state/store.rs b/src/libs/auth/src/state/store.rs index 5e6b68f0c1..9782fa2cc6 100644 --- a/src/libs/auth/src/state/store.rs +++ b/src/libs/auth/src/state/store.rs @@ -2,7 +2,6 @@ use crate::errors::{ JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED, JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISABLED, }; -use crate::state::assert::assert_set_config; use crate::state::asserts::authentication::assert_set_authentication_config; use crate::state::heap::insert_config; use crate::state::heap::{get_automation, get_config}; From 5c15bef07ff54b9030d7ae9077757bfd740b8c6f Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 13:53:46 +0100 Subject: [PATCH 52/64] feat: set automation --- src/libs/auth/src/state/asserts/automation.rs | 24 +++++++++++++ src/libs/auth/src/state/asserts/mod.rs | 6 +++- src/libs/auth/src/state/heap.rs | 8 ++--- src/libs/auth/src/state/impls.rs | 34 ++++++++++++++++++- src/libs/auth/src/state/store.rs | 23 ++++++++++--- src/libs/auth/src/state/types.rs | 7 ++++ 6 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 src/libs/auth/src/state/asserts/automation.rs diff --git a/src/libs/auth/src/state/asserts/automation.rs b/src/libs/auth/src/state/asserts/automation.rs new file mode 100644 index 0000000000..8a1bf6815b --- /dev/null +++ b/src/libs/auth/src/state/asserts/automation.rs @@ -0,0 +1,24 @@ +use crate::state::types::automation::AutomationConfig; +use crate::state::types::interface::SetAutomationConfig; +use junobuild_shared::assert::assert_version; +use junobuild_shared::types::state::Version; + +pub fn assert_set_automation_config( + proposed_config: &SetAutomationConfig, + current_config: &Option, +) -> Result<(), String> { + assert_config_version(current_config, proposed_config.version)?; + + Ok(()) +} + +fn assert_config_version( + current_config: &Option, + proposed_version: Option, +) -> Result<(), String> { + if let Some(cfg) = current_config { + assert_version(proposed_version, cfg.version)? + } + + Ok(()) +} diff --git a/src/libs/auth/src/state/asserts/mod.rs b/src/libs/auth/src/state/asserts/mod.rs index bf3f94276e..90b545c98b 100644 --- a/src/libs/auth/src/state/asserts/mod.rs +++ b/src/libs/auth/src/state/asserts/mod.rs @@ -1 +1,5 @@ -pub mod authentication; +mod authentication; +mod automation; + +pub use authentication::*; +pub use automation::*; diff --git a/src/libs/auth/src/state/heap.rs b/src/libs/auth/src/state/heap.rs index d3ee135103..333b7978f5 100644 --- a/src/libs/auth/src/state/heap.rs +++ b/src/libs/auth/src/state/heap.rs @@ -45,25 +45,25 @@ pub fn get_automation(auth_heap: &impl AuthHeapStrategy) -> Option) { +pub fn insert_automation(auth_heap: &impl AuthHeapStrategy, automation: &AutomationConfig) { auth_heap .with_auth_state_mut(|authentication| insert_automation_impl(automation, authentication)) } fn insert_automation_impl( - automation: &Option, + automation: &AutomationConfig, state: &mut Option, ) { match state { None => { *state = Some(AuthenticationHeapState { config: AuthenticationConfig::default(), - automation: automation.clone(), + automation: Some(automation.clone()), salt: None, openid: None, }) } - Some(state) => state.automation = automation.clone(), + Some(state) => state.automation = Some(automation.clone()), } } diff --git a/src/libs/auth/src/state/impls.rs b/src/libs/auth/src/state/impls.rs index bba2acce0f..80c7996bbb 100644 --- a/src/libs/auth/src/state/impls.rs +++ b/src/libs/auth/src/state/impls.rs @@ -1,6 +1,7 @@ use crate::openid::types::provider::OpenIdCertificate; +use crate::state::types::automation::AutomationConfig; use crate::state::types::config::AuthenticationConfig; -use crate::state::types::interface::SetAuthenticationConfig; +use crate::state::types::interface::{SetAuthenticationConfig, SetAutomationConfig}; use crate::state::types::state::{OpenIdCachedCertificate, OpenIdLastFetchAttempt}; use ic_cdk::api::time; use junobuild_shared::data::version::next_version; @@ -47,6 +48,37 @@ impl AuthenticationConfig { } } +impl AutomationConfig { + pub fn prepare( + current_config: &Option, + user_config: &SetAutomationConfig, + ) -> Self { + let now = time(); + + let created_at: Timestamp = match current_config { + None => now, + Some(current_doc) => current_doc.created_at.unwrap_or(now), + }; + + let version = next_version(current_config); + + let updated_at: Timestamp = now; + + AutomationConfig { + openid: user_config.openid.clone(), + created_at: Some(created_at), + updated_at: Some(updated_at), + version: Some(version), + } + } +} + +impl Versioned for AutomationConfig { + fn version(&self) -> Option { + self.version + } +} + impl OpenIdCachedCertificate { pub fn init() -> Self { Self { diff --git a/src/libs/auth/src/state/store.rs b/src/libs/auth/src/state/store.rs index 9782fa2cc6..d5b43edf47 100644 --- a/src/libs/auth/src/state/store.rs +++ b/src/libs/auth/src/state/store.rs @@ -2,12 +2,12 @@ use crate::errors::{ JUNO_AUTH_ERROR_AUTOMATION_NOT_CONFIGURED, JUNO_AUTH_ERROR_NOT_CONFIGURED, JUNO_AUTH_ERROR_OPENID_DISABLED, }; -use crate::state::asserts::authentication::assert_set_authentication_config; -use crate::state::heap::insert_config; +use crate::state::asserts::{assert_set_authentication_config, assert_set_automation_config}; use crate::state::heap::{get_automation, get_config}; -use crate::state::types::automation::OpenIdAutomationProviders; +use crate::state::heap::{insert_automation, insert_config}; +use crate::state::types::automation::{AutomationConfig, OpenIdAutomationProviders}; use crate::state::types::config::{AuthenticationConfig, OpenIdAuthProviders}; -use crate::state::types::interface::SetAuthenticationConfig; +use crate::state::types::interface::{SetAuthenticationConfig, SetAutomationConfig}; use crate::state::{get_salt, insert_salt}; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::print; @@ -28,6 +28,21 @@ pub fn set_authentication_config( Ok(config) } +pub fn set_automation_config( + auth_heap: &impl AuthHeapStrategy, + proposed_config: &SetAutomationConfig, +) -> Result { + let current_config = get_automation(auth_heap); + + assert_set_automation_config(proposed_config, ¤t_config)?; + + let config = AutomationConfig::prepare(¤t_config, proposed_config); + + insert_automation(auth_heap, &config); + + Ok(config) +} + pub async fn init_salt(auth_heap: &impl AuthHeapStrategy) -> Result<(), String> { let existing_salt = get_salt(auth_heap); diff --git a/src/libs/auth/src/state/types.rs b/src/libs/auth/src/state/types.rs index 114f2d32f5..0e2a9668fb 100644 --- a/src/libs/auth/src/state/types.rs +++ b/src/libs/auth/src/state/types.rs @@ -168,6 +168,7 @@ pub mod automation { } pub mod interface { + use crate::state::types::automation::AutomationConfigOpenId; use crate::state::types::config::{ AuthenticationConfigInternetIdentity, AuthenticationConfigOpenId, AuthenticationRules, }; @@ -182,4 +183,10 @@ pub mod interface { pub rules: Option, pub version: Option, } + + #[derive(Default, CandidType, Serialize, Deserialize, Clone)] + pub struct SetAutomationConfig { + pub openid: Option, + pub version: Option, + } } From 3ffd51f30a7861f76beead915553ebce6651eb54 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 14:28:47 +0100 Subject: [PATCH 53/64] feat: expose set automation config --- src/libs/satellite/src/api/config.rs | 18 +++++++++++++++++- src/libs/satellite/src/automation/mod.rs | 1 + src/libs/satellite/src/automation/store.rs | 14 ++++++++++++++ src/libs/satellite/src/lib.rs | 19 ++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/libs/satellite/src/automation/store.rs diff --git a/src/libs/satellite/src/api/config.rs b/src/libs/satellite/src/api/config.rs index 962db010ce..cc59998421 100644 --- a/src/libs/satellite/src/api/config.rs +++ b/src/libs/satellite/src/api/config.rs @@ -4,14 +4,18 @@ use crate::assets::storage::store::{ use crate::auth::store::{ get_config as get_auth_config_store, set_config as set_auth_config_store, }; +use crate::automation::store::{ + get_config as get_automation_config_store, set_config as set_automation_config_store, +}; use crate::db::store::{ get_config_store as get_db_config_store, set_config_store as set_db_config_store, }; use crate::db::types::config::DbConfig; use crate::db::types::interface::SetDbConfig; use crate::types::interface::Config; +use junobuild_auth::state::types::automation::AutomationConfig; use junobuild_auth::state::types::config::AuthenticationConfig; -use junobuild_auth::state::types::interface::SetAuthenticationConfig; +use junobuild_auth::state::types::interface::{SetAuthenticationConfig, SetAutomationConfig}; use junobuild_shared::ic::UnwrapOrTrap; use junobuild_storage::types::config::StorageConfig; use junobuild_storage::types::interface::SetStorageConfig; @@ -44,6 +48,18 @@ pub fn get_auth_config() -> Option { get_auth_config_store() } +// --------------------------------------------------------- +// Automation config +// --------------------------------------------------------- + +pub async fn set_automation_config(config: SetAutomationConfig) -> AutomationConfig { + set_automation_config_store(&config).await.unwrap_or_trap() +} + +pub fn get_automation_config() -> Option { + get_automation_config_store() +} + // --------------------------------------------------------- // Db config // --------------------------------------------------------- diff --git a/src/libs/satellite/src/automation/mod.rs b/src/libs/satellite/src/automation/mod.rs index d2cb1026c2..d03cd7c7cf 100644 --- a/src/libs/satellite/src/automation/mod.rs +++ b/src/libs/satellite/src/automation/mod.rs @@ -1,6 +1,7 @@ pub mod authenticate; mod controllers; mod prepare; +pub mod store; mod strategy_impls; mod token; pub mod types; diff --git a/src/libs/satellite/src/automation/store.rs b/src/libs/satellite/src/automation/store.rs new file mode 100644 index 0000000000..dfe6213531 --- /dev/null +++ b/src/libs/satellite/src/automation/store.rs @@ -0,0 +1,14 @@ +use crate::auth::strategy_impls::AuthHeap; +use junobuild_auth::state::types::automation::AutomationConfig; +use junobuild_auth::state::types::interface::SetAutomationConfig; +use junobuild_auth::state::{ + get_automation as get_state_automation, set_automation_config as set_store_automation_config, +}; + +pub async fn set_config(proposed_config: &SetAutomationConfig) -> Result { + set_store_automation_config(&AuthHeap, proposed_config) +} + +pub fn get_config() -> Option { + get_state_automation(&AuthHeap) +} diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 9b391a5525..6646288f8d 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -31,8 +31,9 @@ use crate::types::interface::{ use crate::types::state::CollectionType; use automation::types::AuthenticateAutomationArgs; use ic_cdk_macros::{init, post_upgrade, pre_upgrade, query, update}; +use junobuild_auth::state::types::automation::AutomationConfig; use junobuild_auth::state::types::config::AuthenticationConfig; -use junobuild_auth::state::types::interface::SetAuthenticationConfig; +use junobuild_auth::state::types::interface::{SetAuthenticationConfig, SetAutomationConfig}; use junobuild_cdn::proposals::{ CommitProposal, ListProposalResults, ListProposalsParams, Proposal, ProposalId, ProposalType, RejectProposal, @@ -379,6 +380,22 @@ pub fn get_auth_config() -> Option { api::config::get_auth_config() } +// --------------------------------------------------------- +// Automation config +// --------------------------------------------------------- + +#[doc(hidden)] +#[update(guard = "caller_is_admin_controller")] +pub async fn set_automation_config(config: SetAutomationConfig) -> AutomationConfig { + api::config::set_automation_config(config).await +} + +#[doc(hidden)] +#[query(guard = "caller_is_admin_controller")] +pub fn get_automation_config() -> Option { + api::config::get_automation_config() +} + // --------------------------------------------------------- // Db config // --------------------------------------------------------- From 51fb242fe5e8787c9c88420a4c577cbc611f063a Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Fri, 6 Feb 2026 14:35:16 +0100 Subject: [PATCH 54/64] feat: generate did with automation config --- src/declarations/satellite/satellite.did.d.ts | 37 ++++++++++++++++++- .../satellite.factory.certified.did.js | 37 ++++++++++++++++++- .../satellite/satellite.factory.did.js | 37 ++++++++++++++++++- .../satellite/satellite.factory.did.mjs | 37 ++++++++++++++++++- src/declarations/sputnik/sputnik.did.d.ts | 37 ++++++++++++++++++- .../sputnik/sputnik.factory.certified.did.js | 37 ++++++++++++++++++- .../sputnik/sputnik.factory.did.js | 37 ++++++++++++++++++- src/libs/satellite/satellite.did | 33 +++++++++++++++++ src/satellite/satellite.did | 33 +++++++++++++++++ src/sputnik/sputnik.did | 33 +++++++++++++++++ .../test_satellite/test_satellite.did.d.ts | 37 ++++++++++++++++++- .../test_satellite.factory.certified.did.js | 37 ++++++++++++++++++- .../test_satellite.factory.did.js | 37 ++++++++++++++++++- .../test_satellite/test_satellite.did | 33 +++++++++++++++++ 14 files changed, 492 insertions(+), 10 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 55516847dd..683a21bc24 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -77,6 +77,17 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export interface AutomationConfig { + updated_at: [] | [bigint]; + openid: [] | [AutomationConfigOpenId]; + created_at: [] | [bigint]; + version: [] | [bigint]; +} +export interface AutomationConfigOpenId { + observatory_id: [] | [Principal]; + providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; +} +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -282,6 +293,18 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } +export type OpenIdAutomationProvider = { GitHub: null }; +export interface OpenIdAutomationProviderConfig { + controller: [] | [OpenIdAutomationProviderControllerConfig]; + repositories: Array<[RepositoryKey, OpenIdAutomationRepositoryConfig]>; +} +export interface OpenIdAutomationProviderControllerConfig { + scope: [] | [AutomationScope]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAutomationRepositoryConfig { + branches: [] | [Array]; +} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -310,7 +333,9 @@ export type PrepareAutomationError = | { InvalidController: string } | { GetCachedJwks: null } | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; + | { GetOrFetchJwks: GetOrRefreshJwksError } + | { ControllerAlreadyExists: null } + | { TooManyControllers: string }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -350,6 +375,10 @@ export interface RateConfig { max_tokens: bigint; time_per_token_ns: bigint; } +export interface RepositoryKey { + owner: string; + name: string; +} export interface Rule { max_capacity: [] | [number]; memory: [] | [Memory]; @@ -374,6 +403,10 @@ export interface SetAuthenticationConfig { internet_identity: [] | [AuthenticationConfigInternetIdentity]; rules: [] | [AuthenticationRules]; } +export interface SetAutomationConfig { + openid: [] | [AutomationConfigOpenId]; + version: [] | [bigint]; +} export interface SetController { metadata: Array<[string, string]>; kind: [] | [ControllerKind]; @@ -496,6 +529,7 @@ export interface _SERVICE { deposit_cycles: ActorMethod<[DepositCyclesArgs], undefined>; get_asset: ActorMethod<[string, string], [] | [AssetNoContent]>; get_auth_config: ActorMethod<[], [] | [AuthenticationConfig]>; + get_automation_config: ActorMethod<[], [] | [AutomationConfig]>; get_config: ActorMethod<[], Config>; get_db_config: ActorMethod<[], [] | [DbConfig]>; get_delegation: ActorMethod<[GetDelegationArgs], GetDelegationResultResponse>; @@ -527,6 +561,7 @@ export interface _SERVICE { reject_proposal: ActorMethod<[CommitProposal], null>; set_asset_token: ActorMethod<[string, string, [] | [string]], undefined>; set_auth_config: ActorMethod<[SetAuthenticationConfig], AuthenticationConfig>; + set_automation_config: ActorMethod<[SetAutomationConfig], AutomationConfig>; set_controllers: ActorMethod<[SetControllersArgs], Array<[Principal, Controller]>>; set_custom_domain: ActorMethod<[string, [] | [string]], undefined>; set_db_config: ActorMethod<[SetDbConfig], DbConfig>; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 0c6ce3f077..69bdc64cec 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -509,6 +542,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], []), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], []), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], []), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], []), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], []), @@ -550,6 +584,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index c757a1fbfc..40f0ca0bfd 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -509,6 +542,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], ['query']), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], ['query']), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], ['query']), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], ['query']), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], ['query']), @@ -550,6 +584,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index c757a1fbfc..40f0ca0bfd 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -509,6 +542,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], ['query']), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], ['query']), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], ['query']), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], ['query']), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], ['query']), @@ -550,6 +584,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index 55516847dd..683a21bc24 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -77,6 +77,17 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export interface AutomationConfig { + updated_at: [] | [bigint]; + openid: [] | [AutomationConfigOpenId]; + created_at: [] | [bigint]; + version: [] | [bigint]; +} +export interface AutomationConfigOpenId { + observatory_id: [] | [Principal]; + providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; +} +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -282,6 +293,18 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } +export type OpenIdAutomationProvider = { GitHub: null }; +export interface OpenIdAutomationProviderConfig { + controller: [] | [OpenIdAutomationProviderControllerConfig]; + repositories: Array<[RepositoryKey, OpenIdAutomationRepositoryConfig]>; +} +export interface OpenIdAutomationProviderControllerConfig { + scope: [] | [AutomationScope]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAutomationRepositoryConfig { + branches: [] | [Array]; +} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -310,7 +333,9 @@ export type PrepareAutomationError = | { InvalidController: string } | { GetCachedJwks: null } | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; + | { GetOrFetchJwks: GetOrRefreshJwksError } + | { ControllerAlreadyExists: null } + | { TooManyControllers: string }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -350,6 +375,10 @@ export interface RateConfig { max_tokens: bigint; time_per_token_ns: bigint; } +export interface RepositoryKey { + owner: string; + name: string; +} export interface Rule { max_capacity: [] | [number]; memory: [] | [Memory]; @@ -374,6 +403,10 @@ export interface SetAuthenticationConfig { internet_identity: [] | [AuthenticationConfigInternetIdentity]; rules: [] | [AuthenticationRules]; } +export interface SetAutomationConfig { + openid: [] | [AutomationConfigOpenId]; + version: [] | [bigint]; +} export interface SetController { metadata: Array<[string, string]>; kind: [] | [ControllerKind]; @@ -496,6 +529,7 @@ export interface _SERVICE { deposit_cycles: ActorMethod<[DepositCyclesArgs], undefined>; get_asset: ActorMethod<[string, string], [] | [AssetNoContent]>; get_auth_config: ActorMethod<[], [] | [AuthenticationConfig]>; + get_automation_config: ActorMethod<[], [] | [AutomationConfig]>; get_config: ActorMethod<[], Config>; get_db_config: ActorMethod<[], [] | [DbConfig]>; get_delegation: ActorMethod<[GetDelegationArgs], GetDelegationResultResponse>; @@ -527,6 +561,7 @@ export interface _SERVICE { reject_proposal: ActorMethod<[CommitProposal], null>; set_asset_token: ActorMethod<[string, string, [] | [string]], undefined>; set_auth_config: ActorMethod<[SetAuthenticationConfig], AuthenticationConfig>; + set_automation_config: ActorMethod<[SetAutomationConfig], AutomationConfig>; set_controllers: ActorMethod<[SetControllersArgs], Array<[Principal, Controller]>>; set_custom_domain: ActorMethod<[string, [] | [string]], undefined>; set_db_config: ActorMethod<[SetDbConfig], DbConfig>; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index 0c6ce3f077..69bdc64cec 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -509,6 +542,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], []), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], []), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], []), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], []), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], []), @@ -550,6 +584,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index c757a1fbfc..40f0ca0bfd 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -509,6 +542,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], ['query']), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], ['query']), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], ['query']), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], ['query']), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], ['query']), @@ -550,6 +584,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 513e374e96..5964930a15 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -60,6 +60,20 @@ type AuthenticationError = variant { RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationConfig = record { + updated_at : opt nat64; + openid : opt AutomationConfigOpenId; + created_at : opt nat64; + version : opt nat64; +}; +type AutomationConfigOpenId = record { + observatory_id : opt principal; + providers : vec record { + OpenIdAutomationProvider; + OpenIdAutomationProviderConfig; + }; +}; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -233,6 +247,16 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; +type OpenIdAutomationProvider = variant { GitHub }; +type OpenIdAutomationProviderConfig = record { + controller : opt OpenIdAutomationProviderControllerConfig; + repositories : vec record { RepositoryKey; OpenIdAutomationRepositoryConfig }; +}; +type OpenIdAutomationProviderControllerConfig = record { + scope : opt AutomationScope; + max_time_to_live : opt nat64; +}; +type OpenIdAutomationRepositoryConfig = record { branches : opt vec text }; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -256,6 +280,8 @@ type PrepareAutomationError = variant { GetCachedJwks; JwtVerify : JwtVerifyError; GetOrFetchJwks : GetOrRefreshJwksError; + ControllerAlreadyExists; + TooManyControllers : text; }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -289,6 +315,7 @@ type ProposalType = variant { SegmentsDeployment : SegmentsDeploymentOptions; }; type RateConfig = record { max_tokens : nat64; time_per_token_ns : nat64 }; +type RepositoryKey = record { owner : text; name : text }; type Rule = record { max_capacity : opt nat32; memory : opt Memory; @@ -313,6 +340,10 @@ type SetAuthenticationConfig = record { internet_identity : opt AuthenticationConfigInternetIdentity; rules : opt AuthenticationRules; }; +type SetAutomationConfig = record { + openid : opt AutomationConfigOpenId; + version : opt nat64; +}; type SetController = record { metadata : vec record { text; text }; kind : opt ControllerKind; @@ -429,6 +460,7 @@ service : (InitSatelliteArgs) -> { deposit_cycles : (DepositCyclesArgs) -> (); get_asset : (text, text) -> (opt AssetNoContent) query; get_auth_config : () -> (opt AuthenticationConfig) query; + get_automation_config : () -> (opt AutomationConfig) query; get_config : () -> (Config); get_db_config : () -> (opt DbConfig) query; get_delegation : (GetDelegationArgs) -> (GetDelegationResultResponse) query; @@ -462,6 +494,7 @@ service : (InitSatelliteArgs) -> { reject_proposal : (CommitProposal) -> (null); set_asset_token : (text, text, opt text) -> (); set_auth_config : (SetAuthenticationConfig) -> (AuthenticationConfig); + set_automation_config : (SetAutomationConfig) -> (AutomationConfig); set_controllers : (SetControllersArgs) -> ( vec record { principal; Controller }, ); diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 79b94fc603..fa7f395754 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -62,6 +62,20 @@ type AuthenticationError = variant { RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationConfig = record { + updated_at : opt nat64; + openid : opt AutomationConfigOpenId; + created_at : opt nat64; + version : opt nat64; +}; +type AutomationConfigOpenId = record { + observatory_id : opt principal; + providers : vec record { + OpenIdAutomationProvider; + OpenIdAutomationProviderConfig; + }; +}; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -235,6 +249,16 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; +type OpenIdAutomationProvider = variant { GitHub }; +type OpenIdAutomationProviderConfig = record { + controller : opt OpenIdAutomationProviderControllerConfig; + repositories : vec record { RepositoryKey; OpenIdAutomationRepositoryConfig }; +}; +type OpenIdAutomationProviderControllerConfig = record { + scope : opt AutomationScope; + max_time_to_live : opt nat64; +}; +type OpenIdAutomationRepositoryConfig = record { branches : opt vec text }; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -258,6 +282,8 @@ type PrepareAutomationError = variant { GetCachedJwks; JwtVerify : JwtVerifyError; GetOrFetchJwks : GetOrRefreshJwksError; + ControllerAlreadyExists; + TooManyControllers : text; }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -291,6 +317,7 @@ type ProposalType = variant { SegmentsDeployment : SegmentsDeploymentOptions; }; type RateConfig = record { max_tokens : nat64; time_per_token_ns : nat64 }; +type RepositoryKey = record { owner : text; name : text }; type Rule = record { max_capacity : opt nat32; memory : opt Memory; @@ -315,6 +342,10 @@ type SetAuthenticationConfig = record { internet_identity : opt AuthenticationConfigInternetIdentity; rules : opt AuthenticationRules; }; +type SetAutomationConfig = record { + openid : opt AutomationConfigOpenId; + version : opt nat64; +}; type SetController = record { metadata : vec record { text; text }; kind : opt ControllerKind; @@ -431,6 +462,7 @@ service : (InitSatelliteArgs) -> { deposit_cycles : (DepositCyclesArgs) -> (); get_asset : (text, text) -> (opt AssetNoContent) query; get_auth_config : () -> (opt AuthenticationConfig) query; + get_automation_config : () -> (opt AutomationConfig) query; get_config : () -> (Config); get_db_config : () -> (opt DbConfig) query; get_delegation : (GetDelegationArgs) -> (GetDelegationResultResponse) query; @@ -464,6 +496,7 @@ service : (InitSatelliteArgs) -> { reject_proposal : (CommitProposal) -> (null); set_asset_token : (text, text, opt text) -> (); set_auth_config : (SetAuthenticationConfig) -> (AuthenticationConfig); + set_automation_config : (SetAutomationConfig) -> (AutomationConfig); set_controllers : (SetControllersArgs) -> ( vec record { principal; Controller }, ); diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 32d4dad4a4..722754a887 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -62,6 +62,20 @@ type AuthenticationError = variant { RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationConfig = record { + updated_at : opt nat64; + openid : opt AutomationConfigOpenId; + created_at : opt nat64; + version : opt nat64; +}; +type AutomationConfigOpenId = record { + observatory_id : opt principal; + providers : vec record { + OpenIdAutomationProvider; + OpenIdAutomationProviderConfig; + }; +}; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -235,6 +249,16 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; +type OpenIdAutomationProvider = variant { GitHub }; +type OpenIdAutomationProviderConfig = record { + controller : opt OpenIdAutomationProviderControllerConfig; + repositories : vec record { RepositoryKey; OpenIdAutomationRepositoryConfig }; +}; +type OpenIdAutomationProviderControllerConfig = record { + scope : opt AutomationScope; + max_time_to_live : opt nat64; +}; +type OpenIdAutomationRepositoryConfig = record { branches : opt vec text }; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -258,6 +282,8 @@ type PrepareAutomationError = variant { GetCachedJwks; JwtVerify : JwtVerifyError; GetOrFetchJwks : GetOrRefreshJwksError; + ControllerAlreadyExists; + TooManyControllers : text; }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -291,6 +317,7 @@ type ProposalType = variant { SegmentsDeployment : SegmentsDeploymentOptions; }; type RateConfig = record { max_tokens : nat64; time_per_token_ns : nat64 }; +type RepositoryKey = record { owner : text; name : text }; type Rule = record { max_capacity : opt nat32; memory : opt Memory; @@ -315,6 +342,10 @@ type SetAuthenticationConfig = record { internet_identity : opt AuthenticationConfigInternetIdentity; rules : opt AuthenticationRules; }; +type SetAutomationConfig = record { + openid : opt AutomationConfigOpenId; + version : opt nat64; +}; type SetController = record { metadata : vec record { text; text }; kind : opt ControllerKind; @@ -431,6 +462,7 @@ service : (InitSatelliteArgs) -> { deposit_cycles : (DepositCyclesArgs) -> (); get_asset : (text, text) -> (opt AssetNoContent) query; get_auth_config : () -> (opt AuthenticationConfig) query; + get_automation_config : () -> (opt AutomationConfig) query; get_config : () -> (Config); get_db_config : () -> (opt DbConfig) query; get_delegation : (GetDelegationArgs) -> (GetDelegationResultResponse) query; @@ -464,6 +496,7 @@ service : (InitSatelliteArgs) -> { reject_proposal : (CommitProposal) -> (null); set_asset_token : (text, text, opt text) -> (); set_auth_config : (SetAuthenticationConfig) -> (AuthenticationConfig); + set_automation_config : (SetAutomationConfig) -> (AutomationConfig); set_controllers : (SetControllersArgs) -> ( vec record { principal; Controller }, ); diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 7555fba72f..83fa6241e7 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -77,6 +77,17 @@ export type AuthenticationError = export interface AuthenticationRules { allowed_callers: Array; } +export interface AutomationConfig { + updated_at: [] | [bigint]; + openid: [] | [AutomationConfigOpenId]; + created_at: [] | [bigint]; + version: [] | [bigint]; +} +export interface AutomationConfigOpenId { + observatory_id: [] | [Principal]; + providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; +} +export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { batch_id: bigint; @@ -282,6 +293,18 @@ export interface OpenIdAuthProviderDelegationConfig { targets: [] | [Array]; max_time_to_live: [] | [bigint]; } +export type OpenIdAutomationProvider = { GitHub: null }; +export interface OpenIdAutomationProviderConfig { + controller: [] | [OpenIdAutomationProviderControllerConfig]; + repositories: Array<[RepositoryKey, OpenIdAutomationRepositoryConfig]>; +} +export interface OpenIdAutomationProviderControllerConfig { + scope: [] | [AutomationScope]; + max_time_to_live: [] | [bigint]; +} +export interface OpenIdAutomationRepositoryConfig { + branches: [] | [Array]; +} export type OpenIdDelegationProvider = { GitHub: null } | { Google: null }; export interface OpenIdGetDelegationArgs { jwt: string; @@ -310,7 +333,9 @@ export type PrepareAutomationError = | { InvalidController: string } | { GetCachedJwks: null } | { JwtVerify: JwtVerifyError } - | { GetOrFetchJwks: GetOrRefreshJwksError }; + | { GetOrFetchJwks: GetOrRefreshJwksError } + | { ControllerAlreadyExists: null } + | { TooManyControllers: string }; export type PrepareDelegationError = | { JwtFindProvider: JwtFindProviderError; @@ -350,6 +375,10 @@ export interface RateConfig { max_tokens: bigint; time_per_token_ns: bigint; } +export interface RepositoryKey { + owner: string; + name: string; +} export type Result = { Ok: number } | { Err: string }; export interface Rule { max_capacity: [] | [number]; @@ -375,6 +404,10 @@ export interface SetAuthenticationConfig { internet_identity: [] | [AuthenticationConfigInternetIdentity]; rules: [] | [AuthenticationRules]; } +export interface SetAutomationConfig { + openid: [] | [AutomationConfigOpenId]; + version: [] | [bigint]; +} export interface SetController { metadata: Array<[string, string]>; kind: [] | [ControllerKind]; @@ -497,6 +530,7 @@ export interface _SERVICE { deposit_cycles: ActorMethod<[DepositCyclesArgs], undefined>; get_asset: ActorMethod<[string, string], [] | [AssetNoContent]>; get_auth_config: ActorMethod<[], [] | [AuthenticationConfig]>; + get_automation_config: ActorMethod<[], [] | [AutomationConfig]>; get_config: ActorMethod<[], Config>; get_db_config: ActorMethod<[], [] | [DbConfig]>; get_delegation: ActorMethod<[GetDelegationArgs], GetDelegationResultResponse>; @@ -528,6 +562,7 @@ export interface _SERVICE { reject_proposal: ActorMethod<[CommitProposal], null>; set_asset_token: ActorMethod<[string, string, [] | [string]], undefined>; set_auth_config: ActorMethod<[SetAuthenticationConfig], AuthenticationConfig>; + set_automation_config: ActorMethod<[SetAutomationConfig], AutomationConfig>; set_controllers: ActorMethod<[SetControllersArgs], Array<[Principal, Controller]>>; set_custom_domain: ActorMethod<[string, [] | [string]], undefined>; set_db_config: ActorMethod<[SetDbConfig], DbConfig>; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index 76d1444ca8..48fe336e4b 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -510,6 +543,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], []), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], []), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], []), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], []), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], []), @@ -551,6 +585,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index 2a4b4a4dbe..9abc2d5ef0 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -87,7 +87,9 @@ export const idlFactory = ({ IDL }) => { InvalidController: IDL.Text, GetCachedJwks: IDL.Null, JwtVerify: JwtVerifyError, - GetOrFetchJwks: GetOrRefreshJwksError + GetOrFetchJwks: GetOrRefreshJwksError, + ControllerAlreadyExists: IDL.Null, + TooManyControllers: IDL.Text }); const AuthenticationAutomationError = IDL.Variant({ PrepareAutomation: PrepareAutomationError, @@ -218,6 +220,33 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const OpenIdAutomationProviderControllerConfig = IDL.Record({ + scope: IDL.Opt(AutomationScope), + max_time_to_live: IDL.Opt(IDL.Nat64) + }); + const RepositoryKey = IDL.Record({ owner: IDL.Text, name: IDL.Text }); + const OpenIdAutomationRepositoryConfig = IDL.Record({ + branches: IDL.Opt(IDL.Vec(IDL.Text)) + }); + const OpenIdAutomationProviderConfig = IDL.Record({ + controller: IDL.Opt(OpenIdAutomationProviderControllerConfig), + repositories: IDL.Vec(IDL.Tuple(RepositoryKey, OpenIdAutomationRepositoryConfig)) + }); + const AutomationConfigOpenId = IDL.Record({ + observatory_id: IDL.Opt(IDL.Principal), + providers: IDL.Vec(IDL.Tuple(OpenIdAutomationProvider, OpenIdAutomationProviderConfig)) + }); + const AutomationConfig = IDL.Record({ + updated_at: IDL.Opt(IDL.Nat64), + openid: IDL.Opt(AutomationConfigOpenId), + created_at: IDL.Opt(IDL.Nat64), + version: IDL.Opt(IDL.Nat64) + }); const ConfigMaxMemorySize = IDL.Record({ stable: IDL.Opt(IDL.Nat64), heap: IDL.Opt(IDL.Nat64) @@ -428,6 +457,10 @@ export const idlFactory = ({ IDL }) => { internet_identity: IDL.Opt(AuthenticationConfigInternetIdentity), rules: IDL.Opt(AuthenticationRules) }); + const SetAutomationConfig = IDL.Record({ + openid: IDL.Opt(AutomationConfigOpenId), + version: IDL.Opt(IDL.Nat64) + }); const SetController = IDL.Record({ metadata: IDL.Vec(IDL.Tuple(IDL.Text, IDL.Text)), kind: IDL.Opt(ControllerKind), @@ -510,6 +543,7 @@ export const idlFactory = ({ IDL }) => { deposit_cycles: IDL.Func([DepositCyclesArgs], [], []), get_asset: IDL.Func([IDL.Text, IDL.Text], [IDL.Opt(AssetNoContent)], ['query']), get_auth_config: IDL.Func([], [IDL.Opt(AuthenticationConfig)], ['query']), + get_automation_config: IDL.Func([], [IDL.Opt(AutomationConfig)], ['query']), get_config: IDL.Func([], [Config], []), get_db_config: IDL.Func([], [IDL.Opt(DbConfig)], ['query']), get_delegation: IDL.Func([GetDelegationArgs], [GetDelegationResultResponse], ['query']), @@ -551,6 +585,7 @@ export const idlFactory = ({ IDL }) => { reject_proposal: IDL.Func([CommitProposal], [IDL.Null], []), set_asset_token: IDL.Func([IDL.Text, IDL.Text, IDL.Opt(IDL.Text)], [], []), set_auth_config: IDL.Func([SetAuthenticationConfig], [AuthenticationConfig], []), + set_automation_config: IDL.Func([SetAutomationConfig], [AutomationConfig], []), set_controllers: IDL.Func( [SetControllersArgs], [IDL.Vec(IDL.Tuple(IDL.Principal, Controller))], diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index fb6db0b21e..7939b37b43 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -62,6 +62,20 @@ type AuthenticationError = variant { RegisterUser : text; }; type AuthenticationRules = record { allowed_callers : vec principal }; +type AutomationConfig = record { + updated_at : opt nat64; + openid : opt AutomationConfigOpenId; + created_at : opt nat64; + version : opt nat64; +}; +type AutomationConfigOpenId = record { + observatory_id : opt principal; + providers : vec record { + OpenIdAutomationProvider; + OpenIdAutomationProviderConfig; + }; +}; +type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { batch_id : nat; @@ -235,6 +249,16 @@ type OpenIdAuthProviderDelegationConfig = record { targets : opt vec principal; max_time_to_live : opt nat64; }; +type OpenIdAutomationProvider = variant { GitHub }; +type OpenIdAutomationProviderConfig = record { + controller : opt OpenIdAutomationProviderControllerConfig; + repositories : vec record { RepositoryKey; OpenIdAutomationRepositoryConfig }; +}; +type OpenIdAutomationProviderControllerConfig = record { + scope : opt AutomationScope; + max_time_to_live : opt nat64; +}; +type OpenIdAutomationRepositoryConfig = record { branches : opt vec text }; type OpenIdDelegationProvider = variant { GitHub; Google }; type OpenIdGetDelegationArgs = record { jwt : text; @@ -258,6 +282,8 @@ type PrepareAutomationError = variant { GetCachedJwks; JwtVerify : JwtVerifyError; GetOrFetchJwks : GetOrRefreshJwksError; + ControllerAlreadyExists; + TooManyControllers : text; }; type PrepareDelegationError = variant { JwtFindProvider : JwtFindProviderError; @@ -291,6 +317,7 @@ type ProposalType = variant { SegmentsDeployment : SegmentsDeploymentOptions; }; type RateConfig = record { max_tokens : nat64; time_per_token_ns : nat64 }; +type RepositoryKey = record { owner : text; name : text }; type Rule = record { max_capacity : opt nat32; memory : opt Memory; @@ -315,6 +342,10 @@ type SetAuthenticationConfig = record { internet_identity : opt AuthenticationConfigInternetIdentity; rules : opt AuthenticationRules; }; +type SetAutomationConfig = record { + openid : opt AutomationConfigOpenId; + version : opt nat64; +}; type SetController = record { metadata : vec record { text; text }; kind : opt ControllerKind; @@ -431,6 +462,7 @@ service : (InitSatelliteArgs) -> { deposit_cycles : (DepositCyclesArgs) -> (); get_asset : (text, text) -> (opt AssetNoContent) query; get_auth_config : () -> (opt AuthenticationConfig) query; + get_automation_config : () -> (opt AutomationConfig) query; get_config : () -> (Config); get_db_config : () -> (opt DbConfig) query; get_delegation : (GetDelegationArgs) -> (GetDelegationResultResponse) query; @@ -464,6 +496,7 @@ service : (InitSatelliteArgs) -> { reject_proposal : (CommitProposal) -> (null); set_asset_token : (text, text, opt text) -> (); set_auth_config : (SetAuthenticationConfig) -> (AuthenticationConfig); + set_automation_config : (SetAutomationConfig) -> (AutomationConfig); set_controllers : (SetControllersArgs) -> ( vec record { principal; Controller }, ); From 7ceed2d9f94f29c1bd1aee699f37a4c9ce19b636 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 7 Feb 2026 09:15:32 +0100 Subject: [PATCH 55/64] feat: move repo to custom --- .../src/openid/credentials/automation/verify.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 165df19d63..f8ae0f7612 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -47,7 +47,13 @@ fn verify_openid_credentials( provider: &OpenIdAutomationProvider, config: &OpenIdAutomationProviderConfig, ) -> VerifyOpenIdAutomationCredentialsResult { - let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + let assert_audience = |_claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + // TODO: assert caller is audience + + Ok(()) + }; + + let assert_repository = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { let repository = claims .repository .as_ref() @@ -87,18 +93,12 @@ fn verify_openid_credentials( Ok(()) }; - let assert_custom = |_claims: &AutomationClaims| -> Result<(), JwtVerifyError> { - // No custom assertion for automation. Replay attack protection must notably be implemented by consumer. - - Ok(()) - }; - let token = verify_openid_jwt( jwt, provider.issuers(), &jwks.keys, assert_audience, - assert_custom, + assert_repository, ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; From ecd5954d9ea3ddc79fe98941320859ae3bb377a8 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 7 Feb 2026 09:22:08 +0100 Subject: [PATCH 56/64] feat: assert audience --- .../openid/credentials/automation/verify.rs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index f8ae0f7612..8384ddbf0a 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -10,6 +10,7 @@ use crate::state::types::automation::{ OpenIdAutomationProviderConfig, OpenIdAutomationProviders, RepositoryKey, }; use crate::strategies::AuthHeapStrategy; +use junobuild_shared::ic::api::caller; type VerifyOpenIdAutomationCredentialsResult = Result<(OpenIdAutomationCredential, OpenIdAutomationProvider), VerifyOpenidCredentialsError>; @@ -47,8 +48,13 @@ fn verify_openid_credentials( provider: &OpenIdAutomationProvider, config: &OpenIdAutomationProviderConfig, ) -> VerifyOpenIdAutomationCredentialsResult { - let assert_audience = |_claims: &AutomationClaims| -> Result<(), JwtVerifyError> { - // TODO: assert caller is audience + let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + // Ensure the JWT has not been intercepted and submitted with a different identity. + // We verify the audience matches the caller's principal (GitHub does not allow customizing + // other JWT fields, making audience our only option for binding the JWT to a specific principal). + if claims.aud != caller().to_text() { + return Err(JwtVerifyError::BadClaim("aud".to_string())); + } Ok(()) }; @@ -116,6 +122,7 @@ mod tests { OpenIdAutomationProviderConfig, OpenIdAutomationRepositories, OpenIdAutomationRepositoryConfig, RepositoryKey, }; + use ic_cdk::call; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; @@ -182,7 +189,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), - aud: "https://github.com/octo-org".into(), + aud: caller().to_text(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -209,6 +216,40 @@ mod tests { assert_eq!(credential.r#ref.as_deref(), Some("refs/heads/main")); } + #[test] + fn rejects_mismatched_audience() { + let now = now_secs(); + + let claims = AutomationClaims { + iss: ISS_GITHUB_ACTIONS.into(), + sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), + aud: "another-principal-id".into(), + iat: Some(now), + exp: Some(now + 600), + nbf: None, + jti: Some("example-id".into()), + repository: Some("octo-org/octo-repo".into()), + repository_owner: Some("octo-org".into()), + r#ref: Some("refs/heads/main".into()), + run_id: Some("123456".into()), + run_number: Some("1".into()), + run_attempt: Some("1".into()), + }; + + let jwt = create_token(&claims); + let jwks = test_jwks(); + let config = test_config(); + + let result = + verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + + assert!(result.is_err()); + assert!(matches!( + result.unwrap_err(), + VerifyOpenidCredentialsError::JwtVerify(JwtVerifyError::BadClaim(ref c)) if c == "aud" + )); + } + #[test] fn rejects_unauthorized_repository() { let now = now_secs(); @@ -216,7 +257,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:other-org/other-repo:ref:refs/heads/main".into(), - aud: "https://github.com/other-org".into(), + aud: caller().to_text(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -250,7 +291,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/feature".into(), - aud: "https://github.com/octo-org".into(), + aud: caller().to_text(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -298,7 +339,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/any-branch".into(), - aud: "https://github.com/octo-org".into(), + aud: caller().to_text(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -327,7 +368,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), - aud: "https://github.com/octo-org".into(), + aud: caller().to_text(), iat: Some(now), exp: Some(now + 600), nbf: None, From 63c85431190f1d003a5d38e1fc96d7ce4c4dc800 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Sat, 7 Feb 2026 09:30:09 +0100 Subject: [PATCH 57/64] feat: caller is the controller id --- src/libs/auth/src/automation/prepare.rs | 10 ++++++---- src/libs/auth/src/automation/types.rs | 1 - src/libs/satellite/src/automation/prepare.rs | 7 +------ 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs index d9b72a6c3c..c401e049da 100644 --- a/src/libs/auth/src/automation/prepare.rs +++ b/src/libs/auth/src/automation/prepare.rs @@ -6,24 +6,26 @@ use crate::automation::utils::duration::build_expiration; use crate::automation::utils::scope::build_scope; use crate::openid::types::provider::OpenIdAutomationProvider; use crate::strategies::{AuthAutomationStrategy, AuthHeapStrategy}; +use junobuild_shared::ic::api::caller; use junobuild_shared::segments::controllers::{ assert_controllers, assert_max_number_of_controllers, }; use junobuild_shared::types::state::ControllerId; pub fn openid_prepare_automation( - controller_id: &ControllerId, provider: &OpenIdAutomationProvider, auth_heap: &impl AuthHeapStrategy, auth_automation: &impl AuthAutomationStrategy, ) -> PrepareAutomationResult { + let controller_id = caller(); + let existing_controllers = auth_automation.get_controllers(); - if existing_controllers.contains_key(controller_id) { + if existing_controllers.contains_key(&controller_id) { return Err(PrepareAutomationError::ControllerAlreadyExists); } - let submitted_controllers: [ControllerId; 1] = [*controller_id]; + let submitted_controllers: [ControllerId; 1] = [controller_id]; assert_controllers(&submitted_controllers) .map_err(PrepareAutomationError::InvalidController)?; @@ -41,7 +43,7 @@ pub fn openid_prepare_automation( let expires_at = build_expiration(provider, auth_heap); let controller: PreparedControllerAutomation = PreparedControllerAutomation { - id: *controller_id, + id: controller_id, expires_at, scope, }; diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index 071816b97a..32ed86742f 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -7,7 +7,6 @@ use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] pub struct OpenIdPrepareAutomationArgs { pub jwt: String, - pub controller_id: ControllerId, } pub type PrepareAutomationResult = Result; diff --git a/src/libs/satellite/src/automation/prepare.rs b/src/libs/satellite/src/automation/prepare.rs index ce4ce3ba36..9a92032e66 100644 --- a/src/libs/satellite/src/automation/prepare.rs +++ b/src/libs/satellite/src/automation/prepare.rs @@ -32,12 +32,7 @@ pub async fn openid_prepare_automation( Err(err) => return Err(PrepareAutomationError::from(err)), }; - let result = automation::openid_prepare_automation( - &args.controller_id, - &provider, - &AuthHeap, - &AuthAutomation, - ); + let result = automation::openid_prepare_automation(&provider, &AuthHeap, &AuthAutomation); result.map(|prepared_automation| (prepared_automation, provider, credential)) } From 5be3f9b5dedb3d247eb9db7ea6cb2f8128541832 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Mon, 9 Feb 2026 18:48:14 +0100 Subject: [PATCH 58/64] feat: use salt --- src/libs/auth/src/automation/types.rs | 2 + .../openid/credentials/automation/verify.rs | 99 ++++++++--- .../src/openid/credentials/delegation/mod.rs | 1 - .../openid/credentials/delegation/verify.rs | 19 +- src/libs/auth/src/openid/jwt/verify.rs | 168 ++++++++++++------ src/libs/auth/src/openid/mod.rs | 1 + .../{credentials/delegation => }/utils/mod.rs | 0 .../delegation => }/utils/nonce.rs | 0 src/libs/satellite/src/automation/prepare.rs | 2 +- 9 files changed, 204 insertions(+), 88 deletions(-) rename src/libs/auth/src/openid/{credentials/delegation => }/utils/mod.rs (100%) rename src/libs/auth/src/openid/{credentials/delegation => }/utils/nonce.rs (100%) diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index 32ed86742f..de0f0d8dee 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -1,5 +1,6 @@ use crate::openid::jwkset::types::errors::GetOrRefreshJwksError; use crate::openid::jwt::types::errors::{JwtFindProviderError, JwtVerifyError}; +use crate::state::types::state::Salt; use candid::{CandidType, Deserialize}; use junobuild_shared::types::state::ControllerId; use serde::Serialize; @@ -7,6 +8,7 @@ use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] pub struct OpenIdPrepareAutomationArgs { pub jwt: String, + pub salt: Salt, } pub type PrepareAutomationResult = Result; diff --git a/src/libs/auth/src/openid/credentials/automation/verify.rs b/src/libs/auth/src/openid/credentials/automation/verify.rs index 8384ddbf0a..1f9963968d 100644 --- a/src/libs/auth/src/openid/credentials/automation/verify.rs +++ b/src/libs/auth/src/openid/credentials/automation/verify.rs @@ -9,8 +9,8 @@ use crate::openid::types::provider::{OpenIdAutomationProvider, OpenIdProvider}; use crate::state::types::automation::{ OpenIdAutomationProviderConfig, OpenIdAutomationProviders, RepositoryKey, }; +use crate::state::types::state::Salt; use crate::strategies::AuthHeapStrategy; -use junobuild_shared::ic::api::caller; type VerifyOpenIdAutomationCredentialsResult = Result<(OpenIdAutomationCredential, OpenIdAutomationProvider), VerifyOpenidCredentialsError>; @@ -27,6 +27,7 @@ type VerifyOpenIdAutomationCredentialsResult = /// In the Satellite implementation, this is handled by `save_unique_token_jti()`. pub async fn verify_openid_credentials_with_jwks_renewal( jwt: &str, + salt: &Salt, providers: &OpenIdAutomationProviders, auth_heap: &impl AuthHeapStrategy, ) -> VerifyOpenIdAutomationCredentialsResult { @@ -39,7 +40,7 @@ pub async fn verify_openid_credentials_with_jwks_renewal( .await .map_err(VerifyOpenidCredentialsError::GetOrFetchJwks)?; - verify_openid_credentials(jwt, &jwks, &automation_provider, config) + verify_openid_credentials(jwt, &jwks, &automation_provider, config, salt) } fn verify_openid_credentials( @@ -47,12 +48,13 @@ fn verify_openid_credentials( jwks: &Jwks, provider: &OpenIdAutomationProvider, config: &OpenIdAutomationProviderConfig, + salt: &Salt, ) -> VerifyOpenIdAutomationCredentialsResult { - let assert_audience = |claims: &AutomationClaims| -> Result<(), JwtVerifyError> { + let assert_nonce = |claims: &AutomationClaims, nonce: &String| -> Result<(), JwtVerifyError> { // Ensure the JWT has not been intercepted and submitted with a different identity. - // We verify the audience matches the caller's principal (GitHub does not allow customizing + // We verify the audience matches the caller's principal + salt (GitHub does not allow customizing // other JWT fields, making audience our only option for binding the JWT to a specific principal). - if claims.aud != caller().to_text() { + if claims.aud != nonce.as_str() { return Err(JwtVerifyError::BadClaim("aud".to_string())); } @@ -103,7 +105,8 @@ fn verify_openid_credentials( jwt, provider.issuers(), &jwks.keys, - assert_audience, + &salt, + assert_nonce, assert_repository, ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; @@ -118,11 +121,12 @@ mod tests { use super::*; use crate::openid::jwt::types::cert::{Jwk, JwkParams, JwkParamsRsa, JwkType, Jwks}; use crate::openid::types::provider::OpenIdAutomationProvider; + use crate::openid::utils::nonce::build_nonce; use crate::state::types::automation::{ OpenIdAutomationProviderConfig, OpenIdAutomationRepositories, OpenIdAutomationRepositoryConfig, RepositoryKey, }; - use ic_cdk::call; + use crate::state::types::state::Salt; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; @@ -140,6 +144,10 @@ mod tests { .as_secs() } + fn test_salt() -> Salt { + [42u8; 32] + } + fn test_jwks() -> Jwks { Jwks { keys: vec![Jwk { @@ -185,11 +193,13 @@ mod tests { #[test] fn verifies_valid_automation_credentials() { let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), - aud: caller().to_text(), + aud: nonce.clone(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -206,8 +216,13 @@ mod tests { let jwks = test_jwks(); let config = test_config(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_ok()); let (credential, provider) = result.unwrap(); @@ -219,11 +234,12 @@ mod tests { #[test] fn rejects_mismatched_audience() { let now = now_secs(); + let salt = test_salt(); let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), - aud: "another-principal-id".into(), + aud: "wrong-nonce".into(), iat: Some(now), exp: Some(now + 600), nbf: None, @@ -240,8 +256,13 @@ mod tests { let jwks = test_jwks(); let config = test_config(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_err()); assert!(matches!( @@ -253,11 +274,13 @@ mod tests { #[test] fn rejects_unauthorized_repository() { let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:other-org/other-repo:ref:refs/heads/main".into(), - aud: caller().to_text(), + aud: nonce, iat: Some(now), exp: Some(now + 600), nbf: None, @@ -274,8 +297,13 @@ mod tests { let jwks = test_jwks(); let config = test_config(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_err()); assert!(matches!( @@ -287,11 +315,13 @@ mod tests { #[test] fn rejects_unauthorized_branch() { let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/feature".into(), - aud: caller().to_text(), + aud: nonce, iat: Some(now), exp: Some(now + 600), nbf: None, @@ -308,8 +338,13 @@ mod tests { let jwks = test_jwks(); let config = test_config(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_err()); assert!(matches!( @@ -321,6 +356,8 @@ mod tests { #[test] fn allows_all_branches_when_not_configured() { let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); let mut repositories: OpenIdAutomationRepositories = HashMap::new(); repositories.insert( @@ -339,7 +376,7 @@ mod tests { let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/any-branch".into(), - aud: caller().to_text(), + aud: nonce, iat: Some(now), exp: Some(now + 600), nbf: None, @@ -355,8 +392,13 @@ mod tests { let jwt = create_token(&claims); let jwks = test_jwks(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_ok()); } @@ -364,11 +406,13 @@ mod tests { #[test] fn rejects_missing_repository_claim() { let now = now_secs(); + let salt = test_salt(); + let nonce = build_nonce(&salt); let claims = AutomationClaims { iss: ISS_GITHUB_ACTIONS.into(), sub: "repo:octo-org/octo-repo:ref:refs/heads/main".into(), - aud: caller().to_text(), + aud: nonce, iat: Some(now), exp: Some(now + 600), nbf: None, @@ -385,8 +429,13 @@ mod tests { let jwks = test_jwks(); let config = test_config(); - let result = - verify_openid_credentials(&jwt, &jwks, &OpenIdAutomationProvider::GitHub, &config); + let result = verify_openid_credentials( + &jwt, + &jwks, + &OpenIdAutomationProvider::GitHub, + &config, + &salt, + ); assert!(result.is_err()); assert!(matches!( diff --git a/src/libs/auth/src/openid/credentials/delegation/mod.rs b/src/libs/auth/src/openid/credentials/delegation/mod.rs index 9d1799239b..35c6668fcf 100644 --- a/src/libs/auth/src/openid/credentials/delegation/mod.rs +++ b/src/libs/auth/src/openid/credentials/delegation/mod.rs @@ -1,6 +1,5 @@ mod impls; pub mod types; -mod utils; mod verify; pub use verify::*; diff --git a/src/libs/auth/src/openid/credentials/delegation/verify.rs b/src/libs/auth/src/openid/credentials/delegation/verify.rs index 2a99bebc1f..1b41d83c0d 100644 --- a/src/libs/auth/src/openid/credentials/delegation/verify.rs +++ b/src/libs/auth/src/openid/credentials/delegation/verify.rs @@ -1,6 +1,5 @@ use crate::openid::credentials::delegation::types::interface::OpenIdDelegationCredential; use crate::openid::credentials::delegation::types::token::DelegationClaims; -use crate::openid::credentials::delegation::utils::nonce::build_nonce; use crate::openid::credentials::types::errors::VerifyOpenidCredentialsError; use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; use crate::openid::jwt::types::cert::Jwks; @@ -59,19 +58,17 @@ fn verify_openid_credentials( client_id: &OpenIdAuthProviderClientId, salt: &Salt, ) -> VerifyOpenIdDelegationCredentialsResult { - let assert_audience = |claims: &DelegationClaims| -> Result<(), JwtVerifyError> { - if claims.aud != client_id.as_str() { - return Err(JwtVerifyError::BadClaim("aud".to_string())); + let assert_nonce = |claims: &DelegationClaims, nonce: &String| -> Result<(), JwtVerifyError> { + if claims.nonce.as_deref() != Some(nonce.as_str()) { + return Err(JwtVerifyError::BadClaim("nonce".to_string())); } Ok(()) }; - let assert_no_replay = |claims: &DelegationClaims| -> Result<(), JwtVerifyError> { - let nonce = build_nonce(salt); - - if claims.nonce.as_deref() != Some(nonce.as_str()) { - return Err(JwtVerifyError::BadClaim("nonce".to_string())); + let assert_audience = |claims: &DelegationClaims| -> Result<(), JwtVerifyError> { + if claims.aud != client_id.as_str() { + return Err(JwtVerifyError::BadClaim("aud".to_string())); } Ok(()) @@ -81,8 +78,9 @@ fn verify_openid_credentials( jwt, provider.issuers(), &jwks.keys, + salt, + assert_nonce, assert_audience, - assert_no_replay, ) .map_err(VerifyOpenidCredentialsError::JwtVerify)?; @@ -96,6 +94,7 @@ mod tests { use super::*; use crate::openid::jwt::types::cert::{Jwk, JwkParams, JwkParamsRsa, JwkType, Jwks}; use crate::openid::types::provider::OpenIdDelegationProvider; + use crate::openid::utils::nonce::build_nonce; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/src/libs/auth/src/openid/jwt/verify.rs b/src/libs/auth/src/openid/jwt/verify.rs index b402c27fac..871949420c 100644 --- a/src/libs/auth/src/openid/jwt/verify.rs +++ b/src/libs/auth/src/openid/jwt/verify.rs @@ -2,6 +2,8 @@ use crate::openid::jwt::header::decode_jwt_header; use crate::openid::jwt::types::cert::{JwkParams, JwkType}; use crate::openid::jwt::types::token::JwtClaims; use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError}; +use crate::openid::utils::nonce::build_nonce; +use crate::state::types::state::Salt; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use serde::de::DeserializeOwned; @@ -9,16 +11,17 @@ fn pick_key<'a>(kid: &str, jwks: &'a [Jwk]) -> Option<&'a Jwk> { jwks.iter().find(|j| j.kid.as_deref() == Some(kid)) } -pub fn verify_openid_jwt( +pub fn verify_openid_jwt( jwt: &str, issuers: &[&str], jwks: &[Jwk], - assert_audience: Aud, + salt: &Salt, + assert_nonce: Nonce, assert_custom: Custom, ) -> Result, JwtVerifyError> where Claims: DeserializeOwned + JwtClaims, - Aud: FnOnce(&Claims) -> Result<(), JwtVerifyError>, + Nonce: FnOnce(&Claims, &String) -> Result<(), JwtVerifyError>, Custom: FnOnce(&Claims) -> Result<(), JwtVerifyError>, { // 1) Read header to get `kid` @@ -64,10 +67,11 @@ where let c = &token.claims; - // 6) Manual checks audience - assert_audience(c)?; + // 6) Checks the nonce - i.e. the caller + salt is present in the jwt + let nonce = build_nonce(salt); + assert_nonce(c, &nonce)?; - // 7) Assert custom fields such as the nonce for delegation to prevent replay attack + // 7) Assert custom fields according consumer's flow assert_custom(c)?; // 8) Assert expiration @@ -114,6 +118,8 @@ mod verify_tests { use crate::openid::jwt::types::cert::{JwkParams, JwkParamsRsa, JwkType}; use crate::openid::jwt::types::token::JwtClaims; use crate::openid::jwt::types::{cert::Jwk, errors::JwtVerifyError}; + use crate::openid::utils::nonce::build_nonce; + use crate::state::types::state::Salt; use candid::Deserialize; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use serde::Serialize; @@ -126,7 +132,6 @@ mod verify_tests { const ISS_GOOGLE: &str = "https://accounts.google.com"; const AUD_OK: &str = "client-123"; - const NONCE_OK: &str = "nonce-xyz"; const KID_OK: &str = "test-kid-1"; fn now_secs() -> u64 { @@ -136,6 +141,10 @@ mod verify_tests { .as_secs() } + fn test_salt() -> Salt { + [42u8; 32] + } + fn header(typ: Option<&str>, kid: Option<&str>) -> Header { let mut h = Header::new(Algorithm::RS256); h.typ = typ.map(|t| t.to_string()); @@ -212,16 +221,16 @@ mod verify_tests { } } - fn assert_audience(claims: &GoogleClaims) -> Result<(), JwtVerifyError> { - if claims.aud != AUD_OK { - return Err(JwtVerifyError::BadClaim("aud".to_string())); + fn assert_nonce(claims: &GoogleClaims, nonce: &String) -> Result<(), JwtVerifyError> { + if claims.nonce.as_deref() != Some(nonce.as_str()) { + return Err(JwtVerifyError::BadClaim("nonce".to_string())); } Ok(()) } - fn assert_nonce(claims: &GoogleClaims) -> Result<(), JwtVerifyError> { - if claims.nonce.as_deref() != Some(NONCE_OK) { - return Err(JwtVerifyError::BadClaim("nonce".to_string())); + fn assert_audience(claims: &GoogleClaims) -> Result<(), JwtVerifyError> { + if claims.aud != AUD_OK { + return Err(JwtVerifyError::BadClaim("aud".to_string())); } Ok(()) } @@ -229,6 +238,10 @@ mod verify_tests { #[test] fn verifies_ok() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), Some(KID_OK)), &claims( @@ -236,7 +249,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -245,19 +258,24 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .expect("should verify"); assert_eq!(out.claims.iss, ISS_GOOGLE); assert_eq!(out.claims.aud, AUD_OK); - assert_eq!(out.claims.nonce.as_deref(), Some(NONCE_OK)); + assert_eq!(out.claims.nonce.as_deref(), Some(nonce.as_str())); } #[test] fn missing_kid() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), None), &claims( @@ -265,7 +283,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -274,8 +292,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::MissingKid)); @@ -284,6 +303,10 @@ mod verify_tests { #[test] fn no_key_for_kid() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), Some("kid-unknown")), &claims( @@ -291,7 +314,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -300,8 +323,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::NoKeyForKid)); @@ -310,6 +334,10 @@ mod verify_tests { #[test] fn wrong_issuer_is_badsig_from_lib() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), Some(KID_OK)), &claims( @@ -317,7 +345,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -327,8 +355,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); @@ -337,6 +366,10 @@ mod verify_tests { #[test] fn wrong_typ_is_badclaim_typ() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JOT"), Some(KID_OK)), &claims( @@ -344,7 +377,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -353,8 +386,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "typ")); @@ -363,6 +397,10 @@ mod verify_tests { #[test] fn bad_audience() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), Some(KID_OK)), &claims( @@ -370,7 +408,7 @@ mod verify_tests { "wrong-aud", Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -379,8 +417,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "aud")); @@ -389,6 +428,8 @@ mod verify_tests { #[test] fn bad_nonce() { let now = now_secs(); + let salt = test_salt(); + let token = sign_token( &header(Some("JWT"), Some(KID_OK)), &claims( @@ -396,7 +437,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some("nope"), + Some("wrong-nonce"), Some(now + 600), ), ); @@ -405,8 +446,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "nonce")); @@ -415,6 +457,10 @@ mod verify_tests { #[test] fn iat_too_far_in_future() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let future = now + 4 * 60; // +4min, threshold is 2min skew let token = sign_token( &header(Some("JWT"), Some(KID_OK)), @@ -423,7 +469,7 @@ mod verify_tests { AUD_OK, Some(future), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -432,8 +478,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "iat_future")); @@ -442,6 +489,10 @@ mod verify_tests { #[test] fn iat_too_old() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let old = now.saturating_sub(11 * 60); // >10 min let token = sign_token( &header(Some("JWT"), Some(KID_OK)), @@ -450,7 +501,7 @@ mod verify_tests { AUD_OK, Some(old), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -459,8 +510,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadClaim(ref f) if f == "iat_expired")); @@ -469,6 +521,10 @@ mod verify_tests { #[test] fn nbf_in_future_is_rejected_by_lib() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let nbf_future = now + 300; // +5 min let token = sign_token( &header(Some("JWT"), Some(KID_OK)), @@ -477,7 +533,7 @@ mod verify_tests { AUD_OK, Some(now), Some(nbf_future), - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -487,8 +543,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); @@ -497,6 +554,10 @@ mod verify_tests { #[test] fn bad_signature_with_wrong_key_material() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); + let token = sign_token( &header(Some("JWT"), Some(KID_OK)), &claims( @@ -504,7 +565,7 @@ mod verify_tests { AUD_OK, Some(now), None, - Some(NONCE_OK), + Some(&nonce), Some(now + 600), ), ); @@ -529,8 +590,9 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[bad_jwk], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .unwrap_err(); assert!(matches!(err, JwtVerifyError::BadSig(_))); @@ -539,6 +601,9 @@ mod verify_tests { #[test] fn decodes_optional_profile_claims() { let now = now_secs(); + let salt = test_salt(); + + let nonce = build_nonce(&salt); let c = GoogleClaims { iss: ISS_GOOGLE.into(), @@ -553,7 +618,7 @@ mod verify_tests { family_name: Some("World".into()), preferred_username: Some("hello_world".into()), picture: Some("https://example.com/world.png".into()), - nonce: Some(NONCE_OK.into()), + nonce: Some(nonce.clone()), locale: Some("fr-CH".into()), }; @@ -563,14 +628,15 @@ mod verify_tests { &token, &[ISS_GOOGLE], &[jwk_with_kid(KID_OK)], - |claims| assert_audience(claims), - |claims| assert_nonce(claims), + &salt, + assert_nonce, + assert_audience, ) .expect("should verify"); let claims = out.claims; assert_eq!(claims.email.as_deref(), Some("hello@example.com")); - assert_eq!(claims.name.as_deref(), Some("World")); // unicode/emoji + assert_eq!(claims.name.as_deref(), Some("World")); assert_eq!(claims.given_name.as_deref(), Some("Hello")); assert_eq!(claims.family_name.as_deref(), Some("World")); assert_eq!(claims.preferred_username.as_deref(), Some("hello_world")); diff --git a/src/libs/auth/src/openid/mod.rs b/src/libs/auth/src/openid/mod.rs index 422504801f..74d28a01c7 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -3,3 +3,4 @@ mod impls; pub mod jwkset; pub mod jwt; pub mod types; +mod utils; diff --git a/src/libs/auth/src/openid/credentials/delegation/utils/mod.rs b/src/libs/auth/src/openid/utils/mod.rs similarity index 100% rename from src/libs/auth/src/openid/credentials/delegation/utils/mod.rs rename to src/libs/auth/src/openid/utils/mod.rs diff --git a/src/libs/auth/src/openid/credentials/delegation/utils/nonce.rs b/src/libs/auth/src/openid/utils/nonce.rs similarity index 100% rename from src/libs/auth/src/openid/credentials/delegation/utils/nonce.rs rename to src/libs/auth/src/openid/utils/nonce.rs diff --git a/src/libs/satellite/src/automation/prepare.rs b/src/libs/satellite/src/automation/prepare.rs index 9a92032e66..d8d13dfb39 100644 --- a/src/libs/satellite/src/automation/prepare.rs +++ b/src/libs/satellite/src/automation/prepare.rs @@ -24,7 +24,7 @@ pub async fn openid_prepare_automation( ) -> OpenIdPrepareAutomationResult { let (credential, provider) = match credentials::automation::verify_openid_credentials_with_jwks_renewal( - &args.jwt, providers, &AuthHeap, + &args.jwt, &args.salt, providers, &AuthHeap, ) .await { From 23e66bc06b9d87fc47d7d5add181bfb0ea25ca7d Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Mon, 9 Feb 2026 18:54:36 +0100 Subject: [PATCH 59/64] feat: use salt --- src/declarations/satellite/satellite.did.d.ts | 2 +- .../satellite/satellite.factory.certified.did.js | 2 +- src/declarations/satellite/satellite.factory.did.js | 2 +- src/declarations/satellite/satellite.factory.did.mjs | 2 +- src/declarations/sputnik/sputnik.did.d.ts | 2 +- src/declarations/sputnik/sputnik.factory.certified.did.js | 2 +- src/declarations/sputnik/sputnik.factory.did.js | 2 +- src/libs/satellite/satellite.did | 5 +---- src/satellite/satellite.did | 5 +---- src/sputnik/sputnik.did | 5 +---- .../declarations/test_satellite/test_satellite.did.d.ts | 2 +- .../test_satellite/test_satellite.factory.certified.did.js | 2 +- .../test_satellite/test_satellite.factory.did.js | 2 +- src/tests/fixtures/test_satellite/test_satellite.did | 5 +---- 14 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 683a21bc24..2b51db8dac 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -314,7 +314,7 @@ export interface OpenIdGetDelegationArgs { } export interface OpenIdPrepareAutomationArgs { jwt: string; - controller_id: Principal; + salt: Uint8Array; } export interface OpenIdPrepareDelegationArgs { jwt: string; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 69bdc64cec..c383ebafdc 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 40f0ca0bfd..2352d9b45e 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 40f0ca0bfd..2352d9b45e 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index 683a21bc24..2b51db8dac 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -314,7 +314,7 @@ export interface OpenIdGetDelegationArgs { } export interface OpenIdPrepareAutomationArgs { jwt: string; - controller_id: Principal; + salt: Uint8Array; } export interface OpenIdPrepareDelegationArgs { jwt: string; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index 69bdc64cec..c383ebafdc 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index 40f0ca0bfd..2352d9b45e 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 5964930a15..9b6ec25966 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -264,10 +264,7 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; -type OpenIdPrepareAutomationArgs = record { - jwt : text; - controller_id : principal; -}; +type OpenIdPrepareAutomationArgs = record { jwt : text; salt : blob }; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index fa7f395754..630508cbe0 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -266,10 +266,7 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; -type OpenIdPrepareAutomationArgs = record { - jwt : text; - controller_id : principal; -}; +type OpenIdPrepareAutomationArgs = record { jwt : text; salt : blob }; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 722754a887..d45d8463fe 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -266,10 +266,7 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; -type OpenIdPrepareAutomationArgs = record { - jwt : text; - controller_id : principal; -}; +type OpenIdPrepareAutomationArgs = record { jwt : text; salt : blob }; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 83fa6241e7..12663f9215 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -314,7 +314,7 @@ export interface OpenIdGetDelegationArgs { } export interface OpenIdPrepareAutomationArgs { jwt: string; - controller_id: Principal; + salt: Uint8Array; } export interface OpenIdPrepareDelegationArgs { jwt: string; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index 48fe336e4b..ffc8c7e148 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index 9abc2d5ef0..2f8d09542a 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -77,7 +77,7 @@ export const idlFactory = ({ IDL }) => { }); const OpenIdPrepareAutomationArgs = IDL.Record({ jwt: IDL.Text, - controller_id: IDL.Principal + salt: IDL.Vec(IDL.Nat8) }); const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index 7939b37b43..8f6e41f20f 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -266,10 +266,7 @@ type OpenIdGetDelegationArgs = record { salt : blob; expiration : nat64; }; -type OpenIdPrepareAutomationArgs = record { - jwt : text; - controller_id : principal; -}; +type OpenIdPrepareAutomationArgs = record { jwt : text; salt : blob }; type OpenIdPrepareDelegationArgs = record { jwt : text; session_key : blob; From 2f4212bdefdae0c08d5b1d18a099d7240cbfc897 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 10 Feb 2026 06:44:41 +0100 Subject: [PATCH 60/64] docs: update comment --- src/libs/collections/src/constants/db.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/collections/src/constants/db.rs b/src/libs/collections/src/constants/db.rs index 87296001ae..d3dd96ac8e 100644 --- a/src/libs/collections/src/constants/db.rs +++ b/src/libs/collections/src/constants/db.rs @@ -92,7 +92,8 @@ pub const COLLECTION_AUTOMATION_TOKEN_DEFAULT_RULE: SetRule = SetRule { }; pub const COLLECTION_AUTOMATION_WORKFLOW_DEFAULT_RULE: SetRule = SetRule { - // Created and read through internal hooks. Write is restricted to Satellites themselves. + // Created through internal hooks. Write is restricted to Satellites themselves. + // Read allowed for controllers. read: Controllers, write: Controllers, memory: Some(Memory::Stable), From 2b3fb291410f439046dce0d95bf490722beabf46 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 10 Feb 2026 09:13:05 +0100 Subject: [PATCH 61/64] feat: return automation with get_config --- src/declarations/satellite/satellite.did.d.ts | 1 + src/declarations/satellite/satellite.factory.certified.did.js | 3 ++- src/declarations/satellite/satellite.factory.did.js | 3 ++- src/declarations/satellite/satellite.factory.did.mjs | 3 ++- src/declarations/sputnik/sputnik.did.d.ts | 1 + src/declarations/sputnik/sputnik.factory.certified.did.js | 3 ++- src/declarations/sputnik/sputnik.factory.did.js | 3 ++- src/libs/satellite/satellite.did | 1 + src/libs/satellite/src/api/config.rs | 2 ++ src/libs/satellite/src/types.rs | 2 ++ src/satellite/satellite.did | 1 + src/sputnik/sputnik.did | 1 + src/tests/declarations/test_satellite/test_satellite.did.d.ts | 1 + .../test_satellite/test_satellite.factory.certified.did.js | 3 ++- .../declarations/test_satellite/test_satellite.factory.did.js | 3 ++- src/tests/fixtures/test_satellite/test_satellite.did | 1 + 16 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 2b51db8dac..194aba2a63 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -102,6 +102,7 @@ export interface Config { db: [] | [DbConfig]; authentication: [] | [AuthenticationConfig]; storage: StorageConfig; + automation: [] | [AutomationConfig]; } export interface ConfigMaxMemorySize { stable: [] | [bigint]; diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index c383ebafdc..4902e59c46 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 2352d9b45e..4fbd9a074d 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 2352d9b45e..4fbd9a074d 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index 2b51db8dac..194aba2a63 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -102,6 +102,7 @@ export interface Config { db: [] | [DbConfig]; authentication: [] | [AuthenticationConfig]; storage: StorageConfig; + automation: [] | [AutomationConfig]; } export interface ConfigMaxMemorySize { stable: [] | [bigint]; diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index c383ebafdc..4902e59c46 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index 2352d9b45e..4fbd9a074d 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 9b6ec25966..8325d2899c 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -85,6 +85,7 @@ type Config = record { db : opt DbConfig; authentication : opt AuthenticationConfig; storage : StorageConfig; + automation : opt AutomationConfig; }; type ConfigMaxMemorySize = record { stable : opt nat64; heap : opt nat64 }; type Controller = record { diff --git a/src/libs/satellite/src/api/config.rs b/src/libs/satellite/src/api/config.rs index cc59998421..ae322ed4f1 100644 --- a/src/libs/satellite/src/api/config.rs +++ b/src/libs/satellite/src/api/config.rs @@ -28,11 +28,13 @@ pub fn get_config() -> Config { let storage = get_storage_config_store(); let db = get_db_config_store(); let authentication = get_auth_config_store(); + let automation = get_automation_config_store(); Config { storage, db, authentication, + automation } } diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index 3e5f975faa..050f28f082 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -68,12 +68,14 @@ pub mod interface { use junobuild_cdn::proposals::ProposalId; use junobuild_storage::types::config::StorageConfig; use serde::{Deserialize, Serialize}; + use junobuild_auth::state::types::automation::AutomationConfig; #[derive(CandidType, Deserialize)] pub struct Config { pub storage: StorageConfig, pub db: Option, pub authentication: Option, + pub automation: Option, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 630508cbe0..ea68c96b61 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -87,6 +87,7 @@ type Config = record { db : opt DbConfig; authentication : opt AuthenticationConfig; storage : StorageConfig; + automation : opt AutomationConfig; }; type ConfigMaxMemorySize = record { stable : opt nat64; heap : opt nat64 }; type Controller = record { diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index d45d8463fe..046a0ccd58 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -87,6 +87,7 @@ type Config = record { db : opt DbConfig; authentication : opt AuthenticationConfig; storage : StorageConfig; + automation : opt AutomationConfig; }; type ConfigMaxMemorySize = record { stable : opt nat64; heap : opt nat64 }; type Controller = record { diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 12663f9215..1f03ee7e4f 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -102,6 +102,7 @@ export interface Config { db: [] | [DbConfig]; authentication: [] | [AuthenticationConfig]; storage: StorageConfig; + automation: [] | [AutomationConfig]; } export interface ConfigMaxMemorySize { stable: [] | [bigint]; diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index ffc8c7e148..809d41d163 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index 2f8d09542a..ed5d568fb7 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -284,7 +284,8 @@ export const idlFactory = ({ IDL }) => { const Config = IDL.Record({ db: IDL.Opt(DbConfig), authentication: IDL.Opt(AuthenticationConfig), - storage: StorageConfig + storage: StorageConfig, + automation: IDL.Opt(AutomationConfig) }); const OpenIdGetDelegationArgs = IDL.Record({ jwt: IDL.Text, diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index 8f6e41f20f..45086480f7 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -87,6 +87,7 @@ type Config = record { db : opt DbConfig; authentication : opt AuthenticationConfig; storage : StorageConfig; + automation : opt AutomationConfig; }; type ConfigMaxMemorySize = record { stable : opt nat64; heap : opt nat64 }; type Controller = record { From 0ebeeafd6c24b726c958cbacd49ee31eee959ddc Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 10 Feb 2026 13:32:08 +0100 Subject: [PATCH 62/64] feat: include set and get automation config --- src/libs/satellite/src/api/config.rs | 2 +- src/libs/satellite/src/lib.rs | 17 +++++++++-------- src/libs/satellite/src/types.rs | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/satellite/src/api/config.rs b/src/libs/satellite/src/api/config.rs index ae322ed4f1..f41919b73d 100644 --- a/src/libs/satellite/src/api/config.rs +++ b/src/libs/satellite/src/api/config.rs @@ -34,7 +34,7 @@ pub fn get_config() -> Config { storage, db, authentication, - automation + automation, } } diff --git a/src/libs/satellite/src/lib.rs b/src/libs/satellite/src/lib.rs index 6646288f8d..867020936a 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -572,14 +572,15 @@ macro_rules! include_satellite { count_collection_assets, count_collection_docs, count_docs, count_proposals, del_asset, del_assets, del_controllers, del_custom_domain, del_doc, del_docs, del_filtered_assets, del_filtered_docs, del_many_assets, del_many_docs, del_rule, delete_proposal_assets, - deposit_cycles, get_asset, get_auth_config, get_config, get_db_config, get_delegation, - get_doc, get_many_assets, get_many_docs, get_proposal, get_storage_config, - http_request, http_request_streaming_callback, init, init_asset_upload, init_proposal, - init_proposal_asset_upload, init_proposal_many_assets_upload, list_assets, - list_controllers, list_custom_domains, list_docs, list_proposals, list_rules, - post_upgrade, pre_upgrade, reject_proposal, set_asset_token, set_auth_config, - set_controllers, set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, - set_storage_config, submit_proposal, switch_storage_system_memory, upload_asset_chunk, + deposit_cycles, get_asset, get_auth_config, get_automation_config, get_config, + get_db_config, get_delegation, get_doc, get_many_assets, get_many_docs, get_proposal, + get_storage_config, http_request, http_request_streaming_callback, init, + init_asset_upload, init_proposal, init_proposal_asset_upload, + init_proposal_many_assets_upload, list_assets, list_controllers, list_custom_domains, + list_docs, list_proposals, list_rules, post_upgrade, pre_upgrade, reject_proposal, + set_asset_token, set_auth_config, set_automation_config, set_controllers, + set_custom_domain, set_db_config, set_doc, set_many_docs, set_rule, set_storage_config, + submit_proposal, switch_storage_system_memory, upload_asset_chunk, upload_proposal_asset_chunk, }; diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index 050f28f082..d6439ee165 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -64,11 +64,11 @@ pub mod interface { GetDelegationError, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, SignedDelegation, }; + use junobuild_auth::state::types::automation::AutomationConfig; use junobuild_auth::state::types::config::AuthenticationConfig; use junobuild_cdn::proposals::ProposalId; use junobuild_storage::types::config::StorageConfig; use serde::{Deserialize, Serialize}; - use junobuild_auth::state::types::automation::AutomationConfig; #[derive(CandidType, Deserialize)] pub struct Config { From 742abf4b0ca4a76543ef88c4f0cd36313af3ff63 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 10 Feb 2026 13:44:18 +0100 Subject: [PATCH 63/64] feat: return prepared automation --- src/libs/auth/src/automation/prepare.rs | 11 +++-------- src/libs/auth/src/automation/types.rs | 7 ++----- src/libs/satellite/src/automation/authenticate.rs | 2 +- .../satellite/src/automation/controllers/register.rs | 8 +++++--- src/libs/satellite/src/automation/token/services.rs | 4 +++- src/libs/satellite/src/automation/types.rs | 6 ++++-- src/libs/satellite/src/types.rs | 3 ++- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/libs/auth/src/automation/prepare.rs b/src/libs/auth/src/automation/prepare.rs index c401e049da..b122c73cfe 100644 --- a/src/libs/auth/src/automation/prepare.rs +++ b/src/libs/auth/src/automation/prepare.rs @@ -1,6 +1,5 @@ use crate::automation::types::{ - PrepareAutomationError, PrepareAutomationResult, PreparedAutomation, - PreparedControllerAutomation, + AutomationController, PrepareAutomationError, PrepareAutomationResult, PreparedAutomation, }; use crate::automation::utils::duration::build_expiration; use crate::automation::utils::scope::build_scope; @@ -42,11 +41,7 @@ pub fn openid_prepare_automation( let expires_at = build_expiration(provider, auth_heap); - let controller: PreparedControllerAutomation = PreparedControllerAutomation { - id: controller_id, - expires_at, - scope, - }; + let controller: AutomationController = AutomationController { expires_at, scope }; - Ok(PreparedAutomation { controller }) + Ok(PreparedAutomation(controller_id, controller)) } diff --git a/src/libs/auth/src/automation/types.rs b/src/libs/auth/src/automation/types.rs index de0f0d8dee..68ffc87d79 100644 --- a/src/libs/auth/src/automation/types.rs +++ b/src/libs/auth/src/automation/types.rs @@ -14,13 +14,10 @@ pub struct OpenIdPrepareAutomationArgs { pub type PrepareAutomationResult = Result; #[derive(CandidType, Serialize, Deserialize)] -pub struct PreparedAutomation { - pub controller: PreparedControllerAutomation, -} +pub struct PreparedAutomation(pub ControllerId, pub AutomationController); #[derive(CandidType, Serialize, Deserialize)] -pub struct PreparedControllerAutomation { - pub id: ControllerId, +pub struct AutomationController { pub scope: AutomationScope, pub expires_at: u64, } diff --git a/src/libs/satellite/src/automation/authenticate.rs b/src/libs/satellite/src/automation/authenticate.rs index b3e7a62fe4..d6c09a0048 100644 --- a/src/libs/satellite/src/automation/authenticate.rs +++ b/src/libs/satellite/src/automation/authenticate.rs @@ -30,7 +30,7 @@ pub async fn openid_authenticate_automation( return Ok(Err(AuthenticationAutomationError::RegisterController(err))); } - Ok(()) + Ok(automation) } Err(err) => Err(AuthenticationAutomationError::PrepareAutomation(err)), }; diff --git a/src/libs/satellite/src/automation/controllers/register.rs b/src/libs/satellite/src/automation/controllers/register.rs index 47a38bf641..d568b6082c 100644 --- a/src/libs/satellite/src/automation/controllers/register.rs +++ b/src/libs/satellite/src/automation/controllers/register.rs @@ -11,7 +11,9 @@ pub fn register_controller( provider: &OpenIdAutomationProvider, credential: &OpenIdAutomationCredential, ) -> Result<(), String> { - let controllers: [ControllerId; 1] = [automation.controller.id]; + let PreparedAutomation(controller_id, controller) = automation; + + let controllers: [ControllerId; 1] = [controller_id.clone()]; let automation_workflow_key = build_automation_workflow_key(provider, credential)?; @@ -19,9 +21,9 @@ pub fn register_controller( metadata.insert("workflow_key".to_string(), automation_workflow_key.to_key()); let controller: SetController = SetController { - scope: automation.controller.scope.clone().into(), + scope: controller.scope.clone().into(), metadata, - expires_at: Some(automation.controller.expires_at), + expires_at: Some(controller.expires_at), kind: Some(ControllerKind::Automation), }; diff --git a/src/libs/satellite/src/automation/token/services.rs b/src/libs/satellite/src/automation/token/services.rs index a01541fb78..39fdc78c02 100644 --- a/src/libs/satellite/src/automation/token/services.rs +++ b/src/libs/satellite/src/automation/token/services.rs @@ -44,9 +44,11 @@ pub fn save_unique_token_jti( } // Create metadata. + let PreparedAutomation(controller_id, _) = prepared_automation; + let automation_token_data: AutomationTokenData = AutomationTokenData { controller_id: DocDataPrincipal { - value: prepared_automation.controller.id, + value: controller_id.clone(), }, }; diff --git a/src/libs/satellite/src/automation/types.rs b/src/libs/satellite/src/automation/types.rs index ef33018325..33e5b921f7 100644 --- a/src/libs/satellite/src/automation/types.rs +++ b/src/libs/satellite/src/automation/types.rs @@ -1,5 +1,7 @@ use candid::{CandidType, Deserialize}; -use junobuild_auth::automation::types::{OpenIdPrepareAutomationArgs, PrepareAutomationError}; +use junobuild_auth::automation::types::{ + OpenIdPrepareAutomationArgs, PrepareAutomationError, PreparedAutomation, +}; use serde::Serialize; #[derive(CandidType, Serialize, Deserialize)] @@ -15,4 +17,4 @@ pub enum AuthenticationAutomationError { RegisterController(String), } -pub type AuthenticateAutomationResult = Result<(), AuthenticationAutomationError>; +pub type AuthenticateAutomationResult = Result; diff --git a/src/libs/satellite/src/types.rs b/src/libs/satellite/src/types.rs index d6439ee165..5b180513d0 100644 --- a/src/libs/satellite/src/types.rs +++ b/src/libs/satellite/src/types.rs @@ -60,6 +60,7 @@ pub mod interface { use crate::db::types::config::DbConfig; use crate::Doc; use candid::CandidType; + use junobuild_auth::automation::types::PreparedAutomation; use junobuild_auth::delegation::types::{ GetDelegationError, OpenIdGetDelegationArgs, OpenIdPrepareDelegationArgs, PrepareDelegationError, PreparedDelegation, SignedDelegation, @@ -124,7 +125,7 @@ pub mod interface { #[derive(CandidType, Serialize, Deserialize)] pub enum AuthenticateAutomationResultResponse { - Ok(()), + Ok(PreparedAutomation), Err(AuthenticationAutomationError), } } From a4de006616c2d45f607a42ee83396a898e661045 Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Tue, 10 Feb 2026 13:53:07 +0100 Subject: [PATCH 64/64] feat: return prepared automation --- src/declarations/satellite/satellite.did.d.ts | 8 +++++++- .../satellite/satellite.factory.certified.did.js | 14 +++++++++----- .../satellite/satellite.factory.did.js | 14 +++++++++----- .../satellite/satellite.factory.did.mjs | 14 +++++++++----- src/declarations/sputnik/sputnik.did.d.ts | 8 +++++++- .../sputnik/sputnik.factory.certified.did.js | 14 +++++++++----- src/declarations/sputnik/sputnik.factory.did.js | 14 +++++++++----- src/libs/satellite/satellite.did | 6 +++++- src/satellite/satellite.did | 6 +++++- src/sputnik/sputnik.did | 6 +++++- .../test_satellite/test_satellite.did.d.ts | 8 +++++++- .../test_satellite.factory.certified.did.js | 14 +++++++++----- .../test_satellite/test_satellite.factory.did.js | 14 +++++++++----- .../fixtures/test_satellite/test_satellite.did | 6 +++++- 14 files changed, 104 insertions(+), 42 deletions(-) diff --git a/src/declarations/satellite/satellite.did.d.ts b/src/declarations/satellite/satellite.did.d.ts index 194aba2a63..a290a19570 100644 --- a/src/declarations/satellite/satellite.did.d.ts +++ b/src/declarations/satellite/satellite.did.d.ts @@ -38,7 +38,9 @@ export type AuthenticateAutomationArgs = { OpenId: OpenIdPrepareAutomationArgs; }; export type AuthenticateAutomationResultResponse = - | { Ok: null } + | { + Ok: [Principal, AutomationController]; + } | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { @@ -87,6 +89,10 @@ export interface AutomationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; } +export interface AutomationController { + scope: AutomationScope; + expires_at: bigint; +} export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { diff --git a/src/declarations/satellite/satellite.factory.certified.did.js b/src/declarations/satellite/satellite.factory.certified.did.js index 4902e59c46..db425c6b99 100644 --- a/src/declarations/satellite/satellite.factory.certified.did.js +++ b/src/declarations/satellite/satellite.factory.certified.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/satellite/satellite.factory.did.js b/src/declarations/satellite/satellite.factory.did.js index 4fbd9a074d..ea3bbd4157 100644 --- a/src/declarations/satellite/satellite.factory.did.js +++ b/src/declarations/satellite/satellite.factory.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/satellite/satellite.factory.did.mjs b/src/declarations/satellite/satellite.factory.did.mjs index 4fbd9a074d..ea3bbd4157 100644 --- a/src/declarations/satellite/satellite.factory.did.mjs +++ b/src/declarations/satellite/satellite.factory.did.mjs @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/sputnik/sputnik.did.d.ts b/src/declarations/sputnik/sputnik.did.d.ts index 194aba2a63..a290a19570 100644 --- a/src/declarations/sputnik/sputnik.did.d.ts +++ b/src/declarations/sputnik/sputnik.did.d.ts @@ -38,7 +38,9 @@ export type AuthenticateAutomationArgs = { OpenId: OpenIdPrepareAutomationArgs; }; export type AuthenticateAutomationResultResponse = - | { Ok: null } + | { + Ok: [Principal, AutomationController]; + } | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { @@ -87,6 +89,10 @@ export interface AutomationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; } +export interface AutomationController { + scope: AutomationScope; + expires_at: bigint; +} export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { diff --git a/src/declarations/sputnik/sputnik.factory.certified.did.js b/src/declarations/sputnik/sputnik.factory.certified.did.js index 4902e59c46..db425c6b99 100644 --- a/src/declarations/sputnik/sputnik.factory.certified.did.js +++ b/src/declarations/sputnik/sputnik.factory.certified.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/declarations/sputnik/sputnik.factory.did.js b/src/declarations/sputnik/sputnik.factory.did.js index 4fbd9a074d..ea3bbd4157 100644 --- a/src/declarations/sputnik/sputnik.factory.did.js +++ b/src/declarations/sputnik/sputnik.factory.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/libs/satellite/satellite.did b/src/libs/satellite/satellite.did index 8325d2899c..eaf9d604ea 100644 --- a/src/libs/satellite/satellite.did +++ b/src/libs/satellite/satellite.did @@ -24,7 +24,7 @@ type AuthenticateAutomationArgs = variant { OpenId : OpenIdPrepareAutomationArgs; }; type AuthenticateAutomationResultResponse = variant { - Ok; + Ok : record { principal; AutomationController }; Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { @@ -73,6 +73,10 @@ type AutomationConfigOpenId = record { OpenIdAutomationProviderConfig; }; }; +type AutomationController = record { + scope : AutomationScope; + expires_at : nat64; +}; type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index ea68c96b61..af75dc32f8 100644 --- a/src/satellite/satellite.did +++ b/src/satellite/satellite.did @@ -26,7 +26,7 @@ type AuthenticateAutomationArgs = variant { OpenId : OpenIdPrepareAutomationArgs; }; type AuthenticateAutomationResultResponse = variant { - Ok; + Ok : record { principal; AutomationController }; Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { @@ -75,6 +75,10 @@ type AutomationConfigOpenId = record { OpenIdAutomationProviderConfig; }; }; +type AutomationController = record { + scope : AutomationScope; + expires_at : nat64; +}; type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { diff --git a/src/sputnik/sputnik.did b/src/sputnik/sputnik.did index 046a0ccd58..84b9e9666f 100644 --- a/src/sputnik/sputnik.did +++ b/src/sputnik/sputnik.did @@ -26,7 +26,7 @@ type AuthenticateAutomationArgs = variant { OpenId : OpenIdPrepareAutomationArgs; }; type AuthenticateAutomationResultResponse = variant { - Ok; + Ok : record { principal; AutomationController }; Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { @@ -75,6 +75,10 @@ type AutomationConfigOpenId = record { OpenIdAutomationProviderConfig; }; }; +type AutomationController = record { + scope : AutomationScope; + expires_at : nat64; +}; type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record { diff --git a/src/tests/declarations/test_satellite/test_satellite.did.d.ts b/src/tests/declarations/test_satellite/test_satellite.did.d.ts index 1f03ee7e4f..e8d40d0cbe 100644 --- a/src/tests/declarations/test_satellite/test_satellite.did.d.ts +++ b/src/tests/declarations/test_satellite/test_satellite.did.d.ts @@ -38,7 +38,9 @@ export type AuthenticateAutomationArgs = { OpenId: OpenIdPrepareAutomationArgs; }; export type AuthenticateAutomationResultResponse = - | { Ok: null } + | { + Ok: [Principal, AutomationController]; + } | { Err: AuthenticationAutomationError }; export type AuthenticateResultResponse = { Ok: Authentication } | { Err: AuthenticationError }; export interface Authentication { @@ -87,6 +89,10 @@ export interface AutomationConfigOpenId { observatory_id: [] | [Principal]; providers: Array<[OpenIdAutomationProvider, OpenIdAutomationProviderConfig]>; } +export interface AutomationController { + scope: AutomationScope; + expires_at: bigint; +} export type AutomationScope = { Write: null } | { Submit: null }; export type CollectionType = { Db: null } | { Storage: null }; export interface CommitBatch { diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js index 809d41d163..6fc6385bbf 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.certified.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/tests/declarations/test_satellite/test_satellite.factory.did.js b/src/tests/declarations/test_satellite/test_satellite.factory.did.js index ed5d568fb7..f718438304 100644 --- a/src/tests/declarations/test_satellite/test_satellite.factory.did.js +++ b/src/tests/declarations/test_satellite/test_satellite.factory.did.js @@ -82,6 +82,14 @@ export const idlFactory = ({ IDL }) => { const AuthenticateAutomationArgs = IDL.Variant({ OpenId: OpenIdPrepareAutomationArgs }); + const AutomationScope = IDL.Variant({ + Write: IDL.Null, + Submit: IDL.Null + }); + const AutomationController = IDL.Record({ + scope: AutomationScope, + expires_at: IDL.Nat64 + }); const PrepareAutomationError = IDL.Variant({ JwtFindProvider: JwtFindProviderError, InvalidController: IDL.Text, @@ -98,7 +106,7 @@ export const idlFactory = ({ IDL }) => { SaveUniqueJtiToken: IDL.Text }); const AuthenticateAutomationResultResponse = IDL.Variant({ - Ok: IDL.Null, + Ok: IDL.Tuple(IDL.Principal, AutomationController), Err: AuthenticationAutomationError }); const CommitBatch = IDL.Record({ @@ -221,10 +229,6 @@ export const idlFactory = ({ IDL }) => { rules: IDL.Opt(AuthenticationRules) }); const OpenIdAutomationProvider = IDL.Variant({ GitHub: IDL.Null }); - const AutomationScope = IDL.Variant({ - Write: IDL.Null, - Submit: IDL.Null - }); const OpenIdAutomationProviderControllerConfig = IDL.Record({ scope: IDL.Opt(AutomationScope), max_time_to_live: IDL.Opt(IDL.Nat64) diff --git a/src/tests/fixtures/test_satellite/test_satellite.did b/src/tests/fixtures/test_satellite/test_satellite.did index 45086480f7..a9628a6e9b 100644 --- a/src/tests/fixtures/test_satellite/test_satellite.did +++ b/src/tests/fixtures/test_satellite/test_satellite.did @@ -26,7 +26,7 @@ type AuthenticateAutomationArgs = variant { OpenId : OpenIdPrepareAutomationArgs; }; type AuthenticateAutomationResultResponse = variant { - Ok; + Ok : record { principal; AutomationController }; Err : AuthenticationAutomationError; }; type AuthenticateResultResponse = variant { @@ -75,6 +75,10 @@ type AutomationConfigOpenId = record { OpenIdAutomationProviderConfig; }; }; +type AutomationController = record { + scope : AutomationScope; + expires_at : nat64; +}; type AutomationScope = variant { Write; Submit }; type CollectionType = variant { Db; Storage }; type CommitBatch = record {