diff --git a/src/console/console.did b/src/console/console.did index 152d748729..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,7 +223,11 @@ type ListSegmentsArgs = record { segment_kind : opt StorableSegmentKind; }; type Memory = variant { Heap; Stable }; -type OpenId = record { provider : OpenIdProvider; data : OpenIdData }; +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; @@ -233,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; @@ -244,15 +249,10 @@ type OpenIdPrepareDelegationArgs = record { session_key : blob; salt : blob; }; -type OpenIdProvider = variant { GitHub; Google }; -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/console/src/accounts/impls.rs b/src/console/src/accounts/impls.rs index 9a5e527a4c..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::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 78a9fc0a96..b259366fce 100644 --- a/src/console/src/auth/delegation.rs +++ b/src/console/src/auth/delegation.rs @@ -4,26 +4,33 @@ 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::state::types::config::OpenIdProviders; +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}; -pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdProvider, OpenIdCredential), PrepareDelegationError>; +pub type OpenIdPrepareDelegationResult = Result< + ( + PreparedDelegation, + OpenIdDelegationProvider, + OpenIdCredential, + ), + PrepareDelegationError, +>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::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, @@ -38,14 +45,15 @@ pub async fn openid_prepare_delegation( pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::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 5dec898413..4da81b00c6 100644 --- a/src/console/src/auth/register.rs +++ b/src/console/src/auth/register.rs @@ -3,12 +3,12 @@ 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::types::interface::OpenIdCredential; -use junobuild_auth::openid::types::provider::OpenIdProvider; +use junobuild_auth::openid::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; pub async fn register_account( public_key: &UserKey, - provider: &OpenIdProvider, + provider: &OpenIdDelegationProvider, credential: &OpenIdCredential, ) -> Result { let user_id = Principal::self_authenticating(public_key); 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..5a782b9791 100644 --- a/src/console/src/memory/lifecycle.rs +++ b/src/console/src/memory/lifecycle.rs @@ -4,6 +4,7 @@ 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; @@ -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/types.rs b/src/console/src/types.rs index 7af84477c8..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::types::provider::OpenIdProvider; + 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}; @@ -83,7 +83,7 @@ pub mod state { #[derive(CandidType, Serialize, Deserialize, Clone)] pub struct OpenId { - pub provider: OpenIdProvider, + pub provider: OpenIdDelegationProvider, pub data: OpenIdData, } diff --git a/src/console/src/upgrade/impls.rs b/src/console/src/upgrade/impls.rs new file mode 100644 index 0000000000..f4a6f20b88 --- /dev/null +++ b/src/console/src/upgrade/impls.rs @@ -0,0 +1,56 @@ +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}; + +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, + } + } +} 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..cde8370010 --- /dev/null +++ b/src/console/src/upgrade/types.rs @@ -0,0 +1,58 @@ +pub mod upgrade { + use crate::memory::manager::init_stable_state; + use crate::types::state::{ + Accounts, FactoryFees, FactoryRates, IcpPayments, InvitationCodes, ReleasesMetadata, + StableState, + }; + use candid::{CandidType, Deserialize}; + 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 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, 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, + } +} diff --git a/src/declarations/console/console.did.d.ts b/src/declarations/console/console.did.d.ts index c084e35f32..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,9 +277,13 @@ export interface ListSegmentsArgs { } export type Memory = { Heap: null } | { Stable: null }; export interface OpenId { - provider: OpenIdProvider; + provider: OpenIdDelegationProvider; data: OpenIdData; } +export interface OpenIdAuthProviderDelegationConfig { + targets: [] | [Array]; + max_time_to_live: [] | [bigint]; +} export interface OpenIdData { name: [] | [string]; locale: [] | [string]; @@ -289,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; @@ -300,15 +305,10 @@ export interface OpenIdPrepareDelegationArgs { session_key: Uint8Array; salt: Uint8Array; } -export type OpenIdProvider = { GitHub: null } | { Google: 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 ef34c851dc..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 OpenIdProvider = 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: OpenIdProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,17 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - 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 250efe59ac..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 OpenIdProvider = 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: OpenIdProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,17 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - 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 250efe59ac..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 OpenIdProvider = 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: OpenIdProvider, + provider: OpenIdDelegationProvider, data: OpenIdData }); const Provider = IDL.Variant({ @@ -133,17 +133,17 @@ export const idlFactory = ({ IDL }) => { const DeleteProposalAssets = IDL.Record({ proposal_ids: IDL.Vec(IDL.Nat) }); - 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/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..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 = { GitHub: null } | { Google: 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 b5c665b978..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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), @@ -446,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 ba851264b2..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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), @@ -446,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 ba851264b2..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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), @@ -446,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 dde06ba223..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 = { GitHub: null } | { Google: 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 b5c665b978..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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), @@ -446,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 ba851264b2..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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), @@ -446,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/auth/src/delegation/get.rs b/src/libs/auth/src/delegation/get.rs index 29801c37e7..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::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::types::provider::OpenIdProvider; +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}; @@ -15,7 +15,7 @@ pub fn openid_get_delegation( session_key: &SessionKey, expiration: Timestamp, credential: &OpenIdCredential, - provider: &OpenIdProvider, + 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: &OpenIdProvider, + provider: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, certificate: &impl AuthCertificateStrategy, ) -> GetDelegationResult { diff --git a/src/libs/auth/src/delegation/impls.rs b/src/libs/auth/src/delegation/impls.rs index 699cd97fb3..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::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 9a61d458bf..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::types::interface::{OpenIdCredential, OpenIdCredentialKey}; -use crate::openid::types::provider::OpenIdProvider; +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}; @@ -18,7 +18,7 @@ use serde_bytes::ByteBuf; pub fn openid_prepare_delegation( session_key: &SessionKey, credential: &OpenIdCredential, - provider: &OpenIdProvider, + 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: &OpenIdProvider, + 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: &OpenIdProvider, + 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 587f842741..7a7ca03d3a 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::types::provider::OpenIdProvider; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use ic_cdk::api::time; use std::cmp::min; -pub fn build_expiration(provider: &OpenIdProvider, 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/seed.rs b/src/libs/auth/src/delegation/utils/seed.rs index 7ef1f44162..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::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::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 2d837cc43d..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::types::provider::OpenIdProvider; +use crate::openid::delegation::types::provider::OpenIdDelegationProvider; use crate::state::get_config; use crate::strategies::AuthHeapStrategy; use junobuild_shared::ic::api::id; @@ -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: &OpenIdDelegationProvider, auth_heap: &impl AuthHeapStrategy, ) -> Option { get_config(auth_heap) diff --git a/src/libs/auth/src/openid/automation/mod.rs b/src/libs/auth/src/openid/automation/mod.rs new file mode 100644 index 0000000000..d9a483abcc --- /dev/null +++ b/src/libs/auth/src/openid/automation/mod.rs @@ -0,0 +1,4 @@ +pub mod types; +mod verify; + +pub use verify::*; diff --git a/src/libs/auth/src/openid/automation/types.rs b/src/libs/auth/src/openid/automation/types.rs new file mode 100644 index 0000000000..9fd4d03ff1 --- /dev/null +++ b/src/libs/auth/src/openid/automation/types.rs @@ -0,0 +1,13 @@ +pub 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 VerifyOpenidAutomationCredentialsError { + GetOrFetchJwks(GetOrRefreshJwksError), + GetCachedJwks, + JwtVerify(JwtVerifyError), + } +} diff --git a/src/libs/auth/src/openid/automation/verify.rs b/src/libs/auth/src/openid/automation/verify.rs new file mode 100644 index 0000000000..20236f718c --- /dev/null +++ b/src/libs/auth/src/openid/automation/verify.rs @@ -0,0 +1,61 @@ +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::strategies::AuthHeapStrategy; + +type VerifyOpenIdAutomationCredentialsResult = Result<(), VerifyOpenidAutomationCredentialsError>; + +pub async fn verify_openid_credentials_with_jwks_renewal( + jwt: &str, + provider: &OpenIdProvider, + auth_heap: &impl AuthHeapStrategy, +) -> VerifyOpenIdAutomationCredentialsResult { + let jwks = get_or_refresh_jwks(&provider, jwt, auth_heap) + .await + .map_err(VerifyOpenidAutomationCredentialsError::GetOrFetchJwks)?; + + verify_openid_credentials(jwt, &jwks, &provider) +} + +fn verify_openid_credentials( + jwt: &str, + jwks: &Jwks, + provider: &OpenIdProvider, +) -> VerifyOpenIdAutomationCredentialsResult { + 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(()) + }; + + verify_openid_jwt( + jwt, + provider.issuers(), + &jwks.keys, + &assert_audience, + &assert_no_replay, + ) + .map_err(VerifyOpenidAutomationCredentialsError::JwtVerify)?; + + Ok(()) +} diff --git a/src/libs/auth/src/openid/delegation/impls.rs b/src/libs/auth/src/openid/delegation/impls.rs new file mode 100644 index 0000000000..e42041d995 --- /dev/null +++ b/src/libs/auth/src/openid/delegation/impls.rs @@ -0,0 +1,70 @@ +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 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, + } + } +} + +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/delegation/mod.rs b/src/libs/auth/src/openid/delegation/mod.rs new file mode 100644 index 0000000000..35c6668fcf --- /dev/null +++ b/src/libs/auth/src/openid/delegation/mod.rs @@ -0,0 +1,5 @@ +mod impls; +pub mod types; +mod verify; + +pub use verify::*; diff --git a/src/libs/auth/src/openid/delegation/types.rs b/src/libs/auth/src/openid/delegation/types.rs new file mode 100644 index 0000000000..8646d806e3 --- /dev/null +++ b/src/libs/auth/src/openid/delegation/types.rs @@ -0,0 +1,47 @@ +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 candid::{CandidType, Deserialize}; + use serde::Serialize; + + #[derive( + CandidType, Serialize, Deserialize, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, + )] + pub enum OpenIdDelegationProvider { + Google, + GitHub, + } +} diff --git a/src/libs/auth/src/openid/delegation/verify.rs b/src/libs/auth/src/openid/delegation/verify.rs new file mode 100644 index 0000000000..1078079e4d --- /dev/null +++ b/src/libs/auth/src/openid/delegation/verify.rs @@ -0,0 +1,89 @@ +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::utils::build_nonce; +use crate::state::types::config::{OpenIdAuthProviderClientId, OpenIdAuthProviders}; +use crate::state::types::state::Salt; +use crate::strategies::AuthHeapStrategy; + +type VerifyOpenIdDelegationCredentialsResult = + Result<(OpenIdCredential, OpenIdDelegationProvider), VerifyOpenidCredentialsError>; + +pub async fn verify_openid_credentials_with_jwks_renewal( + jwt: &str, + salt: &Salt, + providers: &OpenIdAuthProviders, + auth_heap: &impl AuthHeapStrategy, +) -> VerifyOpenIdDelegationCredentialsResult { + 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, &auth_provider, &config.client_id, salt) +} + +pub fn verify_openid_credentials_with_cached_jwks( + jwt: &str, + salt: &Salt, + providers: &OpenIdAuthProviders, + auth_heap: &impl AuthHeapStrategy, +) -> VerifyOpenIdDelegationCredentialsResult { + 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, &auth_provider, &config.client_id, salt) +} + +fn verify_openid_credentials( + jwt: &str, + jwks: &Jwks, + provider: &OpenIdDelegationProvider, + client_id: &OpenIdAuthProviderClientId, + salt: &Salt, +) -> VerifyOpenIdDelegationCredentialsResult { + let assert_audience = |claims: &Claims| -> Result<(), JwtVerifyError> { + if claims.aud != client_id.as_str() { + return Err(JwtVerifyError::BadClaim("aud".to_string())); + } + + 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); + + Ok((credential, provider.clone())) +} diff --git a/src/libs/auth/src/openid/impls.rs b/src/libs/auth/src/openid/impls.rs index 24002271f9..c13a7a7c61 100644 --- a/src/libs/auth/src/openid/impls.rs +++ b/src/libs/auth/src/openid/impls.rs @@ -1,52 +1,26 @@ 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 { 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", + Self::GitHubActions => "https://token.actions.githubusercontent.com/.well-known/jwks", } } 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"], + OpenIdProvider::GitHubActions => &["https://token.actions.githubusercontent.com"], } } } @@ -93,7 +67,8 @@ 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 Proxy"), + OpenIdProvider::GitHubActions => write!(f, "GitHub Actions"), } } } diff --git a/src/libs/auth/src/openid/jwt/provider.rs b/src/libs/auth/src/openid/jwt/provider.rs index 42f5cbebde..0890733fa0 100644 --- a/src/libs/auth/src/openid/jwt/provider.rs +++ b/src/libs/auth/src/openid/jwt/provider.rs @@ -1,16 +1,16 @@ +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::types::provider::OpenIdProvider; -use crate::state::types::config::{OpenIdProviderConfig, OpenIdProviders}; +use crate::state::types::config::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; 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<(OpenIdDelegationProvider, &'a OpenIdProviderAuthConfig), JwtFindProviderError> { // 1) Header sanity check decode_jwt_header(jwt).map_err(JwtFindProviderError::from)?; @@ -33,10 +33,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::delegation::types::provider::OpenIdDelegationProvider; 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::{OpenIdAuthProviders, OpenIdProviderAuthConfig}; use base64::engine::general_purpose::URL_SAFE_NO_PAD; use base64::Engine; use serde_json::json; @@ -51,11 +51,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 { + OpenIdDelegationProvider::Google, + OpenIdProviderAuthConfig { client_id: "client-123".into(), delegation: None, }, @@ -71,9 +71,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, OpenIdDelegationProvider::Google); assert_eq!(cfg.client_id, "client-123"); } @@ -84,8 +84,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, OpenIdDelegationProvider::Google); } #[test] @@ -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_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 +111,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 +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_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_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_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_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_provider(&provs, jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_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_provider(&provs, &jwt).unwrap_err(); + let err = unsafe_find_jwt_auth_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_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/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/mod.rs b/src/libs/auth/src/openid/mod.rs index bce9aa14e3..e962e11ab5 100644 --- a/src/libs/auth/src/openid/mod.rs +++ b/src/libs/auth/src/openid/mod.rs @@ -1,8 +1,7 @@ +pub mod automation; +pub mod delegation; mod impls; pub mod jwkset; pub mod jwt; pub mod types; mod utils; -mod verify; - -pub use verify::*; diff --git a/src/libs/auth/src/openid/types.rs b/src/libs/auth/src/openid/types.rs index b5b51b003b..cf6d15655d 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}; @@ -44,7 +9,8 @@ pub mod provider { )] pub enum OpenIdProvider { Google, - GitHub, + GitHubProxy, + GitHubActions, } #[derive(CandidType, Serialize, Deserialize, Clone)] diff --git a/src/libs/auth/src/openid/verify.rs b/src/libs/auth/src/openid/verify.rs deleted file mode 100644 index ca3b90cc2b..0000000000 --- a/src/libs/auth/src/openid/verify.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::openid::jwkset::{get_jwks, get_or_refresh_jwks}; -use crate::openid::jwt::types::cert::Jwks; -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::utils::build_nonce; -use crate::state::types::config::{OpenIdProviderClientId, OpenIdProviders}; -use crate::state::types::state::Salt; -use crate::strategies::AuthHeapStrategy; - -type VerifyOpenIdCredentialsResult = - Result<(OpenIdCredential, OpenIdProvider), VerifyOpenidCredentialsError>; - -pub async fn verify_openid_credentials_with_jwks_renewal( - jwt: &str, - salt: &Salt, - providers: &OpenIdProviders, - auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { - 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, &config.client_id, salt) -} - -pub fn verify_openid_credentials_with_cached_jwks( - jwt: &str, - salt: &Salt, - providers: &OpenIdProviders, - auth_heap: &impl AuthHeapStrategy, -) -> VerifyOpenIdCredentialsResult { - let (provider, config) = unsafe_find_jwt_provider(providers, jwt) - .map_err(VerifyOpenidCredentialsError::JwtFindProvider)?; - - let jwks = get_jwks(&provider, auth_heap).ok_or(VerifyOpenidCredentialsError::GetCachedJwks)?; - - verify_openid_credentials(jwt, &jwks, &provider, &config.client_id, salt) -} - -fn verify_openid_credentials( - jwt: &str, - jwks: &Jwks, - provider: &OpenIdProvider, - client_id: &OpenIdProviderClientId, - salt: &Salt, -) -> VerifyOpenIdCredentialsResult { - let nonce = build_nonce(salt); - - let token = verify_openid_jwt(jwt, provider.issuers(), client_id, &jwks.keys, &nonce) - .map_err(VerifyOpenidCredentialsError::JwtVerify)?; - - let credential = OpenIdCredential::from(token); - - Ok((credential, provider.clone())) -} 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..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::types::provider::OpenIdProvider; + 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}; @@ -72,7 +72,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 +87,18 @@ pub mod config { pub allowed_callers: Vec, } - pub type OpenIdProviders = BTreeMap; + 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/satellite.did b/src/libs/satellite/satellite.did index 7814ee0c99..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 { GitHub; Google }; -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/api/controllers.rs b/src/libs/satellite/src/api/controllers.rs index d11d23cdfb..76a2e049c2 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, AuthenticateControllerResult}; 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, +) -> AuthenticateControllerResult { + match args { + AuthenticateControllerArgs::OpenId(args) => openid_authenticate_controller(&args).await, + } +} diff --git a/src/libs/satellite/src/auth/delegation.rs b/src/libs/satellite/src/auth/delegation.rs index 78a9fc0a96..b259366fce 100644 --- a/src/libs/satellite/src/auth/delegation.rs +++ b/src/libs/satellite/src/auth/delegation.rs @@ -4,26 +4,33 @@ 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::state::types::config::OpenIdProviders; +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}; -pub type OpenIdPrepareDelegationResult = - Result<(PreparedDelegation, OpenIdProvider, OpenIdCredential), PrepareDelegationError>; +pub type OpenIdPrepareDelegationResult = Result< + ( + PreparedDelegation, + OpenIdDelegationProvider, + OpenIdCredential, + ), + PrepareDelegationError, +>; pub async fn openid_prepare_delegation( args: &OpenIdPrepareDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> OpenIdPrepareDelegationResult { - let (credential, provider) = match openid::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, @@ -38,14 +45,15 @@ pub async fn openid_prepare_delegation( pub fn openid_get_delegation( args: &OpenIdGetDelegationArgs, - providers: &OpenIdProviders, + providers: &OpenIdAuthProviders, ) -> GetDelegationResult { - let (credential, provider) = match openid::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 8e01852726..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::types::interface::OpenIdCredential; -use junobuild_auth::openid::types::provider::OpenIdProvider; +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; @@ -16,7 +16,7 @@ use junobuild_utils::decode_doc_data; pub fn register_user( public_key: &UserKey, - provider: &OpenIdProvider, + provider: &OpenIdDelegationProvider, credential: &OpenIdCredential, ) -> Result { let user_collection = COLLECTION_USER_KEY.to_string(); diff --git a/src/libs/satellite/src/controllers/authenticate.rs b/src/libs/satellite/src/controllers/authenticate.rs new file mode 100644 index 0000000000..0b4e8e8af1 --- /dev/null +++ b/src/libs/satellite/src/controllers/authenticate.rs @@ -0,0 +1,53 @@ +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, +}; +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, +) -> AuthenticateControllerResult { + match openid::automation::verify_openid_credentials_with_jwks_renewal( + &args.jwt, + &OpenIdProvider::GitHubActions, + &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)?; + + // 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..7db3c782bc --- /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 automation 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..d588dca7bc --- /dev/null +++ b/src/libs/satellite/src/controllers/impls.rs @@ -0,0 +1,11 @@ +use crate::controllers::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/controllers/mod.rs b/src/libs/satellite/src/controllers/mod.rs index 55c88cbf3d..a1406ae0cf 100644 --- a/src/libs/satellite/src/controllers/mod.rs +++ b/src/libs/satellite/src/controllers/mod.rs @@ -1 +1,7 @@ +mod authenticate; +mod constants; +mod impls; 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..5f8c4def76 --- /dev/null +++ b/src/libs/satellite/src/controllers/types.rs @@ -0,0 +1,32 @@ +use candid::{CandidType, Deserialize}; +use junobuild_auth::openid::automation::types::errors::VerifyOpenidAutomationCredentialsError; +use junobuild_shared::types::state::{ControllerId, Metadata}; +use serde::Serialize; + +#[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, +} + +#[derive(CandidType, Serialize, Deserialize, Clone)] +pub enum AutomationScope { + Write, + Submit, +} + +#[derive(CandidType, Serialize, Deserialize)] +pub enum AuthenticationControllerError { + VerifyOpenIdCredentials(VerifyOpenidAutomationCredentialsError), + RegisterController(String), +} + +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..db1474a5f0 100644 --- a/src/libs/satellite/src/lib.rs +++ b/src/libs/satellite/src/lib.rs @@ -18,17 +18,20 @@ 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::{ - 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}; 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,8 +66,6 @@ use memory::lifecycle; // ============================================================================================ // These types are made available for use in Serverless Functions. // ============================================================================================ -use crate::db::types::interface::SetDbConfig; -use junobuild_auth::state::types::interface::SetAuthenticationConfig; pub use sdk::core::*; pub use sdk::internal; @@ -232,6 +233,14 @@ 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 // --------------------------------------------------------- @@ -540,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, 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!(); 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 { diff --git a/src/libs/satellite/src/user/core/impls.rs b/src/libs/satellite/src/user/core/impls.rs index c3bf7e3440..c427d9ea69 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::delegation::types::interface::OpenIdCredential; +use junobuild_auth::openid::delegation::types::provider::OpenIdDelegationProvider; use junobuild_auth::profile::types::{OpenIdProfile, Validated}; use junobuild_utils::encode_doc_data; @@ -160,11 +160,11 @@ impl From<&OpenIdCredential> for OpenIdData { } } -impl From<&OpenIdProvider> for AuthProvider { - fn from(provider: &OpenIdProvider) -> Self { - match provider { - OpenIdProvider::Google => AuthProvider::Google, - OpenIdProvider::GitHub => AuthProvider::GitHub, +impl From<&OpenIdDelegationProvider> for AuthProvider { + fn from(auth_provider: &OpenIdDelegationProvider) -> Self { + match auth_provider { + OpenIdDelegationProvider::Google => AuthProvider::Google, + OpenIdDelegationProvider::GitHub => AuthProvider::GitHub, } } } @@ -175,6 +175,7 @@ mod tests { use crate::user::core::types::state::{ AuthProvider, OpenIdData, ProviderData, UserData, WebAuthnData, }; + use junobuild_auth::openid::types::provider::OpenIdProvider; // ------------------------ // WebAuthnData @@ -340,12 +341,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::GitHub), - AuthProvider::GitHub + AuthProvider::try_from(&OpenIdProvider::GitHubProxy), + Ok(AuthProvider::GitHub) )); + assert!(AuthProvider::try_from(&OpenIdProvider::GitHubActions).is_err()); } } 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/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/openid/scheduler.rs b/src/observatory/src/openid/scheduler.rs index be0a668c3a..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::GitHub] - .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::GitHub] { + for provider in [ + OpenIdProvider::Google, + OpenIdProvider::GitHubProxy, + OpenIdProvider::GitHubActions, + ] { schedule_certificate_update(provider, None); } } 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, + } +} diff --git a/src/satellite/satellite.did b/src/satellite/satellite.did index 8d8d97afff..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 { GitHub; Google }; -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 06751195a6..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 { GitHub; Google }; -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 9ee13bd161..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 = { GitHub: null } | { Google: 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 a6e62ab11a..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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 +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 13c363c35e..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,21 +185,21 @@ export const idlFactory = ({ IDL }) => { created_at: IDL.Nat64, version: IDL.Opt(IDL.Nat64) }); - const OpenIdProvider = IDL.Variant({ + 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 +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 5990623871..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 { GitHub; Google }; -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) -> ();