1use serde::{Deserialize, Deserializer, Serialize};
2use utoipa::ToSchema;
3
4pub use crate::runtime::{IdentityKind, IdentityMutationMethodKind};
5use crate::runtime::{ProviderKind, PublicJwk, SigningAlgorithm};
6
7fn deserialize_required_nullable_string<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
8where
9 D: Deserializer<'de>,
10{
11 Option::<String>::deserialize(deserializer)
12}
13
14fn deserialize_required_nullable_project_server_key<'de, D>(
15 deserializer: D,
16) -> Result<Option<ProjectServerKey>, D::Error>
17where
18 D: Deserializer<'de>,
19{
20 Option::<ProjectServerKey>::deserialize(deserializer)
21}
22
23fn deserialize_required_nullable_project_user<'de, D>(
24 deserializer: D,
25) -> Result<Option<ProjectUser>, D::Error>
26where
27 D: Deserializer<'de>,
28{
29 Option::<ProjectUser>::deserialize(deserializer)
30}
31
32#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
33#[serde(rename_all = "snake_case")]
34pub enum ProjectStatus {
35 Active,
36 Disabled,
37}
38
39#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
40#[serde(rename_all = "snake_case")]
41pub enum ApplicationType {
42 Web,
43 Native,
44}
45
46#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
47#[serde(rename_all = "snake_case")]
48pub enum ApplicationStatus {
49 Active,
50 Disabled,
51}
52
53#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
54#[serde(rename_all = "snake_case")]
55pub enum SigningKeyState {
56 Provisioning,
57 Published,
58 Active,
59 Retiring,
60 Retired,
61 Revoked,
62 Abandoned,
63}
64
65#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
66#[serde(rename_all = "snake_case")]
67pub enum ProviderStatus {
68 Provisioning,
69 Active,
70 Disabled,
71}
72
73#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
74pub struct ProblemDetails {
75 #[serde(rename = "type")]
76 pub type_uri: String,
77 pub code: String,
78 pub title: String,
79 pub status: u16,
80 pub detail: String,
81 pub request_id: String,
82}
83
84#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
85pub struct Project {
86 pub id: String,
87 pub public_id: String,
88 #[schema(max_length = 128)]
89 pub display_name: String,
90 #[schema(max_length = 256)]
91 pub belongs_to: Option<String>,
92 pub status: ProjectStatus,
93 pub metadata_revision: i64,
94 pub security_revision: i64,
95}
96
97#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
98pub struct ProjectList {
99 #[schema(max_items = 100)]
100 pub items: Vec<Project>,
101}
102
103#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
104#[serde(deny_unknown_fields)]
105pub struct ProjectOverviewSummary {
106 pub project_id: String,
107 pub applications: ProjectOverviewApplicationCounts,
108 pub providers: ProjectOverviewProviderCounts,
109 pub users: ProjectOverviewUserCounts,
110 pub project_server_keys: ProjectOverviewServerKeyCounts,
111}
112
113#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
114#[serde(deny_unknown_fields)]
115pub struct ProjectOverviewApplicationCounts {
116 #[schema(minimum = 0)]
117 pub total: u64,
118 #[schema(minimum = 0)]
119 pub active: u64,
120 #[schema(minimum = 0)]
121 pub configured: u64,
122}
123
124#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
125#[serde(deny_unknown_fields)]
126pub struct ProjectOverviewProviderCounts {
127 #[schema(minimum = 0)]
128 pub total: u64,
129 #[schema(minimum = 0)]
130 pub active: u64,
131 #[schema(minimum = 0)]
132 pub active_assignments: u64,
133}
134
135#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
136#[serde(deny_unknown_fields)]
137pub struct ProjectOverviewUserCounts {
138 #[schema(minimum = 0)]
139 pub total: u64,
140 #[schema(minimum = 0)]
141 pub active: u64,
142 #[schema(minimum = 0)]
143 pub disabled: u64,
144 #[schema(minimum = 0)]
145 pub merged: u64,
146}
147
148#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
149#[serde(deny_unknown_fields)]
150pub struct ProjectOverviewServerKeyCounts {
151 #[schema(minimum = 0)]
152 pub total: u64,
153 #[schema(minimum = 0)]
154 pub active: u64,
155 #[schema(minimum = 0)]
156 pub revoked: u64,
157}
158
159#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
160#[serde(deny_unknown_fields)]
161pub struct CreateProjectRequest {
162 #[schema(min_length = 1, max_length = 128)]
163 pub display_name: String,
164 #[schema(max_length = 256)]
165 pub belongs_to: Option<String>,
166}
167
168#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
169#[serde(deny_unknown_fields)]
170pub struct UpdateProjectRequest {
171 #[schema(min_length = 1, max_length = 128)]
172 pub display_name: String,
173 #[schema(max_length = 256)]
174 pub belongs_to: Option<String>,
175 #[schema(minimum = 1)]
176 pub expected_metadata_revision: i64,
177}
178
179#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
180pub struct ProjectPolicy {
181 pub project_id: String,
182 #[schema(minimum = 60, maximum = 3600)]
183 pub access_token_lifetime_seconds: i32,
184 pub browser_session_reuse: bool,
185 #[schema(minimum = 1)]
186 pub claims_revision: i64,
187 #[schema(minimum = 1)]
188 pub session_revision: i64,
189}
190
191#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
192#[serde(deny_unknown_fields)]
193pub struct UpdateProjectPolicyRequest {
194 #[schema(minimum = 60, maximum = 3600)]
195 pub access_token_lifetime_seconds: i32,
196 pub browser_session_reuse: bool,
197 #[schema(minimum = 1)]
198 pub expected_claims_revision: i64,
199 #[schema(minimum = 1)]
200 pub expected_session_revision: i64,
201}
202
203#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
204#[serde(deny_unknown_fields)]
205pub struct ExpectedSecurityRevision {
206 #[schema(minimum = 1)]
207 pub expected_security_revision: i64,
208}
209
210#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
211pub struct ApplicationConfiguration {
212 #[schema(max_items = 50)]
213 pub redirect_uris: Vec<String>,
214 #[schema(max_items = 50)]
215 pub allowed_origins: Vec<String>,
216 #[schema(max_items = 50)]
217 pub publishable_keys: Vec<String>,
218}
219
220#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
221pub struct Application {
222 pub id: String,
223 pub project_id: String,
224 pub public_id: String,
225 #[schema(max_length = 128)]
226 pub display_name: String,
227 pub application_type: ApplicationType,
228 pub status: ApplicationStatus,
229 pub metadata_revision: i64,
230 pub security_revision: i64,
231 pub configuration: ApplicationConfiguration,
232}
233
234#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
235pub struct ApplicationList {
236 #[schema(max_items = 100)]
237 pub items: Vec<Application>,
238}
239
240#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
241#[serde(deny_unknown_fields)]
242pub struct CreateApplicationRequest {
243 #[schema(min_length = 1, max_length = 128)]
244 pub display_name: String,
245 pub application_type: ApplicationType,
246}
247
248#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
249#[serde(deny_unknown_fields)]
250pub struct UpdateApplicationRequest {
251 #[schema(min_length = 1, max_length = 128)]
252 pub display_name: String,
253 #[schema(minimum = 1)]
254 pub expected_metadata_revision: i64,
255}
256
257#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
258#[serde(deny_unknown_fields)]
259pub struct ReplaceApplicationConfigurationRequest {
260 #[schema(max_items = 50)]
261 pub redirect_uris: Vec<String>,
262 #[schema(max_items = 50)]
263 pub allowed_origins: Vec<String>,
264 #[schema(minimum = 1)]
265 pub expected_security_revision: i64,
266}
267
268#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
269#[serde(rename_all = "snake_case")]
270pub enum WebhookEndpointStatus {
271 Pending,
272 Active,
273 Disabled,
274}
275
276#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
277pub enum ApplicationUserEventType {
278 #[serde(rename = "user.projection.created")]
279 Created,
280 #[serde(rename = "user.projection.updated")]
281 Updated,
282 #[serde(rename = "user.projection.disabled")]
283 Disabled,
284}
285
286#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
287#[serde(rename_all = "snake_case")]
288pub enum WebhookDeliveryState {
289 Pending,
290 Leased,
291 Delivered,
292 Terminal,
293 Cancelled,
294}
295
296#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
297#[serde(rename_all = "snake_case")]
298pub enum WebhookDeliveryOutcomeClass {
299 Accepted,
300 Transient,
301 Ambiguous,
302 Permanent,
303}
304
305#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
306pub struct WebhookEndpoint {
307 pub id: String,
308 pub public_id: String,
309 pub project_id: String,
310 pub application_id: String,
311 pub url: String,
312 #[schema(max_items = 3)]
313 pub subscribed_event_types: Vec<ApplicationUserEventType>,
314 pub status: WebhookEndpointStatus,
315 #[schema(minimum = 1)]
316 pub revision: i64,
317 pub current_secret_generation: Option<i32>,
318 pub overlap_secret_generation: Option<i32>,
319 pub overlap_expires_at: Option<String>,
320 pub consecutive_failure_count: i32,
321 pub last_delivery_at: Option<String>,
322 pub last_success_at: Option<String>,
323 pub last_failure_class: Option<String>,
324 pub last_tested_at: Option<String>,
325 pub last_test_succeeded_at: Option<String>,
326 pub created_at: String,
327 pub updated_at: String,
328}
329
330#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
331pub struct WebhookEndpointList {
332 #[schema(max_items = 100)]
333 pub items: Vec<WebhookEndpoint>,
334}
335
336#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
337#[serde(deny_unknown_fields)]
338pub struct CreateWebhookEndpointRequest {
339 pub url: String,
340 #[schema(min_items = 1, max_items = 3)]
341 pub subscribed_event_types: Vec<ApplicationUserEventType>,
342 #[schema(min_length = 32, max_length = 128, write_only)]
343 pub secret: String,
344}
345
346#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
347#[serde(deny_unknown_fields)]
348pub struct UpdateWebhookEndpointRequest {
349 #[schema(min_items = 1, max_items = 3)]
350 pub subscribed_event_types: Vec<ApplicationUserEventType>,
351 #[schema(minimum = 1)]
352 pub expected_revision: i64,
353}
354
355#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
356#[serde(deny_unknown_fields)]
357pub struct ExpectedWebhookEndpointRevision {
358 #[schema(minimum = 1)]
359 pub expected_revision: i64,
360}
361
362#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
363#[serde(deny_unknown_fields)]
364pub struct PrepareWebhookSecretRotationRequest {
365 #[schema(min_length = 32, max_length = 128, write_only)]
366 pub secret: String,
367 #[schema(minimum = 1)]
368 pub expected_revision: i64,
369}
370
371#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
372#[serde(rename_all = "snake_case")]
373pub enum WebhookSecretPreparationStatus {
374 Pending,
375 Provisioned,
376 Terminal,
377}
378
379#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
380pub struct PreparedWebhookSecretRotation {
381 pub endpoint: WebhookEndpoint,
382 #[schema(minimum = 1)]
383 pub generation: i32,
384 pub preparation_status: WebhookSecretPreparationStatus,
385 pub already_active: bool,
386}
387
388#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
389#[serde(deny_unknown_fields)]
390pub struct ActivateWebhookSecretRotationRequest {
391 #[schema(minimum = 1)]
392 pub expected_revision: i64,
393 #[schema(minimum = 300, maximum = 86400)]
394 pub overlap_seconds: i64,
395}
396
397#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema)]
398pub struct ApplicationUserEvent {
399 pub event_id: String,
400 pub project_id: String,
401 pub application_id: String,
402 pub user_id: String,
403 pub event_type: ApplicationUserEventType,
404 pub user_revision: i64,
405 pub projection_revision: i64,
406 pub projection_schema: String,
407 pub safe_body: serde_json::Value,
408 pub occurred_at: String,
409}
410
411#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema)]
412pub struct ApplicationUserEventList {
413 #[schema(max_items = 100)]
414 pub items: Vec<ApplicationUserEvent>,
415 pub next_cursor: Option<String>,
416}
417
418#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
419pub struct WebhookDelivery {
420 pub id: String,
421 pub endpoint_id: String,
422 pub event_id: String,
423 pub replay_sequence: i32,
424 pub replay_of_delivery_id: Option<String>,
425 pub state: WebhookDeliveryState,
426 pub attempt_count: i32,
427 pub next_attempt_at: String,
428 pub last_outcome_class: Option<WebhookDeliveryOutcomeClass>,
429 pub last_http_status: Option<i32>,
430 pub delivered_at: Option<String>,
431 pub terminal_at: Option<String>,
432 pub created_at: String,
433}
434
435#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
436pub struct WebhookDeliveryList {
437 #[schema(max_items = 100)]
438 pub items: Vec<WebhookDelivery>,
439 pub next_cursor: Option<String>,
440}
441
442#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
443#[serde(deny_unknown_fields)]
444pub struct ReplayWebhookDeliveryRequest {
445 pub confirm: bool,
446}
447
448#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema)]
449pub struct SigningKey {
450 pub id: String,
451 pub project_id: String,
452 pub kid: String,
453 pub algorithm: SigningAlgorithm,
454 pub state: SigningKeyState,
455 pub ring_revision: i64,
456 pub signing_epoch: i64,
457 pub sign_not_before: Option<String>,
458 pub verify_not_after: Option<String>,
459 pub public_jwk: Option<PublicJwk>,
461}
462
463#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, ToSchema)]
464pub struct SigningKeyList {
465 #[schema(max_items = 100)]
466 pub items: Vec<SigningKey>,
467}
468
469#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
470#[serde(rename_all = "snake_case")]
471pub enum ProjectServerKeyStatus {
472 Active,
473 Revoked,
474}
475
476#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
478#[serde(deny_unknown_fields)]
479pub struct ProjectServerKey {
480 #[schema(max_length = 36)]
481 pub id: String,
482 #[schema(max_length = 36)]
483 pub project_id: String,
484 #[schema(min_length = 22, max_length = 22, pattern = "^[A-Za-z0-9_-]{22}$")]
485 pub public_key_id: String,
486 #[schema(min_length = 1, max_length = 64)]
487 pub label: String,
488 pub status: ProjectServerKeyStatus,
489 #[schema(minimum = 1)]
490 pub digest_key_version: i32,
491 #[schema(
492 min_length = 36,
493 max_length = 36,
494 pattern = "^owl_server_v1\\.[A-Za-z0-9_-]{22}$"
495 )]
496 pub display_prefix: String,
497 #[schema(minimum = 1)]
498 pub revision: i64,
499 #[schema(max_length = 64)]
500 pub created_at: String,
501 #[serde(deserialize_with = "deserialize_required_nullable_string")]
503 #[schema(max_length = 64, required = true)]
504 pub credential_acknowledged_at: Option<String>,
505 #[schema(max_length = 64)]
506 pub last_used_at: Option<String>,
507 #[schema(max_length = 64)]
508 pub revoked_at: Option<String>,
509}
510
511#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
512#[serde(deny_unknown_fields)]
513pub struct ProjectServerKeyList {
514 #[schema(max_items = 100)]
515 pub items: Vec<ProjectServerKey>,
516 #[serde(default)]
517 #[schema(max_length = 64)]
518 pub next_cursor: Option<String>,
519 #[serde(deserialize_with = "deserialize_required_nullable_project_server_key")]
521 #[schema(required = true)]
522 pub active_unacknowledged_key: Option<ProjectServerKey>,
523}
524
525#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
526#[serde(deny_unknown_fields)]
527pub struct CreateProjectServerKeyRequest {
528 #[schema(min_length = 1, max_length = 64)]
529 pub label: String,
530}
531
532#[derive(Clone, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
534#[serde(deny_unknown_fields)]
535pub struct CreateProjectServerKeyResponse {
536 pub key: ProjectServerKey,
537 #[schema(
538 min_length = 80,
539 max_length = 80,
540 pattern = "^owl_server_v1\\.[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}$",
541 read_only
542 )]
543 pub credential: String,
544}
545
546impl std::fmt::Debug for CreateProjectServerKeyResponse {
547 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
548 formatter
549 .debug_struct("CreateProjectServerKeyResponse")
550 .field("key", &self.key)
551 .field("credential", &"[REDACTED]")
552 .finish()
553 }
554}
555
556impl Drop for CreateProjectServerKeyResponse {
557 fn drop(&mut self) {
558 zeroize::Zeroize::zeroize(&mut self.credential);
559 }
560}
561
562#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
563#[serde(deny_unknown_fields)]
564pub struct AcknowledgeProjectServerKeyDeliveryRequest {
565 #[schema(minimum = 1)]
566 pub expected_revision: i64,
567 pub confirm_stored: bool,
569}
570
571#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
572#[serde(deny_unknown_fields)]
573pub struct RevokeProjectServerKeyRequest {
574 #[schema(minimum = 1)]
575 pub expected_revision: i64,
576 pub confirm: bool,
578}
579
580#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
581#[serde(deny_unknown_fields)]
582pub struct RotateSigningKeyRequest {
583 #[schema(minimum = 1)]
584 pub expected_project_revision: i64,
585}
586
587#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
588#[serde(deny_unknown_fields)]
589pub struct KeyTransitionRequest {
590 #[schema(minimum = 1)]
591 pub expected_ring_revision: i64,
592}
593
594#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
595#[allow(
596 clippy::struct_excessive_bools,
597 reason = "generated wire capability flags are independently meaningful and additive"
598)]
599pub struct ProviderManagedProfileCapability {
600 pub supported: bool,
601 pub enabled: bool,
602 #[schema(max_items = 16)]
603 pub exact_scopes: Vec<String>,
604 pub profile_schema: String,
605 pub read_retry_safe: bool,
606 pub renewal_idempotent_replay: bool,
607 pub supports_revocation: bool,
608}
609
610#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
611pub struct Provider {
612 pub id: String,
613 pub project_id: String,
614 #[schema(max_length = 64)]
615 pub provider_key: String,
616 pub kind: ProviderKind,
617 #[schema(max_length = 128)]
618 pub display_name: String,
619 pub issuer: String,
620 pub client_id: String,
621 pub callback_url: String,
622 pub status: ProviderStatus,
623 pub revision: i64,
624 pub login_supported: bool,
626 pub identity_proof_supported: bool,
628 pub managed_profile: ProviderManagedProfileCapability,
629 pub secret_replacement_pending: bool,
631 #[schema(max_items = 100)]
632 pub assigned_application_ids: Vec<String>,
633}
634
635#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
636pub struct ProviderList {
637 #[schema(max_items = 100)]
638 pub items: Vec<Provider>,
639}
640
641#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
642#[serde(rename_all = "snake_case")]
643pub enum ProviderEgressMode {
644 AllowAll,
645 ExactOrigins,
646}
647
648#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
649pub struct ProviderEgressPolicy {
650 pub project_id: String,
651 pub mode: ProviderEgressMode,
652 #[schema(max_items = 1024)]
653 pub exact_origins: Vec<String>,
654 #[schema(minimum = 1)]
655 pub revision: i64,
656}
657
658#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
659#[serde(deny_unknown_fields)]
660pub struct UpdateProviderEgressPolicyRequest {
661 pub mode: ProviderEgressMode,
662 #[schema(max_items = 1024)]
663 pub exact_origins: Vec<String>,
664 #[schema(minimum = 1)]
665 pub expected_revision: i64,
666}
667
668#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
669#[serde(deny_unknown_fields)]
670pub struct OidcPreflightRequest {
671 #[schema(min_length = 1, max_length = 64)]
672 pub provider_key: String,
673 #[schema(min_length = 8, max_length = 2048)]
674 pub issuer: String,
675}
676
677#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
678#[allow(
679 clippy::struct_excessive_bools,
680 reason = "the public diagnostic reports four independent reviewed OIDC capabilities"
681)]
682pub struct OidcPreflightResult {
683 pub canonical_issuer: String,
684 pub callback_url: String,
685 pub callback_guidance: ProviderCallbackGuidance,
686 #[schema(max_items = 8)]
687 pub admitted_endpoint_origins: Vec<String>,
688 #[schema(max_items = 8)]
689 pub exact_scopes: Vec<String>,
690 pub authorization_code_supported: bool,
691 pub pkce_s256_supported: bool,
692 pub rs256_id_tokens_supported: bool,
693 pub managed_profile_supported: bool,
694 pub policy_mode: ProviderEgressMode,
695 #[schema(minimum = 1)]
696 pub policy_revision: i64,
697}
698
699#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
700#[serde(deny_unknown_fields)]
701pub struct NamedProviderPreflightRequest {
702 pub kind: ProviderKind,
704 #[schema(min_length = 1, max_length = 64)]
705 pub provider_key: String,
706}
707
708#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
709#[serde(rename_all = "snake_case")]
710pub enum ProviderCallbackGuidance {
711 RegisterExactRedirectUri,
712}
713
714#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
715#[serde(rename_all = "snake_case")]
716pub enum ProviderConsentBehavior {
717 Standard,
718 ExplicitOfflineConsent,
719}
720
721#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
722pub struct FixedProviderAuthorizationPolicy {
723 #[schema(max_items = 8)]
724 pub exact_scopes: Vec<String>,
725 pub consent_behavior: ProviderConsentBehavior,
726}
727
728#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
729pub struct NamedProviderPreflightResult {
730 pub kind: ProviderKind,
731 pub issuer: String,
732 pub callback_url: String,
733 pub callback_guidance: ProviderCallbackGuidance,
734 pub login: FixedProviderAuthorizationPolicy,
735 pub managed_profile: Option<FixedProviderAuthorizationPolicy>,
736}
737
738#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
739#[serde(deny_unknown_fields)]
740pub struct CreateProviderRequest {
741 pub kind: ProviderKind,
743 #[schema(min_length = 1, max_length = 64)]
744 pub provider_key: String,
745 #[schema(min_length = 1, max_length = 128)]
746 pub display_name: String,
747 #[schema(min_length = 8, max_length = 2048)]
749 pub issuer: Option<String>,
750 #[schema(min_length = 1, max_length = 512)]
751 pub client_id: String,
752 #[schema(write_only, min_length = 1, max_length = 4096)]
753 pub client_secret: String,
754 #[serde(default)]
756 pub managed_profile_enabled: bool,
757 #[schema(minimum = 1)]
758 pub expected_project_revision: i64,
759}
760
761#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
762#[serde(deny_unknown_fields)]
763pub struct UpdateProviderRequest {
764 #[schema(min_length = 1, max_length = 128)]
765 pub display_name: String,
766 #[schema(min_length = 1, max_length = 512)]
767 pub client_id: String,
768 #[schema(minimum = 1)]
769 pub expected_provider_revision: i64,
770}
771
772#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
773#[serde(deny_unknown_fields)]
774pub struct ReplaceProviderSecretRequest {
775 #[schema(min_length = 1, max_length = 128)]
776 pub display_name: String,
777 #[schema(min_length = 1, max_length = 512)]
778 pub client_id: String,
779 #[schema(write_only, min_length = 1, max_length = 4096)]
780 pub client_secret: String,
781 #[schema(minimum = 1)]
782 pub expected_provider_revision: i64,
783}
784
785#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
786#[serde(deny_unknown_fields)]
787pub struct ReconcileProviderRequest {
788 #[schema(write_only, min_length = 1, max_length = 4096)]
789 pub client_secret: String,
790 #[schema(minimum = 1)]
791 pub expected_project_revision: i64,
792}
793
794#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
795#[serde(deny_unknown_fields)]
796pub struct ReconcileProviderSecretReplacementRequest {
797 #[schema(write_only, min_length = 1, max_length = 4096)]
798 pub client_secret: String,
799 #[schema(minimum = 1)]
800 pub expected_provider_revision: i64,
801}
802
803#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
804#[serde(deny_unknown_fields)]
805pub struct ProviderRevisionRequest {
806 #[schema(minimum = 1)]
807 pub expected_provider_revision: i64,
808}
809
810#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
811#[serde(deny_unknown_fields)]
812pub struct ProviderAssignmentRequest {
813 #[schema(minimum = 1)]
814 pub expected_application_revision: i64,
815}
816
817#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
818#[serde(rename_all = "snake_case")]
819pub enum SmtpTlsMode {
820 ImplicitTls,
821 StarttlsRequired,
822}
823
824#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
825#[serde(rename_all = "snake_case")]
826pub enum SmtpGenerationStatus {
827 Reconciled,
828 Pending,
829 Active,
830 Retained,
831 Disabled,
832 Compromised,
833 Retired,
834}
835
836#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
837#[allow(
838 clippy::struct_excessive_bools,
839 reason = "generated Control policy DTO preserves explicit independent switches"
840)]
841pub struct EmailMethodPolicy {
842 pub project_id: String,
843 pub enabled: bool,
844 pub policy_revision: i64,
845 pub security_revision: i64,
846 pub otp_enabled: bool,
847 pub magic_link_enabled: bool,
848 #[schema(minimum = 6, maximum = 10)]
849 pub otp_digits: i16,
850 #[schema(minimum = 30, maximum = 600)]
851 pub otp_validity_seconds: i32,
852 #[schema(minimum = 1, maximum = 5)]
853 pub otp_max_attempts: i16,
854 #[schema(minimum = 30, maximum = 600)]
855 pub resend_after_seconds: i32,
856 #[schema(minimum = 1, maximum = 5)]
857 pub max_generations: i16,
858 #[schema(minimum = 30, maximum = 600)]
859 pub magic_validity_seconds: i32,
860 pub signup_enabled: bool,
861 pub transferred_magic_link_enabled: bool,
862 pub allow_deployment_default: bool,
863}
864
865#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
866#[serde(deny_unknown_fields)]
867#[allow(
868 clippy::struct_excessive_bools,
869 reason = "generated Control policy DTO preserves explicit independent switches"
870)]
871pub struct UpdateEmailMethodPolicyRequest {
872 pub enabled: bool,
873 pub otp_enabled: bool,
874 pub magic_link_enabled: bool,
875 pub otp_digits: i16,
876 pub otp_validity_seconds: i32,
877 pub otp_max_attempts: i16,
878 pub resend_after_seconds: i32,
879 pub max_generations: i16,
880 pub magic_validity_seconds: i32,
881 pub signup_enabled: bool,
882 pub transferred_magic_link_enabled: bool,
883 pub allow_deployment_default: bool,
884 pub expected_policy_revision: i64,
885 pub expected_security_revision: i64,
886}
887
888#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
889#[serde(deny_unknown_fields)]
890pub struct EmailAssignmentRequest {
891 pub enabled: bool,
892 pub expected_application_security_revision: i64,
893}
894
895#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
896pub struct EmailAssignment {
897 pub project_id: String,
898 pub application_id: String,
899 pub enabled: bool,
900 #[schema(minimum = 1)]
901 pub security_revision: i64,
902}
903
904#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
905pub struct EmailAssignmentList {
906 #[schema(max_items = 100)]
907 pub items: Vec<EmailAssignment>,
908}
909
910#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
911pub struct SmtpConfiguration {
912 pub id: String,
913 pub project_id: String,
914 pub generation: i32,
915 pub revision: i64,
916 pub security_eligibility_revision: i64,
917 pub status: SmtpGenerationStatus,
918 #[schema(max_length = 253)]
919 pub host: String,
920 pub port: u16,
921 pub tls_mode: SmtpTlsMode,
922 #[schema(max_length = 254)]
923 pub sender_address: String,
924 #[schema(max_length = 128)]
925 pub sender_name: Option<String>,
926 #[schema(max_length = 254)]
927 pub reply_to: Option<String>,
928 #[schema(max_length = 64)]
929 pub retained_until: Option<String>,
930 #[schema(max_length = 64)]
931 pub safe_fingerprint: Option<String>,
932}
933
934#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
935pub struct DeploymentSmtpGeneration {
936 pub generation: i32,
937 pub revision: i64,
938 pub security_eligibility_revision: i64,
939 pub status: SmtpGenerationStatus,
940 #[schema(max_length = 253)]
941 pub host: String,
942 pub port: u16,
943 pub tls_mode: SmtpTlsMode,
944 #[schema(max_length = 254)]
945 pub sender_address: String,
946 #[schema(max_length = 64)]
947 pub retained_until: Option<String>,
948 #[schema(max_length = 64)]
949 pub safe_fingerprint: String,
950 #[schema(max_items = 16)]
951 pub explicitly_allowed_private_ips: Vec<String>,
952}
953
954#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
955pub struct DeploymentSmtpGenerationList {
956 #[schema(max_items = 32)]
957 pub items: Vec<DeploymentSmtpGeneration>,
958}
959
960#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
961pub struct SmtpConfigurationList {
962 #[schema(max_items = 32)]
963 pub items: Vec<SmtpConfiguration>,
964}
965
966#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
967#[serde(deny_unknown_fields)]
968pub struct ReconcileDeploymentSmtpRequest {
969 #[schema(write_only, min_length = 2, max_length = 4096)]
970 pub credential: String,
971}
972
973#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
974#[serde(deny_unknown_fields)]
975pub struct CreateSmtpConfigurationRequest {
976 #[schema(max_length = 253)]
977 pub host: String,
978 pub port: u16,
979 pub tls_mode: SmtpTlsMode,
980 #[schema(max_length = 254)]
981 pub sender_address: String,
982 #[schema(max_length = 128)]
983 pub sender_name: Option<String>,
984 #[schema(max_length = 254)]
985 pub reply_to: Option<String>,
986 #[schema(write_only, min_length = 2, max_length = 4096)]
987 pub credential: String,
988 pub expected_project_security_revision: i64,
989}
990
991#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
992#[serde(deny_unknown_fields)]
993pub struct SmtpRevisionRequest {
994 pub expected_revision: i64,
995}
996
997#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
998#[serde(deny_unknown_fields)]
999pub struct TestSmtpConfigurationRequest {
1000 #[schema(max_length = 254)]
1001 pub recipient: String,
1002 pub expected_revision: i64,
1003}
1004
1005#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1006pub struct SmtpTestOperation {
1007 pub id: String,
1008 pub project_id: String,
1009 pub smtp_configuration_id: String,
1010 pub status: String,
1011 pub outcome: Option<String>,
1012 pub created_at: String,
1013 pub completed_at: Option<String>,
1014}
1015
1016#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1017#[serde(rename_all = "snake_case")]
1018pub enum ManagedProviderConnectionState {
1019 Active,
1020 ReauthRequired,
1021 Revoked,
1022 Disconnected,
1023}
1024
1025#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1026pub struct ManagedProviderConnection {
1027 pub id: String,
1028 pub project_id: String,
1029 pub provider_id: String,
1030 pub identity_id: String,
1031 pub user_id: String,
1032 pub state: ManagedProviderConnectionState,
1033 pub revision: i64,
1034 pub generation: i64,
1035 pub credential_generation: i64,
1036 pub capability_key: String,
1037 #[schema(max_items = 16)]
1038 pub required_scopes: Vec<String>,
1039 pub source_schema: String,
1040 pub supports_revocation: bool,
1041 #[schema(max_items = 100)]
1042 pub reauthorization_application_ids: Vec<String>,
1043 pub last_safe_outcome: String,
1044 pub last_synchronized_at: Option<String>,
1045 pub next_synchronize_at: Option<String>,
1046 pub next_renewal_at: Option<String>,
1047 pub consecutive_failures: i32,
1048}
1049
1050#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1051pub struct ManagedProviderConnectionList {
1052 #[schema(max_items = 100)]
1053 pub items: Vec<ManagedProviderConnection>,
1054}
1055
1056#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1057#[serde(deny_unknown_fields)]
1058pub struct ManagedProviderConnectionActionRequest {
1059 #[schema(minimum = 1)]
1060 pub expected_revision: i64,
1061 #[schema(minimum = 1)]
1062 pub expected_generation: i64,
1063 #[serde(default)]
1065 pub confirm: bool,
1066}
1067
1068#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1069#[serde(rename_all = "snake_case")]
1070pub enum ManagedReauthorizationStatus {
1071 AwaitingBrowserBinding,
1072 AwaitingProviderStart,
1073 ProviderAuthorizationStarted,
1074 ProviderExchangeInProgress,
1075 Completed,
1076 ProviderExchangeFailed,
1077 Expired,
1078 Cancelled,
1079}
1080
1081#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1082pub struct ManagedReauthorization {
1083 pub id: String,
1084 pub project_id: String,
1085 pub user_id: String,
1086 pub connection_id: String,
1087 pub provider_key: String,
1088 pub application_id: String,
1089 pub status: ManagedReauthorizationStatus,
1090 #[schema(minimum = 1)]
1091 pub revision: i64,
1092 pub expires_at: String,
1093}
1094
1095#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1096#[serde(deny_unknown_fields)]
1097pub struct CreateManagedReauthorizationRequest {
1098 pub application_id: String,
1099 #[schema(minimum = 1)]
1100 pub expected_connection_revision: i64,
1101 #[schema(minimum = 1)]
1102 pub expected_connection_generation: i64,
1103 #[schema(minimum = 1)]
1104 pub expected_credential_generation: i64,
1105}
1106
1107#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1108pub struct CreateManagedReauthorizationResponse {
1109 #[serde(flatten)]
1110 pub interaction: ManagedReauthorization,
1111 pub hosted_target: Option<String>,
1113}
1114
1115#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1116#[serde(deny_unknown_fields)]
1117pub struct CancelManagedReauthorizationRequest {
1118 #[schema(minimum = 1)]
1119 pub expected_revision: i64,
1120}
1121
1122#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1123#[serde(deny_unknown_fields)]
1124pub struct IdentityMutationUserTarget {
1125 pub user_id: String,
1126 #[schema(minimum = 1)]
1127 pub expected_user_revision: i64,
1128 #[schema(minimum = 1)]
1129 pub expected_user_security_revision: i64,
1130}
1131
1132#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1133#[serde(tag = "identity_kind", rename_all = "snake_case", deny_unknown_fields)]
1134pub enum ExistingIdentityReference {
1135 Provider {
1136 identity_id: String,
1137 #[schema(minimum = 1)]
1138 expected_identity_revision: i64,
1139 },
1140 Email {
1141 identity_id: String,
1142 #[schema(minimum = 1)]
1143 expected_identity_revision: i64,
1144 },
1145}
1146
1147#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1148#[serde(tag = "method_kind", rename_all = "snake_case", deny_unknown_fields)]
1149pub enum IdentityMutationProofAuthority {
1150 Provider {
1151 application_id: String,
1152 provider_id: String,
1153 },
1154 Email {
1155 application_id: String,
1156 },
1157}
1158
1159#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1160#[serde(tag = "disposition", rename_all = "snake_case", deny_unknown_fields)]
1161pub enum UnlinkPrimarySourceDisposition {
1162 Preserve,
1163 Clear,
1164 Provider {
1165 identity_id: String,
1166 #[schema(minimum = 1)]
1167 expected_identity_revision: i64,
1168 },
1169 Email {
1170 identity_id: String,
1171 #[schema(minimum = 1)]
1172 expected_identity_revision: i64,
1173 },
1174}
1175
1176#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1177#[serde(tag = "identity_kind", rename_all = "snake_case", deny_unknown_fields)]
1178pub enum MergePrimarySource {
1179 Provider {
1180 identity_id: String,
1181 #[schema(minimum = 1)]
1182 expected_identity_revision: i64,
1183 },
1184 Email {
1185 identity_id: String,
1186 #[schema(minimum = 1)]
1187 expected_identity_revision: i64,
1188 },
1189}
1190
1191#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1192#[serde(rename_all = "snake_case")]
1193pub enum MergeSessionsDisposition {
1194 LoserRevoked,
1195}
1196
1197#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1198#[serde(rename_all = "snake_case")]
1199pub enum MergeBindingsDisposition {
1200 WinnerPreferred,
1201}
1202
1203#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1206#[serde(tag = "operation_kind", rename_all = "snake_case", deny_unknown_fields)]
1207pub enum CreateIdentityMutationIntentRequest {
1208 Link {
1209 destination: IdentityMutationUserTarget,
1210 destination_identity: ExistingIdentityReference,
1211 candidate_identity_kind: IdentityKind,
1212 destination_proof_authority: IdentityMutationProofAuthority,
1213 candidate_proof_authority: IdentityMutationProofAuthority,
1214 },
1215 Unlink {
1216 owner: IdentityMutationUserTarget,
1217 identity: ExistingIdentityReference,
1218 proof_authority: IdentityMutationProofAuthority,
1219 primary_source_disposition: UnlinkPrimarySourceDisposition,
1220 },
1221 Merge {
1222 winner: IdentityMutationUserTarget,
1223 winner_identity: ExistingIdentityReference,
1224 winner_proof_authority: IdentityMutationProofAuthority,
1225 loser: IdentityMutationUserTarget,
1226 loser_identity: ExistingIdentityReference,
1227 loser_proof_authority: IdentityMutationProofAuthority,
1228 primary_source: MergePrimarySource,
1229 sessions_disposition: MergeSessionsDisposition,
1230 bindings_disposition: MergeBindingsDisposition,
1231 },
1232}
1233
1234#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1235#[serde(rename_all = "snake_case")]
1236pub enum IdentityMutationOperationKind {
1237 Link,
1238 Unlink,
1239 Merge,
1240}
1241
1242#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1243#[serde(rename_all = "snake_case")]
1244pub enum IdentityMutationIntentStatus {
1245 PendingProof,
1246 Ready,
1247 Completed,
1248 Expired,
1249 Cancelled,
1250}
1251
1252#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1253#[serde(rename_all = "snake_case")]
1254pub enum IdentityMutationProofRole {
1255 DestinationOwner,
1256 CandidateIdentity,
1257 IdentityOwner,
1258 WinnerOwner,
1259 LoserOwner,
1260}
1261
1262#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1263pub struct IdentityMutationProofSlot {
1264 pub id: String,
1265 pub role: IdentityMutationProofRole,
1266 pub identity_kind: IdentityKind,
1267 pub method_kind: IdentityMutationMethodKind,
1268 pub proved: bool,
1269}
1270
1271#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1272pub struct IdentityMutationIntent {
1273 pub id: String,
1274 pub project_id: String,
1275 pub operation_kind: IdentityMutationOperationKind,
1276 pub status: IdentityMutationIntentStatus,
1277 #[schema(minimum = 1)]
1278 pub revision: i64,
1279 #[schema(max_length = 64)]
1280 pub effective_expires_at: String,
1281 #[schema(min_items = 1, max_items = 2)]
1282 pub slots: Vec<IdentityMutationProofSlot>,
1283}
1284
1285#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1286pub struct CreateIdentityMutationIntentResponse {
1287 #[serde(flatten)]
1288 pub intent: IdentityMutationIntent,
1289 #[schema(max_length = 512)]
1291 pub hosted_target: Option<String>,
1292}
1293
1294#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1295#[serde(deny_unknown_fields)]
1296pub struct CancelIdentityMutationIntentRequest {
1297 #[schema(minimum = 1)]
1298 pub expected_revision: i64,
1299}
1300
1301#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1302#[serde(rename_all = "snake_case")]
1303pub enum LinkIdentityMutationConfirmation {
1304 LinkIdentity,
1305}
1306
1307#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1308#[serde(rename_all = "snake_case")]
1309pub enum UnlinkIdentityMutationConfirmation {
1310 UnlinkIdentity,
1311}
1312
1313#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1314#[serde(rename_all = "snake_case")]
1315pub enum MergeIdentityMutationConfirmation {
1316 MergeUsers,
1317}
1318
1319#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1320#[serde(tag = "operation_kind", rename_all = "snake_case", deny_unknown_fields)]
1321pub enum ConfirmIdentityMutationIntentRequest {
1322 Link {
1323 #[schema(minimum = 1)]
1324 expected_revision: i64,
1325 confirmation: LinkIdentityMutationConfirmation,
1326 },
1327 Unlink {
1328 #[schema(minimum = 1)]
1329 expected_revision: i64,
1330 confirmation: UnlinkIdentityMutationConfirmation,
1331 },
1332 Merge {
1333 #[schema(minimum = 1)]
1334 expected_revision: i64,
1335 confirmation: MergeIdentityMutationConfirmation,
1336 },
1337}
1338
1339#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1340#[serde(rename_all = "snake_case")]
1341pub enum ProjectUserStatus {
1342 Active,
1343 Disabled,
1344 Merged,
1345}
1346
1347#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1348#[serde(rename_all = "snake_case")]
1349pub enum ProjectUserSort {
1350 CreatedNewest,
1351 CreatedOldest,
1352}
1353
1354#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1355#[serde(rename_all = "snake_case")]
1356pub enum ProjectUserIdentityFilter {
1357 Provider,
1358 Email,
1359}
1360
1361#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1362#[serde(rename_all = "snake_case")]
1363pub enum ManagedSessionStatus {
1364 Active,
1365 Revoked,
1366 Expired,
1367}
1368
1369#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1370pub struct ProjectUser {
1371 pub id: String,
1372 pub project_id: String,
1373 #[schema(max_length = 96)]
1374 pub public_id: String,
1375 pub status: ProjectUserStatus,
1376 #[schema(minimum = 1)]
1377 pub user_revision: i64,
1378 #[schema(minimum = 1)]
1379 pub security_revision: i64,
1380 #[schema(max_length = 256)]
1381 pub display_name: Option<String>,
1382 #[schema(max_length = 2048)]
1383 pub picture_url: Option<String>,
1384 #[schema(max_length = 64)]
1385 pub created_at: String,
1386 #[schema(max_length = 64)]
1387 pub updated_at: String,
1388}
1389
1390#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1391pub struct ProjectUserList {
1392 #[schema(max_items = 100)]
1393 pub items: Vec<ProjectUser>,
1394 #[schema(max_length = 36)]
1395 pub next_cursor: Option<String>,
1396}
1397
1398#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1399#[serde(deny_unknown_fields)]
1400pub struct ProjectUserEmailLookupRequest {
1401 #[schema(min_length = 3, max_length = 320)]
1402 pub email: String,
1403}
1404
1405#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1406#[serde(deny_unknown_fields)]
1407pub struct ProjectUserLookup {
1408 #[serde(deserialize_with = "deserialize_required_nullable_project_user")]
1409 #[schema(required = true)]
1410 pub user: Option<ProjectUser>,
1411}
1412
1413#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1414#[serde(rename_all = "snake_case")]
1415pub enum ProjectUserIdentityStatus {
1416 Active,
1417 Disabled,
1418}
1419
1420#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1421#[serde(rename_all = "snake_case")]
1422pub enum RedactedEmailMarker {
1423 Redacted,
1424}
1425
1426#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1429#[serde(tag = "identity_kind", rename_all = "snake_case")]
1430pub enum ProjectUserIdentityPresentation {
1431 Provider {
1432 #[schema(max_length = 64)]
1433 provider_key: String,
1434 },
1435 Email {
1436 address: RedactedEmailMarker,
1437 },
1438}
1439
1440#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1441pub struct ProjectUserIdentity {
1442 pub id: String,
1443 pub project_id: String,
1444 pub user_id: String,
1445 pub status: ProjectUserIdentityStatus,
1446 #[schema(minimum = 1)]
1447 pub identity_revision: i64,
1448 pub is_primary_source: bool,
1449 #[serde(flatten)]
1450 pub presentation: ProjectUserIdentityPresentation,
1451 #[schema(max_length = 64)]
1452 pub verified_or_observed_at: String,
1453 #[schema(max_length = 64)]
1454 pub created_at: String,
1455 #[schema(max_length = 64)]
1456 pub updated_at: String,
1457}
1458
1459#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1460pub struct ProjectUserIdentityList {
1461 #[schema(max_items = 100)]
1462 pub items: Vec<ProjectUserIdentity>,
1463}
1464
1465#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1466pub struct ApplicationSession {
1467 pub id: String,
1468 pub project_id: String,
1469 pub user_id: String,
1470 pub application_id: String,
1471 #[schema(max_length = 96)]
1472 pub application_public_id: String,
1473 #[schema(max_length = 128)]
1474 pub application_display_name: String,
1475 pub browser_session_id: Option<String>,
1476 pub status: ManagedSessionStatus,
1477 #[schema(minimum = 1)]
1478 pub session_revision: i64,
1479 #[schema(max_length = 64)]
1480 pub authenticated_at: String,
1481 #[schema(max_length = 64)]
1482 pub absolute_expires_at: String,
1483 #[schema(max_length = 64)]
1484 pub revoked_at: Option<String>,
1485 #[schema(max_length = 64)]
1486 pub created_at: String,
1487 #[schema(max_length = 64)]
1488 pub updated_at: String,
1489}
1490
1491#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1492pub struct BrowserSession {
1493 pub id: String,
1494 pub project_id: String,
1495 pub user_id: String,
1496 pub status: ManagedSessionStatus,
1497 #[schema(minimum = 1)]
1498 pub session_revision: i64,
1499 #[schema(max_length = 64)]
1500 pub authenticated_at: String,
1501 #[schema(max_length = 64)]
1502 pub last_activity_at: String,
1503 #[schema(max_length = 64)]
1504 pub idle_expires_at: String,
1505 #[schema(max_length = 64)]
1506 pub absolute_expires_at: String,
1507 #[schema(max_length = 64)]
1508 pub terminated_at: Option<String>,
1509 #[schema(max_length = 64)]
1510 pub created_at: String,
1511 #[schema(max_length = 64)]
1512 pub updated_at: String,
1513}
1514
1515#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1516pub struct ProjectUserSessions {
1517 #[schema(max_items = 100)]
1518 pub application_sessions: Vec<ApplicationSession>,
1519 #[schema(max_items = 100)]
1520 pub browser_sessions: Vec<BrowserSession>,
1521}
1522
1523#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, ToSchema)]
1524#[serde(deny_unknown_fields)]
1525pub struct ExpectedSessionRevision {
1526 #[schema(minimum = 1)]
1529 pub expected_session_revision: i64,
1530}
1531
1532macro_rules! control_path {
1533 ($name:ident, $method:ident, $path:literal, $response:ty, $summary:literal $(, body = $body:ty)? $(, params($($params:tt)*))?) => {
1534 #[utoipa::path(
1535 $method,
1536 path = $path,
1537 $(request_body = $body,)?
1538 responses(
1539 (status = 200, description = $summary, body = $response),
1540 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
1541 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
1542 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
1543 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
1544 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
1545 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
1546 ),
1547 $(params($($params)*),)?
1548 security(("operator_api_key" = []))
1549 )]
1550 #[doc(hidden)]
1551 pub fn $name() {}
1552 };
1553}
1554
1555macro_rules! control_preflight_path {
1556 ($name:ident, $path:literal, $response:ty, $summary:literal, $body:ty) => {
1557 #[utoipa::path(
1558 post,
1559 path = $path,
1560 request_body = $body,
1561 responses(
1562 (status = 200, description = $summary, body = $response),
1563 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
1564 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
1565 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
1566 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
1567 (status = 422, description = "Provider metadata or policy rejected", body = ProblemDetails, content_type = "application/problem+json"),
1568 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
1569 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
1570 ),
1571 params(("project_id" = String, Path)),
1572 security(("operator_api_key" = []))
1573 )]
1574 #[doc(hidden)]
1575 pub fn $name() {}
1576 };
1577}
1578
1579#[utoipa::path(
1580 post,
1581 path = "/v1/projects/{project_id}/smtp-configurations/{smtp_id}/test",
1582 request_body = TestSmtpConfigurationRequest,
1583 responses(
1584 (
1585 status = 202,
1586 description = "Enqueued bounded SMTP test",
1587 body = SmtpTestOperation,
1588 headers(("Location" = String, description = "Exact Control path for the SMTP test operation"))
1589 ),
1590 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
1591 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
1592 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
1593 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
1594 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
1595 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
1596 ),
1597 params(
1598 ("project_id" = String, Path),
1599 ("smtp_id" = String, Path),
1600 ("Idempotency-Key" = String, Header)
1601 ),
1602 security(("operator_api_key" = []))
1603)]
1604#[doc(hidden)]
1605pub fn test_smtp_configuration() {}
1606
1607control_path!(
1608 list_projects,
1609 get,
1610 "/v1/projects",
1611 ProjectList,
1612 "Projects",
1613 params(("belongs_to" = Option<String>, Query))
1614);
1615control_path!(
1616 create_project,
1617 post,
1618 "/v1/projects",
1619 Project,
1620 "Created or authoritatively replayed project",
1621 body = CreateProjectRequest,
1622 params(("Idempotency-Key" = String, Header))
1623);
1624control_path!(
1625 get_project,
1626 get,
1627 "/v1/projects/{project_id}",
1628 Project,
1629 "Project",
1630 params(("project_id" = String, Path))
1631);
1632control_path!(
1633 update_project,
1634 patch,
1635 "/v1/projects/{project_id}",
1636 Project,
1637 "Updated project",
1638 body = UpdateProjectRequest,
1639 params(("project_id" = String, Path))
1640);
1641control_path!(
1642 get_project_overview,
1643 get,
1644 "/v1/projects/{project_id}/overview",
1645 ProjectOverviewSummary,
1646 "Project resource overview",
1647 params(("project_id" = String, Path))
1648);
1649control_path!(
1650 get_project_policy,
1651 get,
1652 "/v1/projects/{project_id}/policy",
1653 ProjectPolicy,
1654 "Project policy",
1655 params(("project_id" = String, Path))
1656);
1657control_path!(
1658 update_project_policy,
1659 put,
1660 "/v1/projects/{project_id}/policy",
1661 ProjectPolicy,
1662 "Updated Project policy",
1663 body = UpdateProjectPolicyRequest,
1664 params(("project_id" = String, Path))
1665);
1666control_path!(
1667 disable_project,
1668 post,
1669 "/v1/projects/{project_id}/disable",
1670 Project,
1671 "Disabled project",
1672 body = ExpectedSecurityRevision,
1673 params(("project_id" = String, Path))
1674);
1675control_path!(
1676 list_project_server_keys,
1677 get,
1678 "/v1/projects/{project_id}/server-keys",
1679 ProjectServerKeyList,
1680 "Safe Project server-key metadata",
1681 params(
1682 ("project_id" = String, Path),
1683 ("cursor" = Option<String>, Query),
1684 ("limit" = Option<usize>, Query, minimum = 1, maximum = 100)
1685 )
1686);
1687control_path!(
1688 get_project_server_key,
1689 get,
1690 "/v1/projects/{project_id}/server-keys/{key_id}",
1691 ProjectServerKey,
1692 "Safe metadata for one Project server key",
1693 params(("project_id" = String, Path), ("key_id" = String, Path))
1694);
1695
1696#[utoipa::path(
1697 post,
1698 path = "/v1/projects/{project_id}/server-keys",
1699 request_body = CreateProjectServerKeyRequest,
1700 responses(
1701 (status = 201, description = "Created Project server key with one-time credential reveal", body = CreateProjectServerKeyResponse, headers(("Location" = String, description = "Exact Control path for the created Project server key"))),
1702 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
1703 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
1704 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
1705 (status = 409, description = "Unacknowledged delivery, key limit, idempotency conflict, or secret unavailable on replay", body = ProblemDetails),
1706 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
1707 (status = 503, description = "Required Server verifier fleet is not ready", body = ProblemDetails)
1708 ),
1709 params(
1710 ("project_id" = String, Path),
1711 ("Idempotency-Key" = String, Header)
1712 ),
1713 security(("operator_api_key" = []))
1714)]
1715#[doc(hidden)]
1716pub fn create_project_server_key() {}
1717
1718control_path!(
1719 acknowledge_project_server_key_delivery,
1720 post,
1721 "/v1/projects/{project_id}/server-keys/{key_id}/acknowledge",
1722 ProjectServerKey,
1723 "Project server-key delivery acknowledged",
1724 body = AcknowledgeProjectServerKeyDeliveryRequest,
1725 params(
1726 ("project_id" = String, Path),
1727 ("key_id" = String, Path),
1728 ("Idempotency-Key" = String, Header)
1729 )
1730);
1731control_path!(
1732 revoke_project_server_key,
1733 post,
1734 "/v1/projects/{project_id}/server-keys/{key_id}/revoke",
1735 ProjectServerKey,
1736 "Revoked Project server key",
1737 body = RevokeProjectServerKeyRequest,
1738 params(
1739 ("project_id" = String, Path),
1740 ("key_id" = String, Path),
1741 ("Idempotency-Key" = String, Header)
1742 )
1743);
1744control_path!(
1745 list_applications,
1746 get,
1747 "/v1/projects/{project_id}/applications",
1748 ApplicationList,
1749 "Applications",
1750 params(("project_id" = String, Path))
1751);
1752control_path!(
1753 create_application,
1754 post,
1755 "/v1/projects/{project_id}/applications",
1756 Application,
1757 "Created or authoritatively replayed application",
1758 body = CreateApplicationRequest,
1759 params(
1760 ("project_id" = String, Path),
1761 ("Idempotency-Key" = String, Header)
1762 )
1763);
1764control_path!(
1765 get_application,
1766 get,
1767 "/v1/projects/{project_id}/applications/{application_id}",
1768 Application,
1769 "Application",
1770 params(
1771 ("project_id" = String, Path),
1772 ("application_id" = String, Path)
1773 )
1774);
1775control_path!(
1776 update_application,
1777 patch,
1778 "/v1/projects/{project_id}/applications/{application_id}",
1779 Application,
1780 "Updated application",
1781 body = UpdateApplicationRequest,
1782 params(
1783 ("project_id" = String, Path),
1784 ("application_id" = String, Path)
1785 )
1786);
1787control_path!(
1788 replace_application_configuration,
1789 put,
1790 "/v1/projects/{project_id}/applications/{application_id}/configuration",
1791 Application,
1792 "Replaced exact application configuration",
1793 body = ReplaceApplicationConfigurationRequest,
1794 params(
1795 ("project_id" = String, Path),
1796 ("application_id" = String, Path)
1797 )
1798);
1799control_path!(
1800 disable_application,
1801 post,
1802 "/v1/projects/{project_id}/applications/{application_id}/disable",
1803 Application,
1804 "Disabled application",
1805 body = ExpectedSecurityRevision,
1806 params(
1807 ("project_id" = String, Path),
1808 ("application_id" = String, Path)
1809 )
1810);
1811control_path!(
1812 list_webhook_endpoints,
1813 get,
1814 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints",
1815 WebhookEndpointList,
1816 "Webhook endpoints",
1817 params(
1818 ("project_id" = String, Path),
1819 ("application_id" = String, Path)
1820 )
1821);
1822control_path!(
1823 create_webhook_endpoint,
1824 post,
1825 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints",
1826 WebhookEndpoint,
1827 "Created or authoritatively replayed pending webhook endpoint",
1828 body = CreateWebhookEndpointRequest,
1829 params(
1830 ("project_id" = String, Path),
1831 ("application_id" = String, Path),
1832 ("Idempotency-Key" = String, Header)
1833 )
1834);
1835control_path!(
1836 get_webhook_endpoint,
1837 get,
1838 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}",
1839 WebhookEndpoint,
1840 "Webhook endpoint",
1841 params(
1842 ("project_id" = String, Path),
1843 ("application_id" = String, Path),
1844 ("endpoint_id" = String, Path)
1845 )
1846);
1847control_path!(
1848 update_webhook_endpoint,
1849 put,
1850 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}",
1851 WebhookEndpoint,
1852 "Updated webhook endpoint subscriptions",
1853 body = UpdateWebhookEndpointRequest,
1854 params(
1855 ("project_id" = String, Path),
1856 ("application_id" = String, Path),
1857 ("endpoint_id" = String, Path)
1858 )
1859);
1860control_path!(
1861 test_webhook_endpoint,
1862 post,
1863 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/test",
1864 WebhookEndpoint,
1865 "Validated webhook endpoint DNS and destination policy",
1866 body = ExpectedWebhookEndpointRevision,
1867 params(
1868 ("project_id" = String, Path),
1869 ("application_id" = String, Path),
1870 ("endpoint_id" = String, Path)
1871 )
1872);
1873control_path!(
1874 activate_webhook_endpoint,
1875 post,
1876 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/activate",
1877 WebhookEndpoint,
1878 "Activated a tested webhook endpoint",
1879 body = ExpectedWebhookEndpointRevision,
1880 params(
1881 ("project_id" = String, Path),
1882 ("application_id" = String, Path),
1883 ("endpoint_id" = String, Path)
1884 )
1885);
1886control_path!(
1887 disable_webhook_endpoint,
1888 post,
1889 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/disable",
1890 WebhookEndpoint,
1891 "Disabled webhook endpoint",
1892 body = ExpectedWebhookEndpointRevision,
1893 params(
1894 ("project_id" = String, Path),
1895 ("application_id" = String, Path),
1896 ("endpoint_id" = String, Path)
1897 )
1898);
1899control_path!(
1900 prepare_webhook_secret_rotation,
1901 post,
1902 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/secret-rotations",
1903 PreparedWebhookSecretRotation,
1904 "Prepared a write-only webhook secret generation",
1905 body = PrepareWebhookSecretRotationRequest,
1906 params(
1907 ("project_id" = String, Path),
1908 ("application_id" = String, Path),
1909 ("endpoint_id" = String, Path),
1910 ("Idempotency-Key" = String, Header)
1911 )
1912);
1913control_path!(
1914 activate_webhook_secret_rotation,
1915 post,
1916 "/v1/projects/{project_id}/applications/{application_id}/webhook-endpoints/{endpoint_id}/secret-rotations/{generation}/activate",
1917 WebhookEndpoint,
1918 "Activated a prepared webhook secret generation",
1919 body = ActivateWebhookSecretRotationRequest,
1920 params(
1921 ("project_id" = String, Path),
1922 ("application_id" = String, Path),
1923 ("endpoint_id" = String, Path),
1924 ("generation" = i32, Path)
1925 )
1926);
1927control_path!(
1928 list_application_user_events,
1929 get,
1930 "/v1/projects/{project_id}/applications/{application_id}/user-events",
1931 ApplicationUserEventList,
1932 "Immutable Application user events",
1933 params(
1934 ("project_id" = String, Path),
1935 ("application_id" = String, Path),
1936 ("cursor" = Option<String>, Query),
1937 ("limit" = Option<usize>, Query, minimum = 1, maximum = 100)
1938 )
1939);
1940control_path!(
1941 list_webhook_deliveries,
1942 get,
1943 "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries",
1944 WebhookDeliveryList,
1945 "Webhook delivery history",
1946 params(
1947 ("project_id" = String, Path),
1948 ("application_id" = String, Path),
1949 ("endpoint_id" = Option<String>, Query),
1950 ("cursor" = Option<String>, Query),
1951 ("limit" = Option<usize>, Query, minimum = 1, maximum = 100)
1952 )
1953);
1954control_path!(
1955 get_webhook_delivery,
1956 get,
1957 "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries/{delivery_id}",
1958 WebhookDelivery,
1959 "One retained webhook delivery",
1960 params(
1961 ("project_id" = String, Path),
1962 ("application_id" = String, Path),
1963 ("delivery_id" = String, Path)
1964 )
1965);
1966#[utoipa::path(
1967 post,
1968 path = "/v1/projects/{project_id}/applications/{application_id}/webhook-deliveries/{delivery_id}/replay",
1969 request_body = ReplayWebhookDeliveryRequest,
1970 responses(
1971 (status = 201, description = "Created a new delivery for the same immutable event and endpoint", body = WebhookDelivery, headers(("Location" = String, description = "Exact Control path for the created webhook delivery"))),
1972 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
1973 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
1974 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
1975 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
1976 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
1977 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
1978 ),
1979 params(
1980 ("project_id" = String, Path),
1981 ("application_id" = String, Path),
1982 ("delivery_id" = String, Path)
1983 ),
1984 security(("operator_api_key" = []))
1985)]
1986#[doc(hidden)]
1987pub fn replay_webhook_delivery() {}
1988control_path!(
1989 list_signing_keys,
1990 get,
1991 "/v1/projects/{project_id}/signing-keys",
1992 SigningKeyList,
1993 "Signing keys",
1994 params(("project_id" = String, Path))
1995);
1996control_path!(
1997 rotate_signing_key,
1998 post,
1999 "/v1/projects/{project_id}/signing-keys/rotate",
2000 SigningKey,
2001 "Accepted a durable signing key rotation",
2002 body = RotateSigningKeyRequest,
2003 params(
2004 ("project_id" = String, Path),
2005 ("Idempotency-Key" = String, Header)
2006 )
2007);
2008control_path!(
2009 revoke_signing_key,
2010 post,
2011 "/v1/projects/{project_id}/signing-keys/{key_id}/revoke",
2012 SigningKey,
2013 "Emergency-revoked signing key",
2014 body = KeyTransitionRequest,
2015 params(("project_id" = String, Path), ("key_id" = String, Path))
2016);
2017control_path!(
2018 get_provider_egress_policy,
2019 get,
2020 "/v1/projects/{project_id}/provider-egress-policy",
2021 ProviderEgressPolicy,
2022 "Project Custom OIDC egress policy",
2023 params(("project_id" = String, Path))
2024);
2025control_path!(
2026 update_provider_egress_policy,
2027 put,
2028 "/v1/projects/{project_id}/provider-egress-policy",
2029 ProviderEgressPolicy,
2030 "Updated Project Custom OIDC egress policy",
2031 body = UpdateProviderEgressPolicyRequest,
2032 params(("project_id" = String, Path))
2033);
2034control_preflight_path!(
2035 preflight_oidc_provider,
2036 "/v1/projects/{project_id}/providers/oidc/preflight",
2037 OidcPreflightResult,
2038 "Advisory Custom OIDC discovery preflight",
2039 OidcPreflightRequest
2040);
2041control_preflight_path!(
2042 preflight_named_provider,
2043 "/v1/projects/{project_id}/providers/named/preflight",
2044 NamedProviderPreflightResult,
2045 "Advisory named-provider registration preflight",
2046 NamedProviderPreflightRequest
2047);
2048control_path!(
2049 list_providers,
2050 get,
2051 "/v1/projects/{project_id}/providers",
2052 ProviderList,
2053 "Providers",
2054 params(("project_id" = String, Path))
2055);
2056control_path!(
2057 create_provider,
2058 post,
2059 "/v1/projects/{project_id}/providers",
2060 Provider,
2061 "Configured or authoritatively replayed provider",
2062 body = CreateProviderRequest,
2063 params(
2064 ("project_id" = String, Path),
2065 ("Idempotency-Key" = String, Header)
2066 )
2067);
2068control_path!(
2069 update_provider,
2070 patch,
2071 "/v1/projects/{project_id}/providers/{provider_id}",
2072 Provider,
2073 "Updated provider metadata",
2074 body = UpdateProviderRequest,
2075 params(
2076 ("project_id" = String, Path),
2077 ("provider_id" = String, Path)
2078 )
2079);
2080control_path!(
2081 replace_provider_secret,
2082 post,
2083 "/v1/projects/{project_id}/providers/{provider_id}/replace-secret",
2084 Provider,
2085 "Replaced provider protected client secret",
2086 body = ReplaceProviderSecretRequest,
2087 params(
2088 ("project_id" = String, Path),
2089 ("provider_id" = String, Path),
2090 ("Idempotency-Key" = String, Header)
2091 )
2092);
2093control_path!(
2094 reconcile_provider_secret_replacement,
2095 post,
2096 "/v1/projects/{project_id}/providers/{provider_id}/replace-secret/reconcile",
2097 Provider,
2098 "Reconciled a pending provider protected-secret replacement",
2099 body = ReconcileProviderSecretReplacementRequest,
2100 params(
2101 ("project_id" = String, Path),
2102 ("provider_id" = String, Path)
2103 )
2104);
2105control_path!(
2106 abandon_provider_secret_replacement,
2107 post,
2108 "/v1/projects/{project_id}/providers/{provider_id}/replace-secret/abandon",
2109 Provider,
2110 "Abandoned a pending provider protected-secret replacement",
2111 body = ProviderRevisionRequest,
2112 params(
2113 ("project_id" = String, Path),
2114 ("provider_id" = String, Path)
2115 )
2116);
2117control_path!(
2118 reconcile_provider,
2119 post,
2120 "/v1/projects/{project_id}/providers/{provider_id}/reconcile",
2121 Provider,
2122 "Reconciled provider secret provisioning",
2123 body = ReconcileProviderRequest,
2124 params(
2125 ("project_id" = String, Path),
2126 ("provider_id" = String, Path)
2127 )
2128);
2129control_path!(
2130 disable_provider,
2131 post,
2132 "/v1/projects/{project_id}/providers/{provider_id}/disable",
2133 Provider,
2134 "Disabled provider",
2135 body = ProviderRevisionRequest,
2136 params(
2137 ("project_id" = String, Path),
2138 ("provider_id" = String, Path)
2139 )
2140);
2141control_path!(
2142 assign_provider,
2143 put,
2144 "/v1/projects/{project_id}/providers/{provider_id}/assignments/{application_id}",
2145 Provider,
2146 "Assigned provider",
2147 body = ProviderAssignmentRequest,
2148 params(
2149 ("project_id" = String, Path),
2150 ("provider_id" = String, Path),
2151 ("application_id" = String, Path)
2152 )
2153);
2154control_path!(
2155 unassign_provider,
2156 post,
2157 "/v1/projects/{project_id}/providers/{provider_id}/assignments/{application_id}/unassign",
2158 Provider,
2159 "Unassigned provider",
2160 body = ProviderAssignmentRequest,
2161 params(
2162 ("project_id" = String, Path),
2163 ("provider_id" = String, Path),
2164 ("application_id" = String, Path)
2165 )
2166);
2167
2168control_path!(
2169 get_email_method_policy,
2170 get,
2171 "/v1/projects/{project_id}/email-method",
2172 EmailMethodPolicy,
2173 "Passwordless email policy",
2174 params(("project_id" = String, Path))
2175);
2176control_path!(
2177 update_email_method_policy,
2178 put,
2179 "/v1/projects/{project_id}/email-method",
2180 EmailMethodPolicy,
2181 "Updated passwordless email policy",
2182 body = UpdateEmailMethodPolicyRequest,
2183 params(("project_id" = String, Path))
2184);
2185control_path!(
2186 list_email_assignments,
2187 get,
2188 "/v1/projects/{project_id}/email-method/assignments",
2189 EmailAssignmentList,
2190 "Application email assignments",
2191 params(("project_id" = String, Path))
2192);
2193control_path!(
2194 assign_email_method,
2195 put,
2196 "/v1/projects/{project_id}/applications/{application_id}/email-method",
2197 EmailMethodPolicy,
2198 "Updated Application email assignment",
2199 body = EmailAssignmentRequest,
2200 params(
2201 ("project_id" = String, Path),
2202 ("application_id" = String, Path)
2203 )
2204);
2205control_path!(
2206 list_deployment_smtp_generations,
2207 get,
2208 "/v1/system/smtp-default-generations",
2209 DeploymentSmtpGenerationList,
2210 "Deployment SMTP generations"
2211);
2212control_path!(
2213 reconcile_deployment_smtp_generation,
2214 post,
2215 "/v1/system/smtp-default-generations",
2216 DeploymentSmtpGeneration,
2217 "Reconciled deployment SMTP generation",
2218 body = ReconcileDeploymentSmtpRequest,
2219 params(("Idempotency-Key" = String, Header))
2220);
2221control_path!(
2222 disable_deployment_smtp_generation,
2223 post,
2224 "/v1/system/smtp-default-generations/{generation}/disable",
2225 DeploymentSmtpGeneration,
2226 "Disabled deployment SMTP generation",
2227 body = SmtpRevisionRequest,
2228 params(("generation" = i32, Path))
2229);
2230control_path!(
2231 compromise_deployment_smtp_generation,
2232 post,
2233 "/v1/system/smtp-default-generations/{generation}/compromise",
2234 DeploymentSmtpGeneration,
2235 "Compromised deployment SMTP generation",
2236 body = SmtpRevisionRequest,
2237 params(("generation" = i32, Path))
2238);
2239control_path!(
2240 list_smtp_configurations,
2241 get,
2242 "/v1/projects/{project_id}/smtp-configurations",
2243 SmtpConfigurationList,
2244 "SMTP generations",
2245 params(("project_id" = String, Path))
2246);
2247control_path!(
2248 create_smtp_configuration,
2249 post,
2250 "/v1/projects/{project_id}/smtp-configurations",
2251 SmtpConfiguration,
2252 "Created or authoritatively replayed pending SMTP generation",
2253 body = CreateSmtpConfigurationRequest,
2254 params(
2255 ("project_id" = String, Path),
2256 ("Idempotency-Key" = String, Header)
2257 )
2258);
2259control_path!(
2260 get_smtp_test_operation,
2261 get,
2262 "/v1/projects/{project_id}/smtp-configurations/{smtp_id}/tests/{operation_id}",
2263 SmtpTestOperation,
2264 "SMTP test operation status",
2265 params(
2266 ("project_id" = String, Path),
2267 ("smtp_id" = String, Path),
2268 ("operation_id" = String, Path)
2269 )
2270);
2271control_path!(
2272 activate_smtp_configuration,
2273 post,
2274 "/v1/projects/{project_id}/smtp-configurations/{smtp_id}/activate",
2275 SmtpConfiguration,
2276 "Activated SMTP generation",
2277 body = SmtpRevisionRequest,
2278 params(("project_id" = String, Path), ("smtp_id" = String, Path))
2279);
2280control_path!(
2281 disable_smtp_configuration,
2282 post,
2283 "/v1/projects/{project_id}/smtp-configurations/{smtp_id}/disable",
2284 SmtpConfiguration,
2285 "Disabled SMTP generation",
2286 body = SmtpRevisionRequest,
2287 params(("project_id" = String, Path), ("smtp_id" = String, Path))
2288);
2289control_path!(
2290 compromise_smtp_configuration,
2291 post,
2292 "/v1/projects/{project_id}/smtp-configurations/{smtp_id}/compromise",
2293 SmtpConfiguration,
2294 "Marked SMTP generation compromised",
2295 body = SmtpRevisionRequest,
2296 params(("project_id" = String, Path), ("smtp_id" = String, Path))
2297);
2298
2299control_path!(
2300 list_project_users,
2301 get,
2302 "/v1/projects/{project_id}/users",
2303 ProjectUserList,
2304 "Project users",
2305 params(
2306 ("project_id" = String, Path),
2307 ("status" = Option<ProjectUserStatus>, Query),
2308 ("search" = Option<String>, Query, min_length = 1, max_length = 128),
2309 ("identity_kind" = Option<ProjectUserIdentityFilter>, Query),
2310 ("provider_key" = Option<String>, Query, min_length = 1, max_length = 64),
2311 ("sort" = Option<ProjectUserSort>, Query),
2312 ("cursor" = Option<String>, Query, max_length = 36, description = "Cursor returned by a previous page with the same status, search, identity, provider, and sort criteria"),
2313 ("limit" = Option<usize>, Query, minimum = 1, maximum = 100)
2314 )
2315);
2316control_path!(
2317 lookup_project_user_by_email,
2318 post,
2319 "/v1/projects/{project_id}/users/lookup",
2320 ProjectUserLookup,
2321 "Exact canonical email Project user lookup",
2322 body = ProjectUserEmailLookupRequest,
2323 params(("project_id" = String, Path))
2324);
2325control_path!(
2326 get_project_user,
2327 get,
2328 "/v1/projects/{project_id}/users/{user_id}",
2329 ProjectUser,
2330 "Project user",
2331 params(("project_id" = String, Path), ("user_id" = String, Path))
2332);
2333control_path!(
2334 list_project_user_identities,
2335 get,
2336 "/v1/projects/{project_id}/users/{user_id}/identities",
2337 ProjectUserIdentityList,
2338 "Bounded safe mixed provider and email identity inventory",
2339 params(("project_id" = String, Path), ("user_id" = String, Path))
2340);
2341control_path!(
2342 disable_project_user,
2343 post,
2344 "/v1/projects/{project_id}/users/{user_id}/disable",
2345 ProjectUser,
2346 "Disabled Project user",
2347 body = ExpectedSecurityRevision,
2348 params(("project_id" = String, Path), ("user_id" = String, Path))
2349);
2350control_path!(
2351 enable_project_user,
2352 post,
2353 "/v1/projects/{project_id}/users/{user_id}/enable",
2354 ProjectUser,
2355 "Enabled Project user",
2356 body = ExpectedSecurityRevision,
2357 params(("project_id" = String, Path), ("user_id" = String, Path))
2358);
2359control_path!(
2360 list_project_user_sessions,
2361 get,
2362 "/v1/projects/{project_id}/users/{user_id}/sessions",
2363 ProjectUserSessions,
2364 "Project user sessions",
2365 params(("project_id" = String, Path), ("user_id" = String, Path))
2366);
2367control_path!(
2368 revoke_application_session,
2369 post,
2370 "/v1/projects/{project_id}/users/{user_id}/application-sessions/{session_id}/revoke",
2371 ApplicationSession,
2372 "Revoked Application session",
2373 body = ExpectedSessionRevision,
2374 params(
2375 ("project_id" = String, Path),
2376 ("user_id" = String, Path),
2377 ("session_id" = String, Path)
2378 )
2379);
2380control_path!(
2381 revoke_browser_session,
2382 post,
2383 "/v1/projects/{project_id}/users/{user_id}/browser-sessions/{session_id}/revoke",
2384 BrowserSession,
2385 "Revoked Project browser session",
2386 body = ExpectedSessionRevision,
2387 params(
2388 ("project_id" = String, Path),
2389 ("user_id" = String, Path),
2390 ("session_id" = String, Path)
2391 )
2392);
2393
2394#[cfg(test)]
2395mod tests {
2396 use serde_json::json;
2397
2398 use super::{
2399 CreateIdentityMutationIntentRequest, IdentityMutationProofAuthority,
2400 NamedProviderPreflightRequest, OidcPreflightRequest,
2401 };
2402
2403 #[test]
2404 fn provider_preflight_requests_reject_secret_and_callback_authority() {
2405 assert!(
2406 serde_json::from_value::<OidcPreflightRequest>(json!({
2407 "provider_key": "custom-main",
2408 "issuer": "https://identity.example",
2409 }))
2410 .is_ok()
2411 );
2412 for forbidden in ["client_secret", "callback_url", "project_public_id"] {
2413 let mut request = json!({
2414 "provider_key": "custom-main",
2415 "issuer": "https://identity.example",
2416 });
2417 request[forbidden] = json!("caller-owned");
2418 assert!(serde_json::from_value::<OidcPreflightRequest>(request).is_err());
2419 }
2420 for forbidden in ["client_secret", "callback_url", "issuer"] {
2421 let mut request = json!({
2422 "kind": "google",
2423 "provider_key": "google-main",
2424 });
2425 request[forbidden] = json!("caller-owned");
2426 assert!(serde_json::from_value::<NamedProviderPreflightRequest>(request).is_err());
2427 }
2428 }
2429
2430 #[test]
2431 fn user_and_session_lifecycle_contract_is_bounded_and_control_only() {
2432 let document = serde_json::to_value(crate::control::openapi())
2433 .expect("Control OpenAPI should serialize");
2434 for path in [
2435 "/v1/projects/{project_id}/users",
2436 "/v1/projects/{project_id}/users/{user_id}",
2437 "/v1/projects/{project_id}/users/{user_id}/disable",
2438 "/v1/projects/{project_id}/users/{user_id}/sessions",
2439 "/v1/projects/{project_id}/users/{user_id}/application-sessions/{session_id}/revoke",
2440 "/v1/projects/{project_id}/users/{user_id}/browser-sessions/{session_id}/revoke",
2441 ] {
2442 assert!(document["paths"][path].is_object(), "missing path: {path}");
2443 }
2444 let user = &document["components"]["schemas"]["ProjectUser"]["properties"];
2445 assert!(user.get("provider_credentials").is_none());
2446 assert!(user.get("source_payload").is_none());
2447 assert_eq!(
2448 document["components"]["schemas"]["ProjectUserList"]["properties"]["items"]["maxItems"],
2449 100
2450 );
2451 }
2452
2453 #[test]
2454 fn identity_mutation_control_contract_is_typed_and_safe() {
2455 let document = serde_json::to_value(crate::control::openapi())
2456 .expect("Control OpenAPI should serialize");
2457 let collection = "/v1/projects/{project_id}/identity-mutation-intents";
2458 for path in [
2459 collection,
2460 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}",
2461 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}/cancel",
2462 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}/confirm",
2463 ] {
2464 assert!(document["paths"][path].is_object(), "missing path: {path}");
2465 }
2466 assert!(
2467 document["paths"][collection]["post"]["parameters"]
2468 .as_array()
2469 .expect("create parameters")
2470 .iter()
2471 .any(|parameter| parameter["name"] == "Idempotency-Key")
2472 );
2473
2474 let intent = &document["components"]["schemas"]["IdentityMutationIntent"];
2475 assert_eq!(intent["properties"]["slots"]["maxItems"], 2);
2476 for forbidden in [
2477 "raw_state",
2478 "receipt",
2479 "evidence",
2480 "subject",
2481 "email",
2482 "address",
2483 "provider_secret",
2484 ] {
2485 assert!(intent["properties"].get(forbidden).is_none());
2486 }
2487 let create_schema =
2488 &document["components"]["schemas"]["CreateIdentityMutationIntentRequest"];
2489 let variants = create_schema["oneOf"]
2490 .as_array()
2491 .expect("typed create variants");
2492 assert_eq!(variants.len(), 3);
2493 assert!(
2494 document["paths"]["/v1/projects/{project_id}/identity-mutation-intents"]
2495 ["post"]["responses"]
2496 .get("201")
2497 .is_some()
2498 );
2499 assert!(
2500 document["paths"]["/v1/projects/{project_id}/identity-mutation-intents"]
2501 ["post"]["responses"]
2502 .get("200")
2503 .is_some()
2504 );
2505 assert!(
2506 variants
2507 .iter()
2508 .all(|variant| variant["properties"].get("slots").is_none())
2509 );
2510 }
2511
2512 #[test]
2513 fn identity_mutation_commands_reject_caller_derived_authority() {
2514 let link = json!({
2515 "operation_kind": "link",
2516 "destination": {
2517 "user_id": "user-1",
2518 "expected_user_revision": 3,
2519 "expected_user_security_revision": 4
2520 },
2521 "destination_identity": {
2522 "identity_kind": "provider",
2523 "identity_id": "identity-1",
2524 "expected_identity_revision": 5
2525 },
2526 "candidate_identity_kind": "email",
2527 "destination_proof_authority": {
2528 "method_kind": "provider",
2529 "application_id": "application-1",
2530 "provider_id": "provider-1"
2531 },
2532 "candidate_proof_authority": {
2533 "method_kind": "email",
2534 "application_id": "application-1"
2535 },
2536 "slots": []
2537 });
2538 assert!(serde_json::from_value::<CreateIdentityMutationIntentRequest>(link).is_err());
2539
2540 let authority = json!({
2541 "method_kind": "provider",
2542 "application_id": "application-1",
2543 "provider_id": "provider-1",
2544 "scopes": ["openid"],
2545 "callback": "https://attacker.example/callback"
2546 });
2547 assert!(serde_json::from_value::<IdentityMutationProofAuthority>(authority).is_err());
2548 }
2549}
2550
2551control_path!(
2552 list_managed_provider_connections,
2553 get,
2554 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections",
2555 ManagedProviderConnectionList,
2556 "Safe managed provider connection metadata",
2557 params(("project_id" = String, Path), ("user_id" = String, Path))
2558);
2559control_path!(
2560 synchronize_managed_provider_connection,
2561 post,
2562 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/synchronize",
2563 ManagedProviderConnection,
2564 "Scheduled guarded profile synchronization",
2565 body = ManagedProviderConnectionActionRequest,
2566 params(
2567 ("project_id" = String, Path),
2568 ("user_id" = String, Path),
2569 ("connection_id" = String, Path)
2570 )
2571);
2572#[utoipa::path(
2573 post,
2574 path = "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/reauthorizations",
2575 request_body = CreateManagedReauthorizationRequest,
2576 responses(
2577 (status = 201, description = "Created one exact managed reauthorization interaction", body = CreateManagedReauthorizationResponse, headers(("Location" = String, description = "Exact Control path for the created managed reauthorization"))),
2578 (status = 200, description = "Authoritative idempotent replay of the managed reauthorization", body = CreateManagedReauthorizationResponse),
2579 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
2580 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
2581 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
2582 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
2583 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
2584 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
2585 ),
2586 params(
2587 ("project_id" = String, Path),
2588 ("user_id" = String, Path),
2589 ("connection_id" = String, Path),
2590 ("Idempotency-Key" = String, Header)
2591 ),
2592 security(("operator_api_key" = []))
2593)]
2594#[doc(hidden)]
2595pub fn create_managed_reauthorization() {}
2596control_path!(
2597 get_managed_reauthorization,
2598 get,
2599 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/reauthorizations/{interaction_id}",
2600 ManagedReauthorization,
2601 "Read bounded managed reauthorization status without Hosted target",
2602 params(
2603 ("project_id" = String, Path),
2604 ("user_id" = String, Path),
2605 ("connection_id" = String, Path),
2606 ("interaction_id" = String, Path)
2607 )
2608);
2609control_path!(
2610 cancel_managed_reauthorization,
2611 post,
2612 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/reauthorizations/{interaction_id}/cancel",
2613 ManagedReauthorization,
2614 "Cancelled one current managed reauthorization interaction",
2615 body = CancelManagedReauthorizationRequest,
2616 params(
2617 ("project_id" = String, Path),
2618 ("user_id" = String, Path),
2619 ("connection_id" = String, Path),
2620 ("interaction_id" = String, Path)
2621 )
2622);
2623control_path!(
2624 revoke_managed_provider_connection,
2625 post,
2626 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/revoke",
2627 ManagedProviderConnection,
2628 "Provider revocation when the adapter can prove it",
2629 body = ManagedProviderConnectionActionRequest,
2630 params(
2631 ("project_id" = String, Path),
2632 ("user_id" = String, Path),
2633 ("connection_id" = String, Path)
2634 )
2635);
2636control_path!(
2637 disconnect_managed_provider_connection,
2638 post,
2639 "/v1/projects/{project_id}/users/{user_id}/managed-provider-connections/{connection_id}/disconnect",
2640 ManagedProviderConnection,
2641 "Locally disconnected and destroyed renewable credential",
2642 body = ManagedProviderConnectionActionRequest,
2643 params(
2644 ("project_id" = String, Path),
2645 ("user_id" = String, Path),
2646 ("connection_id" = String, Path)
2647 )
2648);
2649
2650#[utoipa::path(
2651 post,
2652 path = "/v1/projects/{project_id}/identity-mutation-intents",
2653 request_body = CreateIdentityMutationIntentRequest,
2654 responses(
2655 (status = 201, description = "Created one typed identity mutation intent", body = CreateIdentityMutationIntentResponse, headers(("Location" = String, description = "Exact Control path for the created identity mutation intent"))),
2656 (status = 200, description = "Authoritative idempotent replay of the identity mutation intent", body = CreateIdentityMutationIntentResponse),
2657 (status = 400, description = "Invalid request", body = ProblemDetails, content_type = "application/problem+json"),
2658 (status = 401, description = "Missing or invalid operator API key", body = ProblemDetails, content_type = "application/problem+json", headers(("WWW-Authenticate" = String, description = "Bearer authentication challenge"))),
2659 (status = 404, description = "Resource not found", body = ProblemDetails, content_type = "application/problem+json"),
2660 (status = 409, description = "Revision, state, idempotency, or capacity conflict", body = ProblemDetails, content_type = "application/problem+json"),
2661 (status = 500, description = "Stored authority data violated an invariant", body = ProblemDetails, content_type = "application/problem+json"),
2662 (status = 503, description = "Required authority unavailable", body = ProblemDetails, content_type = "application/problem+json")
2663 ),
2664 params(
2665 ("project_id" = String, Path),
2666 ("Idempotency-Key" = String, Header)
2667 ),
2668 security(("operator_api_key" = []))
2669)]
2670#[doc(hidden)]
2671pub fn create_identity_mutation_intent() {}
2672control_path!(
2673 get_identity_mutation_intent,
2674 get,
2675 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}",
2676 IdentityMutationIntent,
2677 "Read safe identity mutation intent readiness",
2678 params(("project_id" = String, Path), ("intent_id" = String, Path))
2679);
2680control_path!(
2681 cancel_identity_mutation_intent,
2682 post,
2683 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}/cancel",
2684 IdentityMutationIntent,
2685 "Cancelled one current identity mutation intent",
2686 body = CancelIdentityMutationIntentRequest,
2687 params(("project_id" = String, Path), ("intent_id" = String, Path))
2688);
2689control_path!(
2690 confirm_identity_mutation_intent,
2691 post,
2692 "/v1/projects/{project_id}/identity-mutation-intents/{intent_id}/confirm",
2693 IdentityMutationIntent,
2694 "Confirmed one ready identity mutation intent",
2695 body = ConfirmIdentityMutationIntentRequest,
2696 params(("project_id" = String, Path), ("intent_id" = String, Path))
2697);
2698
2699#[cfg(test)]
2700mod project_server_key_contract_tests {
2701 use super::*;
2702
2703 fn metadata() -> ProjectServerKey {
2704 ProjectServerKey {
2705 id: "00000000-0000-0000-0000-000000000001".to_owned(),
2706 project_id: "00000000-0000-0000-0000-000000000002".to_owned(),
2707 public_key_id: "AAAAAAAAAAAAAAAAAAAAAA".to_owned(),
2708 label: "production backend".to_owned(),
2709 status: ProjectServerKeyStatus::Active,
2710 digest_key_version: 1,
2711 display_prefix: "owl_server_v1.AAAAAAAAAAAAAAAAAAAAAA".to_owned(),
2712 revision: 1,
2713 created_at: "2026-08-05T00:00:00Z".to_owned(),
2714 credential_acknowledged_at: None,
2715 last_used_at: None,
2716 revoked_at: None,
2717 }
2718 }
2719
2720 #[test]
2721 fn one_time_credential_is_redacted_from_debug() {
2722 let response = CreateProjectServerKeyResponse {
2723 key: metadata(),
2724 credential: format!("owl_server_v1.AAAAAAAAAAAAAAAAAAAAAA.{}", "B".repeat(43)),
2725 };
2726 let debug = format!("{response:?}");
2727 assert!(debug.contains("[REDACTED]"));
2728 assert!(!debug.contains(&"B".repeat(43)));
2729 }
2730
2731 #[test]
2732 fn acknowledgement_status_is_explicit_and_required_in_inventory() {
2733 let mut encoded = serde_json::to_value(metadata()).expect("server-key metadata");
2734 encoded
2735 .as_object_mut()
2736 .expect("server-key metadata object")
2737 .remove("credential_acknowledged_at");
2738 assert!(serde_json::from_value::<ProjectServerKey>(encoded).is_err());
2739
2740 assert!(
2741 serde_json::from_value::<ProjectServerKeyList>(serde_json::json!({
2742 "items": [],
2743 "next_cursor": null
2744 }))
2745 .is_err()
2746 );
2747 assert!(
2748 serde_json::from_value::<ProjectServerKeyList>(serde_json::json!({
2749 "items": [],
2750 "next_cursor": null,
2751 "active_unacknowledged_key": null
2752 }))
2753 .is_ok()
2754 );
2755 }
2756
2757 #[test]
2758 fn lifecycle_commands_reject_unknown_authority_fields() {
2759 assert!(
2760 serde_json::from_value::<CreateProjectServerKeyRequest>(serde_json::json!({
2761 "label": "backend",
2762 "scopes": ["users:read"]
2763 }))
2764 .is_err()
2765 );
2766 assert!(
2767 serde_json::from_value::<RevokeProjectServerKeyRequest>(serde_json::json!({
2768 "expected_revision": 1,
2769 "confirm": true,
2770 "enable": false
2771 }))
2772 .is_err()
2773 );
2774 assert!(
2775 serde_json::from_value::<AcknowledgeProjectServerKeyDeliveryRequest>(
2776 serde_json::json!({
2777 "expected_revision": 1,
2778 "confirm_stored": true,
2779 "credential": "must-never-be-accepted"
2780 })
2781 )
2782 .is_err()
2783 );
2784 }
2785}