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::enable_project,
102        crate::control_resources::delete_project,
103        crate::control_resources::list_project_server_keys,
104        crate::control_resources::get_project_server_key,
105        crate::control_resources::create_project_server_key,
106        crate::control_resources::acknowledge_project_server_key_delivery,
107        crate::control_resources::revoke_project_server_key,
108        crate::control_resources::list_applications,
109        crate::control_resources::create_application,
110        crate::control_resources::get_application,
111        crate::control_resources::update_application,
112        crate::control_resources::replace_application_configuration,
113        crate::control_resources::disable_application,
114        crate::control_resources::list_webhook_endpoints,
115        crate::control_resources::create_webhook_endpoint,
116        crate::control_resources::get_webhook_endpoint,
117        crate::control_resources::update_webhook_endpoint,
118        crate::control_resources::test_webhook_endpoint,
119        crate::control_resources::activate_webhook_endpoint,
120        crate::control_resources::disable_webhook_endpoint,
121        crate::control_resources::prepare_webhook_secret_rotation,
122        crate::control_resources::activate_webhook_secret_rotation,
123        crate::control_resources::list_application_user_events,
124        crate::control_resources::list_webhook_deliveries,
125        crate::control_resources::get_webhook_delivery,
126        crate::control_resources::replay_webhook_delivery,
127        crate::control_resources::list_signing_keys,
128        crate::control_resources::rotate_signing_key,
129        crate::control_resources::revoke_signing_key,
130        crate::control_resources::get_provider_egress_policy,
131        crate::control_resources::update_provider_egress_policy,
132        crate::control_resources::preflight_oidc_provider,
133        crate::control_resources::preflight_named_provider,
134        crate::control_resources::list_providers,
135        crate::control_resources::create_provider,
136        crate::control_resources::update_provider,
137        crate::control_resources::replace_provider_secret,
138        crate::control_resources::reconcile_provider_secret_replacement,
139        crate::control_resources::abandon_provider_secret_replacement,
140        crate::control_resources::reconcile_provider,
141        crate::control_resources::disable_provider,
142        crate::control_resources::assign_provider,
143        crate::control_resources::unassign_provider,
144        crate::control_resources::get_email_method_policy,
145        crate::control_resources::update_email_method_policy,
146        crate::control_resources::list_email_assignments,
147        crate::control_resources::assign_email_method,
148        crate::control_resources::list_deployment_smtp_generations,
149        crate::control_resources::reconcile_deployment_smtp_generation,
150        crate::control_resources::disable_deployment_smtp_generation,
151        crate::control_resources::compromise_deployment_smtp_generation,
152        crate::control_resources::list_smtp_configurations,
153        crate::control_resources::create_smtp_configuration,
154        crate::control_resources::test_smtp_configuration,
155        crate::control_resources::get_smtp_test_operation,
156        crate::control_resources::activate_smtp_configuration,
157        crate::control_resources::disable_smtp_configuration,
158        crate::control_resources::compromise_smtp_configuration,
159        crate::control_resources::list_project_users,
160        crate::control_resources::lookup_project_user_by_email,
161        crate::control_resources::get_project_user,
162        crate::control_resources::list_project_user_identities,
163        crate::control_resources::disable_project_user,
164        crate::control_resources::enable_project_user,
165        crate::control_resources::list_project_user_sessions,
166        crate::control_resources::revoke_application_session,
167        crate::control_resources::revoke_browser_session,
168        crate::control_resources::list_managed_provider_connections,
169        crate::control_resources::synchronize_managed_provider_connection,
170        crate::control_resources::create_managed_reauthorization,
171        crate::control_resources::get_managed_reauthorization,
172        crate::control_resources::cancel_managed_reauthorization,
173        crate::control_resources::revoke_managed_provider_connection,
174        crate::control_resources::disconnect_managed_provider_connection,
175        crate::control_resources::create_identity_mutation_intent,
176        crate::control_resources::get_identity_mutation_intent,
177        crate::control_resources::cancel_identity_mutation_intent,
178        crate::control_resources::confirm_identity_mutation_intent
179    ),
180    components(schemas(
181        HealthResponse,
182        ServiceDescriptor,
183        SystemCapabilities,
184        ProblemDetails,
185        ProjectStatus,
186        ApplicationType,
187        ApplicationStatus,
188        SigningKeyState,
189        ProviderStatus,
190        crate::runtime::ProviderKind,
191        crate::runtime::JwkKeyType,
192        crate::runtime::JwkCurve,
193        crate::runtime::SigningAlgorithm,
194        crate::runtime::JwkUse,
195        crate::runtime::PublicJwk,
196        Project,
197        ProjectList,
198        ProjectOverviewSummary,
199        ProjectOverviewApplicationCounts,
200        ProjectOverviewProviderCounts,
201        ProjectOverviewUserCounts,
202        ProjectOverviewServerKeyCounts,
203        CreateProjectRequest,
204        UpdateProjectRequest,
205        ProjectPolicy,
206        UpdateProjectPolicyRequest,
207        ExpectedSecurityRevision,
208        ApplicationConfiguration,
209        Application,
210        ApplicationList,
211        CreateApplicationRequest,
212        UpdateApplicationRequest,
213        ReplaceApplicationConfigurationRequest,
214        WebhookEndpointStatus,
215        ApplicationUserEventType,
216        WebhookDeliveryState,
217        WebhookDeliveryOutcomeClass,
218        WebhookEndpoint,
219        WebhookEndpointList,
220        CreateWebhookEndpointRequest,
221        UpdateWebhookEndpointRequest,
222        ExpectedWebhookEndpointRevision,
223        PrepareWebhookSecretRotationRequest,
224        WebhookSecretPreparationStatus,
225        PreparedWebhookSecretRotation,
226        ActivateWebhookSecretRotationRequest,
227        ApplicationUserEvent,
228        ApplicationUserEventList,
229        WebhookDelivery,
230        WebhookDeliveryList,
231        ReplayWebhookDeliveryRequest,
232        SigningKey,
233        SigningKeyList,
234        ProjectServerKeyStatus,
235        ProjectServerKey,
236        ProjectServerKeyList,
237        CreateProjectServerKeyRequest,
238        CreateProjectServerKeyResponse,
239        AcknowledgeProjectServerKeyDeliveryRequest,
240        RevokeProjectServerKeyRequest,
241        RotateSigningKeyRequest,
242        KeyTransitionRequest,
243        Provider,
244        ProviderManagedProfileCapability,
245        ProviderList,
246        ProviderEgressMode,
247        ProviderEgressPolicy,
248        UpdateProviderEgressPolicyRequest,
249        OidcPreflightRequest,
250        OidcPreflightResult,
251        NamedProviderPreflightRequest,
252        NamedProviderPreflightResult,
253        ProviderCallbackGuidance,
254        ProviderConsentBehavior,
255        FixedProviderAuthorizationPolicy,
256        CreateProviderRequest,
257        UpdateProviderRequest,
258        ReplaceProviderSecretRequest,
259        ReconcileProviderRequest,
260        ReconcileProviderSecretReplacementRequest,
261        ProviderRevisionRequest,
262        ProviderAssignmentRequest,
263        SmtpTlsMode,
264        SmtpGenerationStatus,
265        EmailMethodPolicy,
266        UpdateEmailMethodPolicyRequest,
267        EmailAssignmentRequest,
268        EmailAssignment,
269        EmailAssignmentList,
270        SmtpConfiguration,
271        SmtpConfigurationList,
272        DeploymentSmtpGeneration,
273        DeploymentSmtpGenerationList,
274        ReconcileDeploymentSmtpRequest,
275        CreateSmtpConfigurationRequest,
276        SmtpRevisionRequest,
277        TestSmtpConfigurationRequest,
278        ManagedProviderConnectionState,
279        ManagedProviderConnection,
280        ManagedProviderConnectionList,
281        ManagedProviderConnectionActionRequest,
282        ManagedReauthorizationStatus,
283        ManagedReauthorization,
284        CreateManagedReauthorizationRequest,
285        CreateManagedReauthorizationResponse,
286        CancelManagedReauthorizationRequest,
287        crate::runtime::IdentityKind,
288        crate::runtime::IdentityMutationMethodKind,
289        IdentityMutationUserTarget,
290        ExistingIdentityReference,
291        IdentityMutationProofAuthority,
292        UnlinkPrimarySourceDisposition,
293        MergePrimarySource,
294        MergeSessionsDisposition,
295        MergeBindingsDisposition,
296        CreateIdentityMutationIntentRequest,
297        IdentityMutationOperationKind,
298        IdentityMutationIntentStatus,
299        IdentityMutationProofRole,
300        IdentityMutationProofSlot,
301        IdentityMutationIntent,
302        CreateIdentityMutationIntentResponse,
303        CancelIdentityMutationIntentRequest,
304        LinkIdentityMutationConfirmation,
305        UnlinkIdentityMutationConfirmation,
306        MergeIdentityMutationConfirmation,
307        ConfirmIdentityMutationIntentRequest,
308        ProjectUserStatus,
309        ProjectUserSort,
310        ProjectUserIdentityFilter,
311        ManagedSessionStatus,
312        ProjectUser,
313        ProjectUserList,
314        ProjectUserEmailLookupRequest,
315        ProjectUserLookup,
316        ProjectUserIdentityStatus,
317        RedactedEmailMarker,
318        ProjectUserIdentityPresentation,
319        ProjectUserIdentity,
320        ProjectUserIdentityList,
321        ApplicationSession,
322        BrowserSession,
323        ProjectUserSessions,
324        ExpectedSessionRevision
325    )),
326    modifiers(&ControlSecurity)
327)]
328struct ControlApiDoc;
329
330struct ControlSecurity;
331
332impl Modify for ControlSecurity {
333    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
334        openapi
335            .components
336            .get_or_insert_default()
337            .add_security_scheme(
338                "operator_api_key",
339                SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
340            );
341    }
342}
343
344/// Generates the complete Control-plane `OpenAPI` document.
345#[must_use]
346pub fn openapi() -> utoipa::openapi::OpenApi {
347    let mut document = ControlApiDoc::openapi();
348    crate::add_response_to_operations(&mut document, "408", |_| {
349        crate::json_error_response(
350            "The request exceeded the Control listener time budget",
351            "ProblemDetails",
352            "application/problem+json",
353        )
354    });
355    document
356}
357
358#[cfg(test)]
359mod identity_inventory_tests {
360    use super::*;
361
362    #[test]
363    fn application_sync_contract_is_typed_bounded_and_control_only() {
364        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
365        for path in [
366            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints",
367            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}",
368            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/test",
369            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/activate",
370            "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/secret-rotations",
371            "/v1/projects/{project_id}/applications/{application_id}/user-events",
372            "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries",
373            "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries/{delivery_id}/replay",
374        ] {
375            assert!(document["paths"][path].is_object(), "missing path: {path}");
376        }
377        assert_eq!(
378            document["components"]["schemas"]["CreateWebhookEndpointRequest"]["properties"]["secret"]
379                ["writeOnly"],
380            true
381        );
382        assert_eq!(
383            document["components"]["schemas"]["WebhookEndpointList"]["properties"]["items"]["maxItems"],
384            100
385        );
386        let endpoint = document["components"]["schemas"]["WebhookEndpoint"].to_string();
387        assert!(!endpoint.contains("secret_ref"));
388        assert!(!endpoint.contains("request_fingerprint"));
389        let runtime =
390            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
391        assert!(
392            runtime["paths"]
393                ["/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints"]
394                .is_null()
395        );
396    }
397
398    #[test]
399    fn email_assignment_read_model_is_bounded_and_control_only() {
400        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
401        let path = "/v1/projects/{project_id}/email-method/assignments";
402        assert!(document["paths"][path]["get"].is_object());
403        assert_eq!(
404            document["components"]["schemas"]["EmailAssignmentList"]["properties"]["items"]["maxItems"],
405            100
406        );
407        assert_eq!(
408            document["components"]["schemas"]["EmailAssignment"]["properties"]["security_revision"]
409                ["minimum"],
410            1
411        );
412        let runtime =
413            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
414        assert!(runtime["paths"].get(path).is_none());
415    }
416
417    #[test]
418    fn identity_inventory_contract_is_bounded_redacted_and_control_only() {
419        let document = serde_json::to_value(openapi()).expect("serialize Control OpenAPI");
420        assert!(
421            document["paths"]
422                .get("/v1/projects/{project_id}/users/{user_id}/identities")
423                .is_some()
424        );
425        assert_eq!(
426            document["components"]["schemas"]["ProjectUserIdentityList"]["properties"]["items"]["maxItems"],
427            100
428        );
429        assert_eq!(
430            document["components"]["schemas"]["ProjectUserLookup"]["required"],
431            serde_json::json!(["user"])
432        );
433        let identity = document["components"]["schemas"]["ProjectUserIdentity"].to_string();
434        for forbidden in [
435            "issuer",
436            "subject",
437            "ciphertext",
438            "digest",
439            "alias",
440            "client_id",
441            "secret",
442            "credential",
443            "receipt",
444            "evidence",
445            "raw_email",
446        ] {
447            assert!(
448                !identity.contains(forbidden),
449                "safe inventory schema exposed forbidden field {forbidden}"
450            );
451        }
452        let runtime =
453            serde_json::to_value(crate::runtime::openapi()).expect("serialize Runtime OpenAPI");
454        assert!(
455            runtime["paths"]
456                .get("/v1/projects/{project_id}/users/{user_id}/identities")
457                .is_none()
458        );
459    }
460}