diff --git a/Cargo.lock b/Cargo.lock index 3011b8f..7b28a77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "admin-api" -version = "0.1.0" +version = "0.3.0" dependencies = [ "argon2", "async-trait", @@ -384,7 +384,7 @@ dependencies = [ [[package]] name = "crank-adapter-rest" -version = "0.1.0" +version = "0.3.0" dependencies = [ "async-trait", "axum", @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "crank-community-auth" -version = "0.1.0" +version = "0.3.0" dependencies = [ "argon2", "async-trait", @@ -417,7 +417,7 @@ dependencies = [ [[package]] name = "crank-community-mcp" -version = "0.1.0" +version = "0.3.0" dependencies = [ "async-trait", "axum", @@ -440,7 +440,7 @@ dependencies = [ [[package]] name = "crank-core" -version = "0.1.0" +version = "0.3.0" dependencies = [ "async-trait", "serde", @@ -453,7 +453,7 @@ dependencies = [ [[package]] name = "crank-mapping" -version = "0.1.0" +version = "0.3.0" dependencies = [ "crank-core", "serde", @@ -464,7 +464,7 @@ dependencies = [ [[package]] name = "crank-registry" -version = "0.1.0" +version = "0.3.0" dependencies = [ "crank-core", "crank-mapping", @@ -480,7 +480,7 @@ dependencies = [ [[package]] name = "crank-runtime" -version = "0.1.0" +version = "0.3.0" dependencies = [ "aes-gcm", "async-trait", @@ -505,7 +505,7 @@ dependencies = [ [[package]] name = "crank-schema" -version = "0.1.0" +version = "0.3.0" dependencies = [ "crank-core", "serde", @@ -1273,7 +1273,7 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "mcp-server" -version = "0.1.0" +version = "0.3.0" dependencies = [ "async-trait", "axum", diff --git a/Cargo.toml b/Cargo.toml index 3698156..6e2951c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ resolver = "3" edition = "2024" license = "MIT" rust-version = "1.85" -version = "0.2.0" +version = "0.3.0" [workspace.dependencies] aes-gcm = "0.10" diff --git a/TASKS.md b/TASKS.md index 1619c2d..92bbb6a 100644 --- a/TASKS.md +++ b/TASKS.md @@ -163,4 +163,5 @@ Progress: - Phase 1 / task `1.5`: создан crate `crank-community-mcp`; в него вынесены `build_app`, `catalog`, `jsonrpc`, `session` и `auth` из `apps/mcp-server`, а `apps/mcp-server/src/main.rs` стал thin launcher с env parsing и DI wiring - Phase 1 / task `1.6`: Community test surface уже приведен к честному baseline — `test = false` удален ранее, premium-only tests вычищены, `just verify` проходит на текущем составе workspace - Phase 1 / task `1.7`: workspace version bumped to `0.2.0`; release gate пройден (`just verify`, `npm run build`, `npm run e2e`), release commit и tag `v0.2.0` подготовлены в `crank-community` + - Phase 2 dependency slice: `IdentityProvider` seam widened to cover `SSO/TOTP` with default `NotSupportedForProvider` methods and shared public DTOs for authorize/callback/two-factor flows; workspace version bumped to `0.3.0` for downstream enterprise consumption - backward-compatible alias `CommunityMachineCredentialVerifier` сохранен, поведение Community не изменено diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index ef99583..f0f4974 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -912,6 +912,9 @@ impl AdminService { .await { Ok(LoginOutcome::Authenticated(identity)) => Ok(identity), + Ok(LoginOutcome::TwoFactorRequired(_)) => Err(ApiError::internal( + "two-factor login flow is not configured for this service", + )), Err(error) => Err(map_identity_error(error)), }; } diff --git a/crates/crank-core/src/ext/auth.rs b/crates/crank-core/src/ext/auth.rs index 708a3a0..dc231dc 100644 --- a/crates/crank-core/src/ext/auth.rs +++ b/crates/crank-core/src/ext/auth.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; +use time::OffsetDateTime; use crate::{ IssueAgentTokenRequest, IssueOneTimeAgentTokenRequest, IssuedAgentTokenResponse, @@ -125,8 +126,64 @@ pub struct LoginPayload { pub password: String, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SsoAuthorizeRequest { + pub workspace_id: WorkspaceId, + pub provider_id: String, + pub base_origin: String, + pub return_to: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SsoAuthorizeRedirect { + pub authorization_url: String, + pub state: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SsoCallbackRequest { + pub workspace_id: WorkspaceId, + pub provider_id: String, + pub code: String, + pub base_origin: String, + pub return_to: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TwoFactorPendingIdentity { + pub user_id: UserId, + pub workspace_id: Option, + pub return_to: Option, + pub expires_at: OffsetDateTime, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TwoFactorStatus { + pub enabled: bool, + pub pending_setup: bool, + pub recovery_codes_remaining: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TwoFactorSetup { + pub secret: String, + pub otpauth_url: String, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TwoFactorActivation { + pub recovery_codes: Vec, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub struct TwoFactorChallenge { + pub code: Option, + pub recovery_code: Option, +} + pub enum LoginOutcome { Authenticated(AuthenticatedIdentity), + TwoFactorRequired(TwoFactorPendingIdentity), } #[async_trait] @@ -136,6 +193,59 @@ pub trait IdentityProvider: Send + Sync { fn kind(&self) -> IdentityProviderKind; async fn login_password(&self, payload: LoginPayload) -> Result; + + async fn begin_sso( + &self, + _request: SsoAuthorizeRequest, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } + + async fn complete_sso( + &self, + _request: SsoCallbackRequest, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } + + async fn get_two_factor_status( + &self, + _user_id: &UserId, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } + + async fn begin_two_factor_setup( + &self, + _user_id: &UserId, + _email: &str, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } + + async fn activate_two_factor( + &self, + _user_id: &UserId, + _code: &str, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } + + async fn disable_two_factor( + &self, + _user_id: &UserId, + _challenge: TwoFactorChallenge, + ) -> Result<(), IdentityError> { + Err(IdentityError::NotSupportedForProvider) + } + + async fn verify_two_factor_login( + &self, + _pending: &TwoFactorPendingIdentity, + _challenge: TwoFactorChallenge, + ) -> Result { + Err(IdentityError::NotSupportedForProvider) + } } pub type SharedIdentityProvider = Arc; diff --git a/crates/crank-core/src/lib.rs b/crates/crank-core/src/lib.rs index cd11542..e7b21c9 100644 --- a/crates/crank-core/src/lib.rs +++ b/crates/crank-core/src/lib.rs @@ -43,7 +43,9 @@ pub use ext::auth::{ AuthenticatedIdentity, IdentityError, IdentityProvider, IdentityProviderKind, LoginOutcome, LoginPayload, MachineCredentialVerifier, MachineCredentialVerifierError, MachineTokenIssuer, NoMachineTokenIssuer, SharedIdentityProvider, SharedMachineCredentialVerifier, - SharedMachineTokenIssuer, TokenIssuerActor, TokenIssuerError, VerifiedMachineCredential, + SharedMachineTokenIssuer, SsoAuthorizeRedirect, SsoAuthorizeRequest, SsoCallbackRequest, + TokenIssuerActor, TokenIssuerError, TwoFactorActivation, TwoFactorChallenge, + TwoFactorPendingIdentity, TwoFactorSetup, TwoFactorStatus, VerifiedMachineCredential, }; pub use ext::capability::{CapabilityProfile, CommunityCapabilityProfile}; pub use ext::protocol::{