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