Skip to main content

owlauth_types/
control.rs

1use serde::{Deserialize, Serialize};
2use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
3use utoipa::{Modify, OpenApi, ToSchema};
4
5pub use crate::control_resources::*;
6use crate::health::HealthResponse;
7
8/// Side-effect-free origin-root descriptor used before credential selection.
9#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
10pub struct ServiceDescriptor {
11    /// Descriptor schema version.
12    pub schema_version: String,
13    /// Exact product identity.
14    pub product: String,
15    /// Stable public deployment identity.
16    pub instance_id: String,
17    /// Canonical same-origin Control API base with a trailing slash.
18    pub api_base_url: String,
19    /// Supported API versions.
20    pub api_versions: Vec<String>,
21    /// Credential class accepted by the selected product.
22    pub credential_class: String,
23    /// Canonical same-origin remote MCP URL when enabled.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub mcp_url: Option<String>,
26}
27
28#[utoipa::path(
29    get,
30    path = "/.well-known/owlauth",
31    responses(
32        (status = 200, description = "Public OwlAuth endpoint descriptor", body = ServiceDescriptor)
33    )
34)]
35#[doc(hidden)]
36#[must_use]
37pub fn get_service_descriptor() -> ServiceDescriptor {
38    ServiceDescriptor {
39        schema_version: "1".to_owned(),
40        product: "owlauth-server".to_owned(),
41        instance_id: "deployment-public-id".to_owned(),
42        api_base_url: "https://admin.example.com/v1/".to_owned(),
43        api_versions: vec!["v1".to_owned()],
44        credential_class: "operator-api-key".to_owned(),
45        mcp_url: None,
46    }
47}
48
49/// Bounded capabilities returned after Control operator authentication.
50#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
51pub struct SystemCapabilities {
52    /// Product identifier for this Control endpoint.
53    pub product: String,
54    /// Whether Project, Application, key, and provider provisioning is implemented.
55    pub provisioning: bool,
56    /// Whether Runtime configuration and signing-key publication readiness is implemented.
57    pub login_readiness: bool,
58    /// Whether end-user federated login, handoff, and session operations are implemented.
59    pub federated_project_auth: bool,
60}
61
62#[utoipa::path(
63    get,
64    path = "/v1/system",
65    responses(
66        (status = 200, description = "Authenticated deployment capabilities", body = SystemCapabilities),
67        (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge")))
68    ),
69    security(("operator_api_key" = []))
70)]
71#[doc(hidden)]
72#[must_use]
73pub fn get_system() -> SystemCapabilities {
74    SystemCapabilities {
75        product: "owlauth-server".to_owned(),
76        provisioning: true,
77        login_readiness: true,
78        federated_project_auth: crate::FEDERATED_PROJECT_AUTH_AVAILABLE,
79    }
80}
81
82#[derive(OpenApi)]
83#[openapi(
84    info(
85        title = "OwlAuth Control API",
86        description = "Deployment Control API"
87    ),
88    paths(
89        crate::health::get_liveness,
90        crate::health::get_readiness,
91        get_service_descriptor,
92        get_system,
93        crate::control_resources::list_projects,
94        crate::control_resources::create_project,
95        crate::control_resources::get_project,
96        crate::control_resources::update_project,
97        crate::control_resources::get_project_overview,
98        crate::control_resources::get_project_policy,
99        crate::control_resources::update_project_policy,
100        crate::control_resources::disable_project,
101        crate::control_resources::list_project_server_keys,
102        crate::control_resources::get_project_server_key,
103        crate::control_resources::create_project_server_key,
104        crate::control_resources::acknowledge_project_server_key_delivery,
105        crate::control_resources::revoke_project_server_key,
106        crate::control_resources::list_applications,
107        crate::control_resources::create_application,
108        crate::control_resources::get_application,
109        crate::control_resources::update_application,
110        crate::control_resources::replace_application_configuration,
111        crate::control_resources::disable_application,
112        crate::control_resources::list_webhook_endpoints,
113        crate::control_resources::create_webhook_endpoint,
114        crate::control_resources::get_webhook_endpoint,
115        crate::control_resources::update_webhook_endpoint,
116        crate::control_resources::test_webhook_endpoint,
117        crate::control_resources::activate_webhook_endpoint,
118        crate::control_resources::disable_webhook_endpoint,
119        crate::control_resources::prepare_webhook_secret_rotation,
120        crate::control_resources::activate_webhook_secret_rotation,
121        crate::control_resources::list_application_user_events,
122        crate::control_resources::list_webhook_deliveries,
123        crate::control_resources::get_webhook_delivery,
124        crate::control_resources::replay_webhook_delivery,
125        crate::control_resources::list_signing_keys,
126        crate::control_resources::rotate_signing_key,
127        crate::control_resources::revoke_signing_key,
128        crate::control_resources::get_provider_egress_policy,
129        crate::control_resources::update_provider_egress_policy,
130        crate::control_resources::preflight_oidc_provider,
131        crate::control_resources::preflight_named_provider,
132        crate::control_resources::list_providers,
133        crate::control_resources::create_provider,
134        crate::control_resources::update_provider,
135        crate::control_resources::replace_provider_secret,
136        crate::control_resources::reconcile_provider_secret_replacement,
137        crate::control_resources::abandon_provider_secret_replacement,
138        crate::control_resources::reconcile_provider,
139        crate::control_resources::disable_provider,
140        crate::control_resources::assign_provider,
141        crate::control_resources::unassign_provider,
142        crate::control_resources::get_email_method_policy,
143        crate::control_resources::update_email_method_policy,
144        crate::control_resources::list_email_assignments,
145        crate::control_resources::assign_email_method,
146        crate::control_resources::list_deployment_smtp_generations,
147        crate::control_resources::reconcile_deployment_smtp_generation,
148        crate::control_resources::disable_deployment_smtp_generation,
149        crate::control_resources::compromise_deployment_smtp_generation,
150        crate::control_resources::list_smtp_configurations,
151        crate::control_resources::create_smtp_configuration,
152        crate::control_resources::test_smtp_configuration,
153        crate::control_resources::get_smtp_test_operation,
154        crate::control_resources::activate_smtp_configuration,
155        crate::control_resources::disable_smtp_configuration,
156        crate::control_resources::compromise_smtp_configuration,
157        crate::control_resources::list_project_users,
158        crate::control_resources::lookup_project_user_by_email,
159        crate::control_resources::get_project_user,
160        crate::control_resources::list_project_user_identities,
161        crate::control_resources::disable_project_user,
162        crate::control_resources::enable_project_user,
163        crate::control_resources::list_project_user_sessions,
164        crate::control_resources::revoke_application_session,
165        crate::control_resources::revoke_browser_session,
166        crate::control_resources::list_managed_provider_connections,
167        crate::control_resources::synchronize_managed_provider_connection,
168        crate::control_resources::create_managed_reauthorization,
169        crate::control_resources::get_managed_reauthorization,
170        crate::control_resources::cancel_managed_reauthorization,
171        crate::control_resources::revoke_managed_provider_connection,
172        crate::control_resources::disconnect_managed_provider_connection,
173        crate::control_resources::create_identity_mutation_intent,
174        crate::control_resources::get_identity_mutation_intent,
175        crate::control_resources::cancel_identity_mutation_intent,
176        crate::control_resources::confirm_identity_mutation_intent
177    ),
178    components(schemas(
179        HealthResponse,
180        ServiceDescriptor,
181        SystemCapabilities,
182        ProblemDetails,
183        ProjectStatus,
184        ApplicationType,
185        ApplicationStatus,
186        SigningKeyState,
187        ProviderStatus,
188        crate::runtime::ProviderKind,
189        crate::runtime::JwkKeyType,
190        crate::runtime::JwkCurve,
191        crate::runtime::SigningAlgorithm,
192        crate::runtime::JwkUse,
193        crate::runtime::PublicJwk,
194        Project,
195        ProjectList,
196        ProjectOverviewSummary,
197        ProjectOverviewApplicationCounts,
198        ProjectOverviewProviderCounts,
199        ProjectOverviewUserCounts,
200        ProjectOverviewServerKeyCounts,
201        CreateProjectRequest,
202        UpdateProjectRequest,
203        ProjectPolicy,
204        UpdateProjectPolicyRequest,
205        ExpectedSecurityRevision,
206        ApplicationConfiguration,
207        Application,
208        ApplicationList,
209        CreateApplicationRequest,
210        UpdateApplicationRequest,
211        ReplaceApplicationConfigurationRequest,
212        WebhookEndpointStatus,
213        ApplicationUserEventType,
214        WebhookDeliveryState,
215        WebhookDeliveryOutcomeClass,
216        WebhookEndpoint,
217        WebhookEndpointList,
218        CreateWebhookEndpointRequest,
219        UpdateWebhookEndpointRequest,
220        ExpectedWebhookEndpointRevision,
221        PrepareWebhookSecretRotationRequest,
222        WebhookSecretPreparationStatus,
223        PreparedWebhookSecretRotation,
224        ActivateWebhookSecretRotationRequest,
225        ApplicationUserEvent,
226        ApplicationUserEventList,
227        WebhookDelivery,
228        WebhookDeliveryList,
229        ReplayWebhookDeliveryRequest,
230        SigningKey,
231        SigningKeyList,
232        ProjectServerKeyStatus,
233        ProjectServerKey,
234        ProjectServerKeyList,
235        CreateProjectServerKeyRequest,
236        CreateProjectServerKeyResponse,
237        AcknowledgeProjectServerKeyDeliveryRequest,
238        RevokeProjectServerKeyRequest,
239        RotateSigningKeyRequest,
240        KeyTransitionRequest,
241        Provider,
242        ProviderManagedProfileCapability,
243        ProviderList,
244        ProviderEgressMode,
245        ProviderEgressPolicy,
246        UpdateProviderEgressPolicyRequest,
247        OidcPreflightRequest,
248        OidcPreflightResult,
249        NamedProviderPreflightRequest,
250        NamedProviderPreflightResult,
251        ProviderCallbackGuidance,
252        ProviderConsentBehavior,
253        FixedProviderAuthorizationPolicy,
254        CreateProviderRequest,
255        UpdateProviderRequest,
256        ReplaceProviderSecretRequest,
257        ReconcileProviderRequest,
258        ReconcileProviderSecretReplacementRequest,
259        ProviderRevisionRequest,
260        ProviderAssignmentRequest,
261        SmtpTlsMode,
262        SmtpGenerationStatus,
263        EmailMethodPolicy,
264        UpdateEmailMethodPolicyRequest,
265        EmailAssignmentRequest,
266        EmailAssignment,
267        EmailAssignmentList,
268        SmtpConfiguration,
269        SmtpConfigurationList,
270        DeploymentSmtpGeneration,
271        DeploymentSmtpGenerationList,
272        ReconcileDeploymentSmtpRequest,
273        CreateSmtpConfigurationRequest,
274        SmtpRevisionRequest,
275        TestSmtpConfigurationRequest,
276        ManagedProviderConnectionState,
277        ManagedProviderConnection,
278        ManagedProviderConnectionList,
279        ManagedProviderConnectionActionRequest,
280        ManagedReauthorizationStatus,
281        ManagedReauthorization,
282        CreateManagedReauthorizationRequest,
283        CreateManagedReauthorizationResponse,
284        CancelManagedReauthorizationRequest,
285        crate::runtime::IdentityKind,
286        crate::runtime::IdentityMutationMethodKind,
287        IdentityMutationUserTarget,
288        ExistingIdentityReference,
289        IdentityMutationProofAuthority,
290        UnlinkPrimarySourceDisposition,
291        MergePrimarySource,
292        MergeSessionsDisposition,
293        MergeBindingsDisposition,
294        CreateIdentityMutationIntentRequest,
295        IdentityMutationOperationKind,
296        IdentityMutationIntentStatus,
297        IdentityMutationProofRole,
298        IdentityMutationProofSlot,
299        IdentityMutationIntent,
300        CreateIdentityMutationIntentResponse,
301        CancelIdentityMutationIntentRequest,
302        LinkIdentityMutationConfirmation,
303        UnlinkIdentityMutationConfirmation,
304        MergeIdentityMutationConfirmation,
305        ConfirmIdentityMutationIntentRequest,
306        ProjectUserStatus,
307        ProjectUserSort,
308        ProjectUserIdentityFilter,
309        ManagedSessionStatus,
310        ProjectUser,
311        ProjectUserList,
312        ProjectUserEmailLookupRequest,
313        ProjectUserLookup,
314        ProjectUserIdentityStatus,
315        RedactedEmailMarker,
316        ProjectUserIdentityPresentation,
317        ProjectUserIdentity,
318        ProjectUserIdentityList,
319        ApplicationSession,
320        BrowserSession,
321        ProjectUserSessions,
322        ExpectedSessionRevision
323    )),
324    modifiers(&ControlSecurity)
325)]
326struct ControlApiDoc;
327
328struct ControlSecurity;
329
330impl Modify for ControlSecurity {
331    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
332        openapi
333            .components
334            .get_or_insert_default()
335            .add_security_scheme(
336                "operator_api_key",
337                SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
338            );
339    }
340}
341
342/// Generates the complete Control-plane `OpenAPI` document.
343#[must_use]
344pub fn openapi() -> utoipa::openapi::OpenApi {
345    let mut document = ControlApiDoc::openapi();
346    crate::add_response_to_operations(&mut document, "408", |_| {
347        crate::json_error_response(
348            "The request exceeded the Control listener time budget",
349            "ProblemDetails",
350            "application/problem+json",
351        )
352    });
353    document
354}
355
356#[cfg(test)]
357mod identity_inventory_tests {
358    use super::*;
359
360    #[test]
361    fn application_sync_contract_is_typed_bounded_and_control_only() {
362        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
363        for path in [
364            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints",
365            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}",
366            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/test",
367            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/activate",
368            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/secret-rotations",
369            "/v1/projects/{project_id}/applications/{application_id}/user-events",
370            "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries",
371            "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries/{delivery_id}/replay",
372        ] {
373            assert!(document["paths"][path].is_object(), "missing path: {path}");
374        }
375        assert_eq!(
376            document["components"]["schemas"]["CreateWebhookEndpointRequest"]["properties"]["secret"]
377                ["writeOnly"],
378            true
379        );
380        assert_eq!(
381            document["components"]["schemas"]["WebhookEndpointList"]["properties"]["items"]["maxItems"],
382            100
383        );
384        let endpoint = document["components"]["schemas"]["WebhookEndpoint"].to_string();
385        assert!(!endpoint.contains("secret_ref"));
386        assert!(!endpoint.contains("request_fingerprint"));
387        let runtime =
388            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
389        assert!(
390            runtime["paths"]
391                ["/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints"]
392                .is_null()
393        );
394    }
395
396    #[test]
397    fn email_assignment_read_model_is_bounded_and_control_only() {
398        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
399        let path = "/v1/projects/{project_id}/email-method/assignments";
400        assert!(document["paths"][path]["get"].is_object());
401        assert_eq!(
402            document["components"]["schemas"]["EmailAssignmentList"]["properties"]["items"]["maxItems"],
403            100
404        );
405        assert_eq!(
406            document["components"]["schemas"]["EmailAssignment"]["properties"]["security_revision"]
407                ["minimum"],
408            1
409        );
410        let runtime =
411            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
412        assert!(runtime["paths"].get(path).is_none());
413    }
414
415    #[test]
416    fn identity_inventory_contract_is_bounded_redacted_and_control_only() {
417        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
418        assert!(
419            document["paths"]
420                .get("/v1/projects/{project_id}/users/{user_id}/identities")
421                .is_some()
422        );
423        assert_eq!(
424            document["components"]["schemas"]["ProjectUserIdentityList"]["properties"]["items"]["maxItems"],
425            100
426        );
427        assert_eq!(
428            document["components"]["schemas"]["ProjectUserLookup"]["required"],
429            serde_json::json!(["user"])
430        );
431        let identity = document["components"]["schemas"]["ProjectUserIdentity"].to_string();
432        for forbidden in [
433            "issuer",
434            "subject",
435            "ciphertext",
436            "digest",
437            "alias",
438            "client_id",
439            "secret",
440            "credential",
441            "receipt",
442            "evidence",
443            "raw_email",
444        ] {
445            assert!(
446                !identity.contains(forbidden),
447                "safe inventory schema exposed forbidden field {forbidden}"
448            );
449        }
450        let runtime =
451            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
452        assert!(
453            runtime["paths"]
454                .get("/v1/projects/{project_id}/users/{user_id}/identities")
455                .is_none()
456        );
457    }
458}