Skip to main content

postrust_proxy/saas/
types.rs

1//! Type definitions for the SaaS domain management module.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::net::IpAddr;
7use uuid::Uuid;
8
9/// Tenant status.
10#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
11#[serde(rename_all = "snake_case")]
12#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
13pub enum TenantStatus {
14    /// Active tenant
15    #[default]
16    Active,
17    /// Suspended tenant
18    Suspended,
19    /// Pending activation
20    Pending,
21}
22
23/// A SaaS tenant (customer).
24#[derive(Clone, Debug, Serialize, Deserialize)]
25pub struct Tenant {
26    /// Primary key.
27    pub id: Uuid,
28    /// Display name, for humans.
29    pub name: String,
30    /// Short URL-safe identifier, unique across tenants.
31    pub slug: String,
32    /// Contact address for this tenant.
33    pub email: String,
34    /// Whether the tenant may act. A suspended tenant keeps its data.
35    pub status: TenantStatus,
36    /// Plan name. Free-form: quotas are the fields below, not this string.
37    pub plan: String,
38    /// How many domains this tenant may register.
39    pub max_domains: i32,
40    /// How many routes each of its domains may have.
41    pub max_routes_per_domain: i32,
42    /// How many upstreams this tenant may define.
43    pub max_upstreams: i32,
44    /// How many backends each of its upstreams may have.
45    pub max_backends_per_upstream: i32,
46    /// How many API keys this tenant may hold, revoked ones included.
47    pub max_api_keys: i32,
48    /// Arbitrary per-tenant settings, untouched by the proxy.
49    pub settings: serde_json::Value,
50    /// When the tenant was created.
51    pub created_at: DateTime<Utc>,
52    /// When it last changed.
53    pub updated_at: DateTime<Utc>,
54}
55
56/// Database row for tenant.
57///
58/// The `*Row` types in this module are `pub(crate)`: they mirror the schema
59/// column for column and exist only so `sqlx::query_as` has something to decode
60/// into. Making them public would freeze the schema under semver, which is a
61/// promise about the database rather than about the API.
62#[derive(Clone, Debug, sqlx::FromRow)]
63pub(crate) struct TenantRow {
64    pub(crate) id: Uuid,
65    pub(crate) name: String,
66    pub(crate) slug: String,
67    pub(crate) email: String,
68    pub(crate) status: String,
69    pub(crate) plan: String,
70    pub(crate) max_domains: i32,
71    pub(crate) max_routes_per_domain: i32,
72    pub(crate) max_upstreams: i32,
73    pub(crate) max_backends_per_upstream: i32,
74    pub(crate) max_api_keys: i32,
75    pub(crate) settings: serde_json::Value,
76    pub(crate) created_at: DateTime<Utc>,
77    pub(crate) updated_at: DateTime<Utc>,
78}
79
80impl From<TenantRow> for Tenant {
81    fn from(row: TenantRow) -> Self {
82        Self {
83            id: row.id,
84            name: row.name,
85            slug: row.slug,
86            email: row.email,
87            status: match row.status.as_str() {
88                "suspended" => TenantStatus::Suspended,
89                "pending" => TenantStatus::Pending,
90                _ => TenantStatus::Active,
91            },
92            plan: row.plan,
93            max_domains: row.max_domains,
94            max_routes_per_domain: row.max_routes_per_domain,
95            max_upstreams: row.max_upstreams,
96            max_backends_per_upstream: row.max_backends_per_upstream,
97            max_api_keys: row.max_api_keys,
98            settings: row.settings,
99            created_at: row.created_at,
100            updated_at: row.updated_at,
101        }
102    }
103}
104
105/// Domain verification status.
106#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
107#[serde(rename_all = "snake_case")]
108#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
109pub enum VerificationStatus {
110    /// Pending verification
111    #[default]
112    Pending,
113    /// Successfully verified
114    Verified,
115    /// Verification failed
116    Failed,
117    /// Verification expired
118    Expired,
119}
120
121/// Domain verification method.
122#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
123#[serde(rename_all = "snake_case")]
124#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
125pub enum VerificationMethod {
126    /// DNS TXT record verification
127    #[default]
128    Dns,
129    /// HTTP file verification
130    Http,
131}
132
133/// SSL certificate status.
134#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
135#[serde(rename_all = "snake_case")]
136#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
137pub enum SslStatus {
138    /// Pending provisioning
139    #[default]
140    Pending,
141    /// Currently provisioning
142    Provisioning,
143    /// Certificate active
144    Active,
145    /// Provisioning failed
146    Failed,
147    /// Certificate expired
148    Expired,
149}
150
151/// SSL provider type.
152#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
153#[serde(rename_all = "snake_case")]
154#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
155pub enum SslProvider {
156    /// Automatic via ACME/Let's Encrypt
157    #[default]
158    Acme,
159    /// Manual certificate upload
160    Manual,
161    /// No SSL
162    None,
163}
164
165/// A custom domain.
166#[derive(Clone, Debug, Serialize, Deserialize)]
167pub struct Domain {
168    /// Primary key.
169    pub id: Uuid,
170    /// The tenant that owns this domain.
171    pub tenant_id: Uuid,
172    /// The name itself, globally unique across all tenants. Not updatable: it
173    /// is what [`Self::verification_token`] proves control of.
174    pub domain: String,
175    /// Whether ownership has been proved.
176    pub verification_status: VerificationStatus,
177    /// How ownership is to be proved. DNS is the default, and the sound one.
178    pub verification_method: VerificationMethod,
179    /// The secret the challenge carries. Bearer-equivalent for this domain.
180    pub verification_token: String,
181    /// How many verification attempts have been made.
182    pub verification_attempts: i32,
183    /// When verification succeeded, if it has.
184    pub verified_at: Option<DateTime<Utc>>,
185    /// When verification was last attempted.
186    pub last_verification_attempt: Option<DateTime<Utc>>,
187    /// Where the certificate has got to. `pending` is the issuance worker's
188    /// inbox.
189    pub ssl_status: SslStatus,
190    /// Where the certificate comes from.
191    pub ssl_provider: SslProvider,
192    /// When the current certificate expires, if there is one.
193    pub ssl_expires_at: Option<DateTime<Utc>>,
194    /// Whether the proxy should serve this domain. Set independently of
195    /// verification, so a verified domain can be taken out of service.
196    pub enabled: bool,
197    /// When the domain was registered.
198    pub created_at: DateTime<Utc>,
199    /// When it last changed.
200    pub updated_at: DateTime<Utc>,
201}
202
203/// Database row for domain with string types.
204#[derive(Clone, Debug, sqlx::FromRow)]
205pub(crate) struct DomainRow {
206    pub(crate) id: Uuid,
207    pub(crate) tenant_id: Uuid,
208    pub(crate) domain: String,
209    pub(crate) verification_status: String,
210    pub(crate) verification_method: String,
211    pub(crate) verification_token: String,
212    pub(crate) verification_attempts: i32,
213    pub(crate) verified_at: Option<DateTime<Utc>>,
214    pub(crate) last_verification_attempt: Option<DateTime<Utc>>,
215    pub(crate) ssl_status: String,
216    pub(crate) ssl_provider: String,
217    pub(crate) ssl_expires_at: Option<DateTime<Utc>>,
218    pub(crate) enabled: bool,
219    pub(crate) created_at: DateTime<Utc>,
220    pub(crate) updated_at: DateTime<Utc>,
221}
222
223impl From<DomainRow> for Domain {
224    fn from(row: DomainRow) -> Self {
225        Self {
226            id: row.id,
227            tenant_id: row.tenant_id,
228            domain: row.domain,
229            verification_status: match row.verification_status.as_str() {
230                "verified" => VerificationStatus::Verified,
231                "failed" => VerificationStatus::Failed,
232                "expired" => VerificationStatus::Expired,
233                _ => VerificationStatus::Pending,
234            },
235            verification_method: match row.verification_method.as_str() {
236                "http" => VerificationMethod::Http,
237                _ => VerificationMethod::Dns,
238            },
239            verification_token: row.verification_token,
240            verification_attempts: row.verification_attempts,
241            verified_at: row.verified_at,
242            last_verification_attempt: row.last_verification_attempt,
243            ssl_status: match row.ssl_status.as_str() {
244                "provisioning" => SslStatus::Provisioning,
245                "active" => SslStatus::Active,
246                "failed" => SslStatus::Failed,
247                "expired" => SslStatus::Expired,
248                _ => SslStatus::Pending,
249            },
250            ssl_provider: match row.ssl_provider.as_str() {
251                "manual" => SslProvider::Manual,
252                "none" => SslProvider::None,
253                _ => SslProvider::Acme,
254            },
255            ssl_expires_at: row.ssl_expires_at,
256            enabled: row.enabled,
257            created_at: row.created_at,
258            updated_at: row.updated_at,
259        }
260    }
261}
262
263/// Path matching type.
264#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
265#[serde(rename_all = "snake_case")]
266#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
267pub enum DomainPathMatchType {
268    /// Prefix matching (default)
269    #[default]
270    Prefix,
271    /// Exact matching
272    Exact,
273    /// Regex matching
274    Regex,
275}
276
277/// A domain route.
278#[derive(Clone, Debug, Serialize, Deserialize)]
279pub struct DomainRoute {
280    /// Primary key.
281    pub id: Uuid,
282    /// The domain this route belongs to.
283    pub domain_id: Uuid,
284    /// The owning tenant, denormalised so a route can be authorised without
285    /// joining to its domain.
286    pub tenant_id: Uuid,
287    /// Name, unique within the domain.
288    pub name: String,
289    /// The path to match, read according to [`Self::path_type`].
290    pub path_pattern: String,
291    /// How to read [`Self::path_pattern`].
292    pub path_type: DomainPathMatchType,
293    /// Methods to match. `None` or empty means any.
294    pub methods: Option<Vec<String>>,
295    /// Higher is matched first.
296    pub priority: i32,
297    /// The upstream to forward to.
298    pub upstream_id: Option<Uuid>,
299    /// Whether to remove the matched prefix before forwarding.
300    pub strip_path: bool,
301    /// Headers to add to the forwarded request.
302    pub add_headers: HashMap<String, String>,
303    /// Headers to remove from the forwarded request.
304    pub remove_headers: Vec<String>,
305    /// Requests allowed per window. Set with
306    /// [`Self::rate_limit_window_secs`] or not at all.
307    pub rate_limit_requests: Option<i32>,
308    /// The window those requests are counted over, in seconds.
309    pub rate_limit_window_secs: Option<i32>,
310    /// Request timeout in seconds.
311    pub timeout_secs: i32,
312    /// Whether this route participates in matching.
313    pub enabled: bool,
314    /// When the route was created.
315    pub created_at: DateTime<Utc>,
316    /// When it last changed.
317    pub updated_at: DateTime<Utc>,
318}
319
320/// Database row for domain route.
321#[derive(Clone, Debug, sqlx::FromRow)]
322pub(crate) struct DomainRouteRow {
323    pub(crate) id: Uuid,
324    pub(crate) domain_id: Uuid,
325    pub(crate) tenant_id: Uuid,
326    pub(crate) name: String,
327    pub(crate) path_pattern: String,
328    pub(crate) path_type: String,
329    pub(crate) methods: Option<Vec<String>>,
330    pub(crate) priority: i32,
331    pub(crate) upstream_id: Option<Uuid>,
332    pub(crate) strip_path: bool,
333    pub(crate) add_headers: serde_json::Value,
334    pub(crate) remove_headers: Vec<String>,
335    pub(crate) rate_limit_requests: Option<i32>,
336    pub(crate) rate_limit_window_secs: Option<i32>,
337    pub(crate) timeout_secs: i32,
338    pub(crate) enabled: bool,
339    pub(crate) created_at: DateTime<Utc>,
340    pub(crate) updated_at: DateTime<Utc>,
341}
342
343impl From<DomainRouteRow> for DomainRoute {
344    fn from(row: DomainRouteRow) -> Self {
345        let add_headers: HashMap<String, String> =
346            serde_json::from_value(row.add_headers).unwrap_or_default();
347
348        Self {
349            id: row.id,
350            domain_id: row.domain_id,
351            tenant_id: row.tenant_id,
352            name: row.name,
353            path_pattern: row.path_pattern,
354            path_type: match row.path_type.as_str() {
355                "exact" => DomainPathMatchType::Exact,
356                "regex" => DomainPathMatchType::Regex,
357                _ => DomainPathMatchType::Prefix,
358            },
359            methods: row.methods,
360            priority: row.priority,
361            upstream_id: row.upstream_id,
362            strip_path: row.strip_path,
363            add_headers,
364            remove_headers: row.remove_headers,
365            rate_limit_requests: row.rate_limit_requests,
366            rate_limit_window_secs: row.rate_limit_window_secs,
367            timeout_secs: row.timeout_secs,
368            enabled: row.enabled,
369            created_at: row.created_at,
370            updated_at: row.updated_at,
371        }
372    }
373}
374
375/// Load balancing strategy.
376#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, sqlx::Type)]
377#[serde(rename_all = "snake_case")]
378#[sqlx(type_name = "VARCHAR", rename_all = "snake_case")]
379pub enum DomainLoadBalanceStrategy {
380    /// Round-robin (default)
381    #[default]
382    RoundRobin,
383    /// Least connections
384    LeastConnections,
385    /// Weighted
386    Weighted,
387    /// Random
388    Random,
389    /// Sticky (cookie-based)
390    Sticky,
391}
392
393/// A domain upstream (backend server group).
394#[derive(Clone, Debug, Serialize, Deserialize)]
395pub struct DomainUpstream {
396    /// Primary key.
397    pub id: Uuid,
398    /// The owning tenant.
399    pub tenant_id: Uuid,
400    /// Name, unique within the tenant. Routes reference it.
401    pub name: String,
402    /// How requests are spread across the backends.
403    pub lb_strategy: DomainLoadBalanceStrategy,
404    /// Whether backends are health-checked.
405    pub health_check_enabled: bool,
406    /// The path health checks request.
407    pub health_check_path: String,
408    /// Seconds between checks.
409    pub health_check_interval_secs: i32,
410    /// Seconds a single check may take.
411    pub health_check_timeout_secs: i32,
412    /// Consecutive successes before a backend is used again.
413    pub healthy_threshold: i32,
414    /// Consecutive failures before a backend is taken out.
415    pub unhealthy_threshold: i32,
416    /// Whether this upstream may receive traffic.
417    pub enabled: bool,
418    /// When the upstream was created.
419    pub created_at: DateTime<Utc>,
420    /// When it last changed.
421    pub updated_at: DateTime<Utc>,
422    /// Backends (loaded separately)
423    #[serde(default)]
424    pub backends: Vec<DomainBackend>,
425}
426
427/// Database row for domain upstream.
428#[derive(Clone, Debug, sqlx::FromRow)]
429pub(crate) struct DomainUpstreamRow {
430    pub(crate) id: Uuid,
431    pub(crate) tenant_id: Uuid,
432    pub(crate) name: String,
433    pub(crate) lb_strategy: String,
434    pub(crate) health_check_enabled: bool,
435    pub(crate) health_check_path: String,
436    pub(crate) health_check_interval_secs: i32,
437    pub(crate) health_check_timeout_secs: i32,
438    pub(crate) healthy_threshold: i32,
439    pub(crate) unhealthy_threshold: i32,
440    pub(crate) enabled: bool,
441    pub(crate) created_at: DateTime<Utc>,
442    pub(crate) updated_at: DateTime<Utc>,
443}
444
445impl From<DomainUpstreamRow> for DomainUpstream {
446    fn from(row: DomainUpstreamRow) -> Self {
447        Self {
448            id: row.id,
449            tenant_id: row.tenant_id,
450            name: row.name,
451            lb_strategy: match row.lb_strategy.as_str() {
452                "least_connections" => DomainLoadBalanceStrategy::LeastConnections,
453                "weighted" => DomainLoadBalanceStrategy::Weighted,
454                "random" => DomainLoadBalanceStrategy::Random,
455                "sticky" => DomainLoadBalanceStrategy::Sticky,
456                _ => DomainLoadBalanceStrategy::RoundRobin,
457            },
458            health_check_enabled: row.health_check_enabled,
459            health_check_path: row.health_check_path,
460            health_check_interval_secs: row.health_check_interval_secs,
461            health_check_timeout_secs: row.health_check_timeout_secs,
462            healthy_threshold: row.healthy_threshold,
463            unhealthy_threshold: row.unhealthy_threshold,
464            enabled: row.enabled,
465            created_at: row.created_at,
466            updated_at: row.updated_at,
467            backends: Vec::new(),
468        }
469    }
470}
471
472/// A backend server.
473#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
474pub struct DomainBackend {
475    /// Primary key.
476    pub id: Uuid,
477    /// The upstream this backend belongs to.
478    pub upstream_id: Uuid,
479    /// `host:port`.
480    pub address: String,
481    /// `http` or `https`.
482    pub scheme: String,
483    /// Relative share of traffic under a weighted strategy.
484    pub weight: i32,
485    /// Whether this backend may receive traffic.
486    pub enabled: bool,
487    /// When the backend was added.
488    pub created_at: DateTime<Utc>,
489}
490
491/// An API key for tenant authentication.
492#[derive(Clone, Debug, Serialize, Deserialize)]
493pub struct ApiKey {
494    /// Primary key.
495    pub id: Uuid,
496    /// The tenant this key authenticates.
497    pub tenant_id: Uuid,
498    /// Label, for telling keys apart.
499    pub name: String,
500    /// The key itself, **present only in the response that created it**.
501    ///
502    /// Only a hash is stored, so this cannot be shown again. A caller that does
503    /// not keep it has to create another key.
504    #[serde(skip_serializing_if = "Option::is_none")]
505    pub key: Option<String>,
506    /// The first few characters, enough to identify a key in a log without
507    /// being enough to use it.
508    pub key_prefix: String,
509    /// What this key may do, as `resource:action` strings.
510    pub scopes: Vec<String>,
511    /// When the key was last used to authenticate.
512    pub last_used_at: Option<DateTime<Utc>>,
513    /// When the key stops working, if it ever does.
514    pub expires_at: Option<DateTime<Utc>>,
515    /// Whether the key is accepted. Revoking sets this rather than deleting,
516    /// so `last_used_at` survives for an audit.
517    pub enabled: bool,
518    /// When the key was created.
519    pub created_at: DateTime<Utc>,
520}
521
522/// Database row for API key.
523#[derive(Clone, Debug, sqlx::FromRow)]
524pub(crate) struct ApiKeyRow {
525    pub(crate) id: Uuid,
526    pub(crate) tenant_id: Uuid,
527    pub(crate) name: String,
528    pub(crate) key_prefix: String,
529    pub(crate) scopes: Vec<String>,
530    pub(crate) last_used_at: Option<DateTime<Utc>>,
531    pub(crate) expires_at: Option<DateTime<Utc>>,
532    pub(crate) enabled: bool,
533    pub(crate) created_at: DateTime<Utc>,
534}
535
536impl From<ApiKeyRow> for ApiKey {
537    fn from(row: ApiKeyRow) -> Self {
538        Self {
539            id: row.id,
540            tenant_id: row.tenant_id,
541            name: row.name,
542            key: None, // Never expose the key after creation
543            key_prefix: row.key_prefix,
544            scopes: row.scopes,
545            last_used_at: row.last_used_at,
546            expires_at: row.expires_at,
547            enabled: row.enabled,
548            created_at: row.created_at,
549        }
550    }
551}
552
553/// Verification challenge status.
554#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
555#[serde(rename_all = "snake_case")]
556pub enum ChallengeStatus {
557    /// Pending verification
558    #[default]
559    Pending,
560    /// Currently checking
561    Checking,
562    /// Successfully verified
563    Verified,
564    /// Verification failed
565    Failed,
566}
567
568/// A verification challenge.
569#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
570pub struct VerificationChallenge {
571    /// Primary key.
572    pub id: Uuid,
573    /// The domain whose ownership this challenge proves.
574    pub domain_id: Uuid,
575    /// `dns` or `http`.
576    pub challenge_type: String,
577    /// The token the challenge is keyed by. A bearer secret for one domain.
578    pub token: String,
579    /// The exact content the verifier expects. Served verbatim rather than
580    /// recomputed, so the check cannot be satisfied by anyone who can guess the
581    /// formula.
582    pub expected_value: String,
583    /// `pending`, `checking`, `verified` or `failed`.
584    pub status: String,
585    /// Why the last attempt failed, if it did.
586    pub error_message: Option<String>,
587    /// When the challenge was issued.
588    pub created_at: DateTime<Utc>,
589    /// When it stops being answerable.
590    pub expires_at: DateTime<Utc>,
591    /// When it was satisfied, if it was.
592    pub verified_at: Option<DateTime<Utc>>,
593}
594
595/// Audit log entry.
596#[derive(Clone, Debug, Serialize, Deserialize, sqlx::FromRow)]
597pub struct AuditLogEntry {
598    /// Primary key.
599    pub id: Uuid,
600    /// The tenant acted on, if the action had one.
601    pub tenant_id: Option<Uuid>,
602    /// The domain acted on, if the action had one.
603    pub domain_id: Option<Uuid>,
604    /// What happened.
605    pub action: String,
606    /// What kind of actor did it -- an API key, a JWT, the system.
607    pub actor_type: String,
608    /// Which actor, as far as it is known.
609    pub actor_id: Option<String>,
610    /// Anything else worth keeping about the action.
611    pub details: serde_json::Value,
612    /// Where the request came from.
613    pub ip_address: Option<IpAddr>,
614    /// When it happened.
615    pub created_at: DateTime<Utc>,
616}
617
618// ============================================================================
619// API Request/Response Types
620// ============================================================================
621
622/// Create tenant request.
623#[derive(Clone, Debug, Deserialize)]
624pub struct CreateTenantRequest {
625    /// Display name.
626    pub name: String,
627    /// Short URL-safe identifier. Must not already be taken.
628    pub slug: String,
629    /// Contact address.
630    pub email: String,
631    /// Plan name. Defaults to the free plan.
632    pub plan: Option<String>,
633}
634
635/// Update tenant request.
636#[derive(Clone, Debug, Deserialize)]
637pub struct UpdateTenantRequest {
638    /// New display name, if it is changing.
639    pub name: Option<String>,
640    /// New contact address, if it is changing.
641    pub email: Option<String>,
642    /// New plan, if it is changing.
643    pub plan: Option<String>,
644    /// Replacement settings blob. Replaces rather than merges.
645    pub settings: Option<serde_json::Value>,
646}
647
648/// Create domain request.
649#[derive(Clone, Debug, Deserialize)]
650pub struct CreateDomainRequest {
651    /// The name to register. Globally unique across tenants, compared without
652    /// regard to case, and stored lowercased with any trailing dot removed --
653    /// see [`normalize_domain`](crate::saas::validation::normalize_domain) for
654    /// what is accepted.
655    pub domain: String,
656    /// How ownership will be proved. Defaults to DNS, which is the sound one:
657    /// HTTP verification is answered by this proxy once the domain points at
658    /// it, so it shows the domain resolves here rather than that the caller
659    /// controls it.
660    #[serde(default)]
661    pub verification_method: VerificationMethod,
662    /// Where the certificate will come from. Defaults to ACME.
663    #[serde(default)]
664    pub ssl_provider: SslProvider,
665}
666
667/// Fields of a domain that can be changed after it is created.
668///
669/// The domain name itself is deliberately absent. It is the identity of the
670/// record and what the verification token proves control of, so renaming would
671/// silently carry a proof of ownership over to a name nobody has proved
672/// anything about. Delete and re-add instead.
673#[derive(Clone, Debug, Deserialize, Default)]
674pub struct UpdateDomainRequest {
675    /// Switch between DNS and HTTP verification.
676    ///
677    /// Takes effect on the next verification attempt. Changing it does not
678    /// un-verify a domain that is already verified.
679    #[serde(default)]
680    pub verification_method: Option<VerificationMethod>,
681
682    /// Switch how the certificate is obtained.
683    ///
684    /// Moving a verified domain to `acme` queues it for issuance.
685    #[serde(default)]
686    pub ssl_provider: Option<SslProvider>,
687}
688
689/// Domain response with verification instructions.
690#[derive(Clone, Debug, Serialize)]
691pub struct DomainResponse {
692    /// The domain as stored. Flattened, so the response is the domain's own
693    /// fields plus the instructions.
694    #[serde(flatten)]
695    pub domain: Domain,
696    /// What the caller has to do to prove ownership.
697    pub verification_instructions: VerificationInstructions,
698}
699
700/// Verification instructions.
701#[derive(Clone, Debug, Serialize)]
702pub struct VerificationInstructions {
703    /// Which method these instructions are for. Only that method's fields are
704    /// populated.
705    pub method: VerificationMethod,
706    /// DNS: the record type to create, always `TXT`.
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub dns_record_type: Option<String>,
709    /// DNS: the name to create it at.
710    #[serde(skip_serializing_if = "Option::is_none")]
711    pub dns_record_name: Option<String>,
712    /// DNS: the value it must hold.
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub dns_record_value: Option<String>,
715    /// HTTP: the URL the verifier will fetch.
716    #[serde(skip_serializing_if = "Option::is_none")]
717    pub http_url: Option<String>,
718    /// HTTP: the content it must return.
719    #[serde(skip_serializing_if = "Option::is_none")]
720    pub http_expected_content: Option<String>,
721}
722
723impl VerificationInstructions {
724    /// Create DNS verification instructions.
725    pub fn dns(domain: &str, token: &str) -> Self {
726        Self {
727            method: VerificationMethod::Dns,
728            dns_record_type: Some("TXT".to_string()),
729            dns_record_name: Some(format!("_postrust-verification.{}", domain)),
730            dns_record_value: Some(format!("postrust-verify={}", token)),
731            http_url: None,
732            http_expected_content: None,
733        }
734    }
735
736    /// Create HTTP verification instructions.
737    pub fn http(domain: &str, token: &str) -> Self {
738        Self {
739            method: VerificationMethod::Http,
740            dns_record_type: None,
741            dns_record_name: None,
742            dns_record_value: None,
743            http_url: Some(format!(
744                "https://{}/.well-known/postrust-verification/{}",
745                domain, token
746            )),
747            http_expected_content: Some(format!("postrust-verify={}", token)),
748        }
749    }
750}
751
752/// Verification result.
753#[derive(Clone, Debug, Serialize)]
754#[serde(rename_all = "snake_case")]
755pub enum VerificationResult {
756    /// Domain verified successfully
757    Verified,
758    /// Verification pending
759    Pending,
760    /// Verification failed
761    Failed {
762        /// Why, in terms the caller can act on.
763        reason: String,
764    },
765}
766
767/// Create domain route request.
768#[derive(Clone, Debug, Deserialize)]
769pub struct CreateDomainRouteRequest {
770    /// Name, unique within the domain.
771    pub name: String,
772    /// The path to match. Defaults to `/`, which catches everything.
773    #[serde(default = "default_path_pattern")]
774    pub path_pattern: String,
775    /// How to read the pattern. Defaults to prefix.
776    #[serde(default)]
777    pub path_type: DomainPathMatchType,
778    /// Methods to match. Omit for any.
779    pub methods: Option<Vec<String>>,
780    /// The upstream to forward to. Must belong to the same tenant.
781    pub upstream_id: Uuid,
782    /// Whether to remove the matched prefix before forwarding.
783    #[serde(default)]
784    pub strip_path: bool,
785    /// Higher is matched first.
786    #[serde(default = "default_priority")]
787    pub priority: i32,
788    /// Headers to add to the forwarded request.
789    #[serde(default)]
790    pub add_headers: HashMap<String, String>,
791    /// Headers to remove from the forwarded request.
792    #[serde(default)]
793    pub remove_headers: Vec<String>,
794    /// Requests allowed per window. Set with the window or not at all.
795    pub rate_limit_requests: Option<i32>,
796    /// The window those requests are counted over, in seconds.
797    pub rate_limit_window_secs: Option<i32>,
798    /// Request timeout in seconds.
799    #[serde(default = "default_timeout")]
800    pub timeout_secs: i32,
801}
802
803fn default_path_pattern() -> String {
804    "/".to_string()
805}
806
807fn default_priority() -> i32 {
808    100
809}
810
811fn default_timeout() -> i32 {
812    30
813}
814
815/// Update domain route request.
816#[derive(Clone, Debug, Deserialize)]
817pub struct UpdateDomainRouteRequest {
818    /// New name, if it is changing.
819    pub name: Option<String>,
820    /// New path pattern, if it is changing.
821    pub path_pattern: Option<String>,
822    /// New path-match type, if it is changing.
823    pub path_type: Option<DomainPathMatchType>,
824    /// Replacement method list. Replaces rather than adds.
825    pub methods: Option<Vec<String>>,
826    /// A different upstream to forward to.
827    pub upstream_id: Option<Uuid>,
828    /// Whether to strip the matched prefix.
829    pub strip_path: Option<bool>,
830    /// New priority.
831    pub priority: Option<i32>,
832    /// Replacement set of headers to add. Replaces rather than merges.
833    pub add_headers: Option<HashMap<String, String>>,
834    /// Replacement list of headers to remove.
835    pub remove_headers: Option<Vec<String>>,
836    /// New request allowance per window.
837    pub rate_limit_requests: Option<i32>,
838    /// New window, in seconds.
839    pub rate_limit_window_secs: Option<i32>,
840    /// New request timeout, in seconds.
841    pub timeout_secs: Option<i32>,
842    /// Whether the route participates in matching.
843    pub enabled: Option<bool>,
844}
845
846/// Create upstream request.
847#[derive(Clone, Debug, Deserialize)]
848pub struct CreateUpstreamRequest {
849    /// Name, unique within the tenant. Routes reference it.
850    pub name: String,
851    /// How requests are spread across the backends.
852    #[serde(default)]
853    pub lb_strategy: DomainLoadBalanceStrategy,
854    /// Whether backends are health-checked.
855    #[serde(default = "default_true")]
856    pub health_check_enabled: bool,
857    /// The path health checks request.
858    #[serde(default = "default_health_path")]
859    pub health_check_path: String,
860    /// Seconds between checks.
861    #[serde(default = "default_health_interval")]
862    pub health_check_interval_secs: i32,
863    /// Seconds a single check may take.
864    #[serde(default = "default_health_timeout")]
865    pub health_check_timeout_secs: i32,
866    /// Consecutive successes before a backend is used again.
867    #[serde(default = "default_healthy_threshold")]
868    pub healthy_threshold: i32,
869    /// Consecutive failures before a backend is taken out.
870    #[serde(default = "default_unhealthy_threshold")]
871    pub unhealthy_threshold: i32,
872    /// Backends to create alongside the upstream.
873    #[serde(default)]
874    pub backends: Vec<CreateBackendRequest>,
875}
876
877fn default_true() -> bool {
878    true
879}
880
881fn default_health_path() -> String {
882    "/health".to_string()
883}
884
885fn default_health_interval() -> i32 {
886    30
887}
888
889fn default_health_timeout() -> i32 {
890    5
891}
892
893fn default_healthy_threshold() -> i32 {
894    2
895}
896
897fn default_unhealthy_threshold() -> i32 {
898    3
899}
900
901/// Update upstream request.
902#[derive(Clone, Debug, Deserialize)]
903pub struct UpdateUpstreamRequest {
904    /// New name, if it is changing.
905    pub name: Option<String>,
906    /// New load-balancing strategy.
907    pub lb_strategy: Option<DomainLoadBalanceStrategy>,
908    /// Whether to health-check the backends.
909    pub health_check_enabled: Option<bool>,
910    /// New health-check path.
911    pub health_check_path: Option<String>,
912    /// New interval between checks, in seconds.
913    pub health_check_interval_secs: Option<i32>,
914    /// New per-check timeout, in seconds.
915    pub health_check_timeout_secs: Option<i32>,
916    /// New healthy threshold.
917    pub healthy_threshold: Option<i32>,
918    /// New unhealthy threshold.
919    pub unhealthy_threshold: Option<i32>,
920    /// Whether the upstream may receive traffic.
921    ///
922    /// Backends are deliberately absent: this type cannot express a backend's
923    /// id, so treating a list here as the new set would re-create every backend
924    /// under a fresh id. Use the backend endpoints.
925    pub enabled: Option<bool>,
926}
927
928/// Create backend request.
929#[derive(Clone, Debug, Deserialize)]
930pub struct CreateBackendRequest {
931    /// `host:port`.
932    pub address: String,
933    /// `http` or `https`. Defaults to `http`.
934    #[serde(default = "default_scheme")]
935    pub scheme: String,
936    /// Relative share of traffic under a weighted strategy.
937    #[serde(default = "default_weight")]
938    pub weight: i32,
939}
940
941fn default_scheme() -> String {
942    "http".to_string()
943}
944
945fn default_weight() -> i32 {
946    100
947}
948
949/// Create API key request.
950#[derive(Clone, Debug, Deserialize)]
951pub struct CreateApiKeyRequest {
952    /// Label, for telling keys apart later.
953    pub name: String,
954    /// What the key may do, as `resource:action` strings from
955    /// [`KNOWN_SCOPES`](crate::saas::auth::KNOWN_SCOPES). Defaults to reading
956    /// and writing domains. Every scope must be one the creating credential
957    /// itself holds.
958    #[serde(default = "default_scopes")]
959    pub scopes: Vec<String>,
960    /// When the key should stop working. `None` means never.
961    pub expires_at: Option<DateTime<Utc>>,
962}
963
964fn default_scopes() -> Vec<String> {
965    vec!["domains:read".to_string(), "domains:write".to_string()]
966}
967
968/// Certificate upload request.
969#[derive(Clone, Debug, Deserialize)]
970pub struct UploadCertificateRequest {
971    /// The certificate chain, PEM-encoded, leaf first.
972    pub cert_pem: String,
973    /// The private key, PEM-encoded. Must match the leaf: the upload is
974    /// refused otherwise, because a mismatch produces a listener that fails
975    /// every handshake instead.
976    pub key_pem: String,
977}
978
979/// Tenant usage stats.
980#[derive(Clone, Debug, Serialize)]
981pub struct TenantUsage {
982    /// Domains registered.
983    pub domains_count: i64,
984    /// How many the tenant's plan allows.
985    pub domains_limit: i32,
986    /// How many of those domains are verified.
987    pub verified_domains: i64,
988    /// Routes across all of them.
989    pub routes_count: i64,
990    /// How many routes each domain may have.
991    pub routes_per_domain_limit: i32,
992    /// Upstreams defined.
993    pub upstreams_count: i64,
994    /// How many upstreams the tenant may define.
995    pub upstreams_limit: i32,
996    /// How many backends each upstream may have.
997    pub backends_per_upstream_limit: i32,
998    /// API keys, including revoked ones.
999    pub api_keys_count: i64,
1000    /// How many API keys the tenant may hold.
1001    pub api_keys_limit: i32,
1002}