Skip to main content

ordinary_config/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![doc = include_str!("../docs/config-reference.md")]
4#![warn(clippy::all, clippy::pedantic)]
5#![allow(clippy::missing_errors_doc)]
6
7// Copyright (C) 2026 Ordinary Labs, LLC.
8//
9// SPDX-License-Identifier: AGPL-3.0-only
10
11mod limits;
12mod validate;
13
14pub use crate::validate::DOMAIN_REGEX;
15use crate::validate::validate;
16use std::collections::BTreeSet;
17
18use crate::limits::check_config_against_limits;
19use anyhow::bail;
20use arrayvec::ArrayVec;
21use hashbrown::{HashMap, HashSet};
22use ordinary_types::{Field, Kind};
23use serde::{Deserialize, Serialize};
24use std::env;
25use std::fmt::{Display, Formatter, Write};
26use std::path::Path;
27use std::process::Command;
28use tracing::instrument;
29
30fn default_env_name() -> String {
31    "development".to_string()
32}
33
34#[derive(Deserialize, Serialize, Clone)]
35pub struct OrdinaryApiConfig {
36    pub domain: String,
37    pub contacts: Vec<String>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    #[serde(default)]
40    pub public_dns_ip: Option<[u8; 4]>,
41    #[serde(default = "default_env_name")]
42    pub env_name: String,
43    #[serde(default)]
44    pub limits: OrdinaryApiLimits,
45}
46
47#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
48#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
49#[derive(Deserialize, Serialize, Debug, Clone)]
50pub struct AssetsLimits {
51    /// Allowed extensions corresponding to
52    /// [MIME](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types/Common_types) types.
53    ///
54    /// (More to be supported in the future)
55    ///
56    /// Supported:
57    /// - "txt"
58    /// - "xml"
59    /// - "html"
60    /// - "css"
61    /// - "css.map"
62    /// - "csv"
63    /// - "js"
64    /// - "png"
65    /// - "apng"
66    /// - "gif"
67    /// - "svg"
68    /// - "jpg"
69    /// - "jpeg"
70    /// - "bmp"
71    /// - "tif"
72    /// - "tiff"
73    /// - "webp"
74    /// - "avif"
75    /// - "ico"
76    /// - "pdf"
77    /// - "json"
78    /// - "wasm"
79    ///
80    /// Special:
81    /// - "none" (for no extension; uses "application/octet-stream")
82    /// - "any" (for unsupported extensions; also uses "application/octet-stream")
83    pub allowed_extensions: Vec<String>,
84    /// Limit on total storage for all assets within an app
85    /// (bytes).
86    pub max_store_size: u64,
87    /// Limit on individual asset size (bytes).
88    pub max_asset_size: u64,
89}
90
91impl Default for AssetsLimits {
92    fn default() -> Self {
93        Self {
94            allowed_extensions: vec![
95                "otf".into(),
96                "ttf".into(),
97                "woff".into(),
98                "woff2".into(),
99                "txt".into(),
100                "xml".into(),
101                "html".into(),
102                "css".into(),
103                "css.map".into(),
104                "csv".into(),
105                "js".into(),
106                "png".into(),
107                "apng".into(),
108                "gif".into(),
109                "svg".into(),
110                "jpg".into(),
111                "jpeg".into(),
112                "bmp".into(),
113                "tif".into(),
114                "tiff".into(),
115                "webp".into(),
116                "avif".into(),
117                "ico".into(),
118                "pdf".into(),
119                "json".into(),
120                "wasm".into(),
121            ],
122            max_store_size: 100_000_000,
123            max_asset_size: 1_500_000,
124        }
125    }
126}
127
128#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
129#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
130#[derive(Deserialize, Serialize, Debug, Clone)]
131pub struct ArtifactLimits {
132    pub max_store_size: u64,
133    pub max_artifact_size: u64,
134}
135
136impl Default for ArtifactLimits {
137    fn default() -> Self {
138        Self {
139            max_store_size: 10_000_000,
140            max_artifact_size: 500_000,
141        }
142    }
143}
144
145#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
146#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
147#[derive(Deserialize, Serialize, Debug, Clone)]
148pub struct CacheLimits {
149    pub max_size_range: (u64, u64),
150    pub max_count_range: (usize, usize),
151
152    pub clean_interval_ranges: ((u64, u64), (u64, u64)),
153}
154
155impl Default for CacheLimits {
156    fn default() -> Self {
157        Self {
158            max_size_range: (1_000_000, 10_000_000),
159            max_count_range: (100, 500),
160
161            clean_interval_ranges: ((5, 10), (15, 20)),
162        }
163    }
164}
165
166#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
167#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
168#[derive(Deserialize, Serialize, Debug, Clone)]
169pub struct ContentLimits {
170    pub search_enabled: bool,
171
172    pub max_content_definitions: u8,
173    pub max_content_fields: u8,
174
175    pub max_store_size: u64,
176    pub max_object_size: u64,
177    pub max_field_size: u64,
178}
179
180impl Default for ContentLimits {
181    fn default() -> Self {
182        Self {
183            search_enabled: true,
184
185            max_content_definitions: 255,
186            max_content_fields: 255,
187
188            max_store_size: 10_000_000,
189            max_object_size: 400_000,
190            max_field_size: 200_000,
191        }
192    }
193}
194
195#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
196#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
197#[derive(Deserialize, Serialize, Debug, Clone)]
198pub struct ModelLimits {
199    pub search_enabled: bool,
200
201    pub max_model_definitions: u8,
202    pub max_model_fields: u8,
203
204    pub max_item_size: u64,
205    pub max_field_size: u64,
206}
207
208impl Default for ModelLimits {
209    fn default() -> Self {
210        Self {
211            search_enabled: true,
212
213            max_model_definitions: 255,
214            max_model_fields: 255,
215
216            max_item_size: 400_000,
217            max_field_size: 100_000,
218        }
219    }
220}
221
222#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
223#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
224#[derive(Deserialize, Serialize, Debug, Clone)]
225pub struct SecretsLimits {
226    pub max_count: u8,
227    pub max_size: u64,
228}
229
230impl Default for SecretsLimits {
231    fn default() -> Self {
232        Self {
233            max_count: 225,
234            max_size: 2_000,
235        }
236    }
237}
238
239#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
240#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
241#[derive(Deserialize, Serialize, Debug, Clone)]
242pub struct StorageLimits {
243    /// Maximum total storage for all apps and api.
244    ///
245    /// Unit: bytes.
246    ///
247    /// Default: 10 GB
248    pub max_storage: u64,
249    /// Maximum per-app storage.
250    ///
251    /// Unit: bytes.
252    ///
253    /// Default: 50 MB
254    pub max_app_storage: u64,
255
256    pub assets: AssetsLimits,
257    pub artifact: ArtifactLimits,
258    pub cache: CacheLimits,
259    pub content: ContentLimits,
260    pub model: ModelLimits,
261    pub secrets: SecretsLimits,
262}
263
264impl Default for StorageLimits {
265    fn default() -> Self {
266        Self {
267            max_storage: 20_000_000_000,
268            max_app_storage: 50_000_000,
269
270            assets: AssetsLimits::default(),
271            artifact: ArtifactLimits::default(),
272            cache: CacheLimits::default(),
273            content: ContentLimits::default(),
274            model: ModelLimits::default(),
275            secrets: SecretsLimits::default(),
276        }
277    }
278}
279
280#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
281#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
282#[derive(Deserialize, Serialize, Debug, Clone, Default)]
283pub struct MonitorLimits {}
284
285#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
286#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
287#[derive(Deserialize, Serialize, Debug, Clone)]
288pub struct IntegrationLimits {
289    /// Max number of integrations per app.
290    pub count: u8,
291
292    /// Integration request timeout.
293    ///
294    /// Unit: seconds.
295    pub max_timeout: u16,
296}
297
298impl Default for IntegrationLimits {
299    fn default() -> Self {
300        Self {
301            count: 255,
302            max_timeout: 10,
303        }
304    }
305}
306
307#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
308#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
309#[derive(Deserialize, Serialize, Debug, Clone)]
310pub struct ActionLimits {
311    /// Max number of actions per app.
312    pub count: u8,
313
314    /// Action request timeout.
315    ///
316    /// Unit: seconds.
317    pub max_timeout: u16,
318}
319
320impl Default for ActionLimits {
321    fn default() -> Self {
322        Self {
323            count: 255,
324            max_timeout: 10,
325        }
326    }
327}
328
329#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
330#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
331#[derive(Deserialize, Serialize, Debug, Clone, Default)]
332pub struct AuthLimits {}
333
334#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
335#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
336#[derive(Deserialize, Serialize, Debug, Clone)]
337pub struct ProxyLimits {
338    /// max number of proxies per application.
339    pub count: u8,
340    /// a list of any internal or external target
341    /// regexes that should be disallowed.
342    pub disallowed_targets: Vec<String>,
343}
344
345impl Default for ProxyLimits {
346    fn default() -> Self {
347        Self {
348            count: 255,
349            disallowed_targets: vec![],
350        }
351    }
352}
353
354#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
355#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
356#[derive(Deserialize, Serialize, Debug, Clone)]
357pub struct MiddlewareLimits {
358    /// max number of proxies per application.
359    pub count: u16,
360    /// a list of any internal or external target
361    /// regexes that should be disallowed.
362    pub disallowed_endpoints: Vec<String>,
363}
364
365impl Default for MiddlewareLimits {
366    fn default() -> Self {
367        Self {
368            count: u16::MAX,
369            disallowed_endpoints: vec![],
370        }
371    }
372}
373
374#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
375#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
376#[derive(Deserialize, Serialize, Debug, Clone)]
377pub struct TemplateLimits {
378    /// Max number of templates per app.
379    pub count: u8,
380
381    /// Template request timeout.
382    ///
383    /// Unit: seconds.
384    pub max_timeout: u16,
385}
386
387impl Default for TemplateLimits {
388    fn default() -> Self {
389        Self {
390            count: 255,
391            max_timeout: 10,
392        }
393    }
394}
395
396#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
397#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
398#[derive(Deserialize, Serialize, Debug, Clone)]
399pub struct OrdinaryApiLimits {
400    pub app_domains: Vec<String>,
401    pub privileged_domains: Vec<String>,
402
403    /// Max default timeout for all HTTP requests.
404    ///
405    /// Default: 10
406    pub max_default_timeout: u16,
407
408    pub proxy: ProxyLimits,
409    pub middleware: MiddlewareLimits,
410
411    pub action: ActionLimits,
412    pub auth: AuthLimits,
413    pub integration: IntegrationLimits,
414    pub monitor: MonitorLimits,
415    pub storage: StorageLimits,
416    pub template: TemplateLimits,
417}
418
419impl Default for OrdinaryApiLimits {
420    fn default() -> Self {
421        Self {
422            app_domains: vec![],
423            privileged_domains: vec![],
424
425            max_default_timeout: 10,
426
427            proxy: ProxyLimits::default(),
428            middleware: MiddlewareLimits::default(),
429
430            action: ActionLimits::default(),
431            auth: AuthLimits::default(),
432            integration: IntegrationLimits::default(),
433            monitor: MonitorLimits::default(),
434            storage: StorageLimits::default(),
435            template: TemplateLimits::default(),
436        }
437    }
438}
439
440#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
441#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
442#[derive(Deserialize, Serialize, Debug, Clone)]
443pub struct ClientLoggingConfig {
444    /// bottom end of the delayed delivery range (seconds)
445    #[serde(skip_serializing_if = "Option::is_none")]
446    #[serde(default)]
447    min_delay: Option<u32>,
448    /// top end of delayed delivery range (seconds)
449    #[serde(skip_serializing_if = "Option::is_none")]
450    #[serde(default)]
451    max_delay: Option<u32>,
452    /// max number of events to be buffered on the client
453    /// prior to flush.
454    #[serde(skip_serializing_if = "Option::is_none")]
455    #[serde(default)]
456    max_buffer: Option<u16>,
457    /// sets the max number of events in a given request.
458    #[serde(skip_serializing_if = "Option::is_none")]
459    #[serde(default)]
460    max_batch: Option<u16>,
461}
462
463#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
464#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
465#[derive(Deserialize, Serialize, Debug, Clone)]
466pub enum RedactedHashAlg {
467    Blake2,
468    Blake3,
469}
470
471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
472#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
473#[derive(Deserialize, Serialize, Debug, Clone)]
474pub struct ServerLoggingConfig {
475    #[serde(skip_serializing_if = "Option::is_none")]
476    #[serde(default)]
477    pub ips: Option<bool>,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    #[serde(default)]
480    pub headers: Option<bool>,
481    #[serde(skip_serializing_if = "Option::is_none")]
482    #[serde(default)]
483    pub credentials: Option<RedactedHashAlg>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    #[serde(default)]
486    pub timing: Option<bool>,
487    #[serde(skip_serializing_if = "Option::is_none")]
488    #[serde(default)]
489    pub sizes: Option<bool>,
490}
491
492#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
493#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
494#[derive(Deserialize, Serialize, Debug, Clone)]
495pub struct LoggingConfig {
496    #[serde(skip_serializing_if = "Option::is_none")]
497    #[serde(default)]
498    pub client: Option<ClientLoggingConfig>,
499    #[serde(skip_serializing_if = "Option::is_none")]
500    #[serde(default)]
501    pub server: Option<ServerLoggingConfig>,
502}
503
504#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
505#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
506#[derive(Deserialize, Serialize, Debug, Clone)]
507pub struct Check {
508    // todo: what to validate the token against
509    // ?? i.e "token.fields.account is included in list of post.author.followers.accounts"
510}
511
512#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
513#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
514#[derive(Deserialize, Serialize, Debug, Clone)]
515pub enum TokenAlgorithm {
516    HmacBlake2b256,
517}
518
519/// Configuration for refresh tokens.
520#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
521#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
522#[derive(Deserialize, Serialize, Debug, Clone)]
523pub struct RefreshTokenConfig {
524    /// Algorithm used for verifying the token.
525    pub algorithm: TokenAlgorithm,
526    /// How long a token should be valid for (seconds).
527    pub lifetime: u32,
528    /// how frequently the key should be rotated
529    pub rotation: u32,
530}
531
532impl Default for RefreshTokenConfig {
533    fn default() -> RefreshTokenConfig {
534        RefreshTokenConfig {
535            algorithm: TokenAlgorithm::HmacBlake2b256,
536            lifetime: 60 * 60 * 24 * 7,
537            rotation: 60 * 60 * 24 * 7 * 2,
538        }
539    }
540}
541
542/// Configuration for access tokens.
543#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
544#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
545#[derive(Deserialize, Serialize, Debug, Clone)]
546pub struct AccessTokenConfig {
547    /// Algorithm used for verifying the token.
548    pub algorithm: TokenAlgorithm,
549    /// How long a token should be valid for (seconds).
550    pub lifetime: u32,
551    /// how frequently the key should be rotated
552    pub rotation: u32,
553    /// Token claims structuring.
554    ///
555    /// Note: `idx` starts at 1 to create space for system claims (id, domain, and account).
556    pub claims: Vec<Field>,
557}
558
559impl Default for AccessTokenConfig {
560    fn default() -> AccessTokenConfig {
561        AccessTokenConfig {
562            algorithm: TokenAlgorithm::HmacBlake2b256,
563            lifetime: 60 * 60 * 24,
564            rotation: 60 * 60 * 24 * 3,
565            claims: vec![],
566        }
567    }
568}
569
570/// Configuration for client password hashing.
571///
572/// When JavaScript or WASM modes are enabled, passwords are
573/// hashed with the application name, and account, before transit
574/// (or in the case of WASM prior to the Opaque client operations
575/// if Opaque is selected for the `PasswordProtocol`).
576#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
577#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
578#[derive(Deserialize, Serialize, Debug, Clone)]
579pub enum ClientPasswordHash {
580    /// Limited by what browsers can support, SHA-256 is
581    /// a good option for a client-side hash to enable
582    /// slightly better password protection when in
583    /// javascript-only mode, without the WASM for Opaque.
584    Sha256,
585}
586
587/// Configuration for password protocol.
588#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
589#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
590#[derive(Deserialize, Serialize, Debug, Clone)]
591pub enum PasswordProtocol {
592    /// When WASM is enabled, this will run the client portions
593    /// browser-side. When JavaScript-only, passwords will be hashed
594    /// and then sent to the server where the client portion will
595    /// be done on behalf of the user. In noscript mode, the password
596    /// is sent only protected by TLS, hashed and then the client operations
597    /// are done server side.
598    ///
599    /// If a user later decides to enable JavaScript or WASM, they'll be
600    /// able to opt in to the no-plain-password-sent modes without interruption.
601    Opaque,
602    // todo: SRP and Bcrypt/Argon2 hashing?
603}
604
605/// Configuration for passwords.
606#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
607#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
608#[derive(Deserialize, Serialize, Debug, Clone)]
609pub struct PasswordConfig {
610    pub protocol: PasswordProtocol,
611}
612
613impl Default for PasswordConfig {
614    fn default() -> PasswordConfig {
615        PasswordConfig {
616            protocol: PasswordProtocol::Opaque,
617        }
618    }
619}
620
621#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
622#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
623#[derive(Deserialize, Serialize, Debug, Clone)]
624pub enum TotpAlgorithm {
625    /// only allowing SHA1 for now
626    /// because many of the major MFA authenticator
627    /// apps don't support SHA256 or SHA512, and fail silently.
628    Sha1,
629}
630
631#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
632#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
633#[derive(Deserialize, Serialize, Debug, Clone)]
634pub struct TotpConfig {
635    /// Used for response after registration form is submitted.
636    /// Will just return a QR code SVG if not set.
637    #[serde(skip_serializing_if = "Option::is_none")]
638    #[serde(default)]
639    pub template: Option<String>,
640    pub algorithm: TotpAlgorithm,
641}
642
643impl Default for TotpConfig {
644    fn default() -> TotpConfig {
645        TotpConfig {
646            template: None,
647            algorithm: TotpAlgorithm::Sha1,
648        }
649    }
650}
651
652#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
653#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
654#[derive(Serialize, Deserialize, Debug, Clone, Default)]
655pub struct MfaConfig {
656    pub totp: TotpConfig,
657}
658
659#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
660#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
661#[derive(Deserialize, Serialize, Debug, Clone)]
662pub enum InviteMode {
663    /// only the root user can invite
664    Root,
665    /// only a site admin can invite
666    Admin,
667    /// anyone who has been invited can invite anyone else
668    Viral,
669    // ?? anyone who has been invited has a limited set of invites they
670    // ?? can share on an optional interval.
671    // todo: Limited,
672    // ?? only those invited with permission to invite can invite others
673    // todo: Selective,
674}
675
676#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
677#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
678#[derive(Deserialize, Serialize, Debug, Clone)]
679pub struct InviteConfig {
680    pub mode: InviteMode,
681    /// How long a token is valid for.
682    ///
683    /// Defaults to 7 days.
684    pub lifetime: u32,
685    /// On what interval to clean up expired token ids.
686    ///
687    /// Defaults to 30 - 90 seconds.
688    pub clean_interval: (u32, u32),
689    /// Values that can be used internally in the API server to
690    /// set default permissions for new accounts.
691    ///
692    /// TODO: in the future, these can also be set in order to pre-validate
693    /// TODO: or constrain `app` account registrations. This will require the
694    /// TODO: use of an `InviteCreate` action trigger (for validating and setting
695    /// TODO: the invite token claims), and a pre-registration `InviteValidate`,
696    /// TODO: as well as passing the invite token claims to the `Registration` trigger
697    /// TODO: (allowing for the validated invite claims to be used in the account
698    /// TODO: claims setting operation).
699    ///
700    /// Note: `idx` starts at 1 to create space for system claims (id, domain, and account).
701    #[serde(skip_serializing_if = "Option::is_none")]
702    #[serde(default)]
703    pub claims: Option<Vec<Field>>,
704}
705
706impl Default for InviteConfig {
707    fn default() -> InviteConfig {
708        InviteConfig {
709            mode: InviteMode::Viral,
710            lifetime: 60 * 60 * 24 * 7,
711            clean_interval: (30, 90),
712            claims: None,
713        }
714    }
715}
716
717/// Auth configuration.
718#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
719#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
720#[derive(Deserialize, Serialize, Debug, Clone)]
721pub struct AuthConfig {
722    /// Configuration for passwords.
723    pub password: PasswordConfig,
724    /// MFA configuration for auth.
725    pub mfa: MfaConfig,
726    /// Configuration for refresh tokens.
727    pub refresh_token: RefreshTokenConfig,
728    /// Configuration for access tokens.
729    pub access_token: AccessTokenConfig,
730    /// Determines whether cookies should be used
731    /// for browser based template navigation, and form submissions
732    ///
733    /// Note: when set to `false`, `protected` templates, and actions triggered
734    /// by form submission will fail. Form based registration and login will
735    /// necessarily be disabled, also.
736    ///
737    /// !! Important: when set to `true`, tokens retrieved for `js` and `noscript` flavors
738    /// !! do not support client signatures, because http-only cookies cannot be signed by
739    /// !! the client.
740    pub cookies_enabled: bool,
741    /// what algorithm is used to hash passwords and MFA codes
742    pub client_hash: ClientPasswordHash,
743
744    /// invite config
745    #[serde(skip_serializing_if = "Option::is_none")]
746    #[serde(default)]
747    pub invite: Option<InviteConfig>,
748}
749
750impl Default for AuthConfig {
751    fn default() -> AuthConfig {
752        AuthConfig {
753            password: PasswordConfig::default(),
754            mfa: MfaConfig::default(),
755            refresh_token: RefreshTokenConfig::default(),
756            access_token: AccessTokenConfig::default(),
757            cookies_enabled: false,
758            client_hash: ClientPasswordHash::Sha256,
759            invite: None,
760        }
761    }
762}
763
764/// Compression algorithms
765#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
766#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
767#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
768pub enum CompressionAlgorithm {
769    All,
770    Gzip,
771    Zstd { level: u8 },
772    Brotli,
773    Deflate,
774}
775
776impl CompressionAlgorithm {
777    #[must_use]
778    pub fn as_u8(&self) -> u8 {
779        match self {
780            Self::All => 0,
781            Self::Gzip => 1,
782            Self::Zstd { level: _ } => 2,
783            Self::Brotli => 3,
784            Self::Deflate => 4,
785        }
786    }
787
788    #[must_use]
789    pub fn as_char(&self) -> char {
790        match self {
791            Self::All => '0',
792            Self::Gzip => '1',
793            Self::Zstd { level: _ } => '2',
794            Self::Brotli => '3',
795            Self::Deflate => '4',
796        }
797    }
798
799    #[must_use]
800    pub fn as_str(&self) -> &'static str {
801        match self {
802            Self::All => "",
803            Self::Gzip => "gzip",
804            Self::Zstd { level: _ } => "zstd",
805            Self::Brotli => "br",
806            Self::Deflate => "deflate",
807        }
808    }
809}
810
811#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
812#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
813#[derive(Deserialize, Serialize, Debug, Clone)]
814pub enum StoredCachePolicy {
815    /// No eviction on clean or write. Constrained only by overall storage limit or `max_size`
816    /// (if set). Will only be evicted if dependencies change and `evict_on_dependency_change`
817    /// is set.
818    Permanent,
819    /// Prioritize the most Frequently accessed, Recently accessed and smallest Sized items
820    ///
821    /// (`frequency_equality_threshold` (hit count), `recency_equality_threshold` (seconds))
822    FRs(u64, u64),
823}
824
825/// Render caching policy.
826///
827/// IMPORTANT: Very experimental, may not work as described. `policy: Permanent` is currently
828/// the most likely to behave correctly.
829#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
830#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
831#[derive(Deserialize, Serialize, Debug, Clone)]
832pub struct StoredCache {
833    pub policy: StoredCachePolicy,
834
835    /// Which compression formats should be stored
836    #[serde(skip_serializing_if = "Option::is_none")]
837    #[serde(default)]
838    pub compression: Option<CompressionAlgorithms>,
839
840    #[serde(skip)]
841    #[serde(default)]
842    pub internal_compression: Option<ArrayVec<CompressionAlgorithm, 4>>,
843
844    /// Upper limit on total time a cached item can be stored
845    ///
846    /// Unit: seconds
847    #[serde(skip_serializing_if = "Option::is_none")]
848    #[serde(default)]
849    pub max_ttl: Option<u64>,
850
851    /// Compared with time since last hit.
852    ///
853    /// Unit: seconds
854    #[serde(skip_serializing_if = "Option::is_none")]
855    #[serde(default)]
856    pub hit_ttl: Option<u64>,
857
858    /// Upper limit on the cumulative size of all cached responses
859    /// for a given template.
860    ///
861    /// Unit: bytes
862    #[serde(skip_serializing_if = "Option::is_none")]
863    #[serde(default)]
864    pub max_size: Option<u64>,
865
866    /// Upper limit on the number of cached responses stored at
867    /// a given time, for a given template.
868    #[serde(skip_serializing_if = "Option::is_none")]
869    #[serde(default)]
870    pub max_count: Option<usize>,
871
872    /// How long an LFU "hit" tick is valid for
873    ///
874    /// Unit: seconds
875    #[serde(skip_serializing_if = "Option::is_none")]
876    #[serde(default)]
877    pub frequency_window: Option<u64>,
878
879    /// Rate at which the cache is cleaned based on other rules.
880    ///
881    /// Option<(min, max)>
882    #[serde(skip_serializing_if = "Option::is_none")]
883    #[serde(default)]
884    pub clean_interval: Option<(u64, u64)>,
885
886    /// Whether a cached item should also track the of models and content
887    /// which it depends on, and evict when they are modified.
888    #[serde(skip_serializing_if = "Option::is_none")]
889    #[serde(default)]
890    pub evict_on_dependency_change: Option<bool>,
891}
892
893#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
894#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
895#[derive(Deserialize, Serialize, Debug, Clone)]
896pub enum XXH3Variation {
897    Bit64,
898    Bit128,
899}
900
901/// Hashing algorithm options for generating etags.
902///
903/// `AHash` is the default if none is selected.
904#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
905#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
906#[derive(Deserialize, Serialize, Debug, Clone)]
907pub enum HttpEtagAlgorithm {
908    AHash,
909    XXH3(XXH3Variation),
910    Rustc,
911    Blake3,
912}
913
914/// HTTP Cache-Control.
915/// See: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control>
916#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
917#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
918#[derive(Deserialize, Serialize, Debug, Clone)]
919pub struct HttpEtag {
920    #[serde(skip_serializing_if = "Option::is_none")]
921    #[serde(default)]
922    pub alg: Option<HttpEtagAlgorithm>,
923}
924
925/// HTTP Cache-Control.
926/// See: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control>
927#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
928#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
929#[derive(Deserialize, Serialize, Debug, Clone, Default)]
930pub struct HttpCacheControl {
931    #[serde(skip_serializing_if = "Option::is_none")]
932    #[serde(default)]
933    pub max_age: Option<usize>,
934    #[serde(skip_serializing_if = "Option::is_none")]
935    #[serde(default)]
936    pub s_maxage: Option<usize>,
937    #[serde(skip_serializing_if = "Option::is_none")]
938    #[serde(default)]
939    pub no_cache: Option<bool>,
940    #[serde(skip_serializing_if = "Option::is_none")]
941    #[serde(default)]
942    pub no_store: Option<bool>,
943    #[serde(skip_serializing_if = "Option::is_none")]
944    #[serde(default)]
945    pub no_transform: Option<bool>,
946    #[serde(skip_serializing_if = "Option::is_none")]
947    #[serde(default)]
948    pub must_revalidate: Option<bool>,
949    #[serde(skip_serializing_if = "Option::is_none")]
950    #[serde(default)]
951    pub proxy_revalidate: Option<bool>,
952    #[serde(skip_serializing_if = "Option::is_none")]
953    #[serde(default)]
954    pub private: Option<bool>,
955    #[serde(skip_serializing_if = "Option::is_none")]
956    #[serde(default)]
957    pub public: Option<bool>,
958    #[serde(skip_serializing_if = "Option::is_none")]
959    #[serde(default)]
960    pub immutable: Option<bool>,
961    #[serde(skip_serializing_if = "Option::is_none")]
962    #[serde(default)]
963    pub stale_while_revalidate: Option<bool>,
964    #[serde(skip_serializing_if = "Option::is_none")]
965    #[serde(default)]
966    pub stale_if_error: Option<bool>,
967}
968
969impl HttpCacheControl {
970    pub fn header_value(&self, cache_control: &mut String, default: &str) -> anyhow::Result<()> {
971        if let Some(max_age) = self.max_age {
972            write!(cache_control, "max-age={max_age}, ")?;
973        }
974        if let Some(s_maxage) = self.s_maxage {
975            write!(cache_control, "s-maxage={s_maxage}, ")?;
976        }
977        if let Some(no_cache) = self.no_cache
978            && no_cache
979        {
980            write!(cache_control, "no-cache, ")?;
981        }
982        if let Some(no_store) = self.no_store
983            && no_store
984        {
985            write!(cache_control, "no-store, ")?;
986        }
987        if let Some(no_transform) = self.no_transform
988            && no_transform
989        {
990            write!(cache_control, "no-transform, ")?;
991        }
992        if let Some(must_revalidate) = self.must_revalidate
993            && must_revalidate
994        {
995            write!(cache_control, "must-revalidate, ")?;
996        }
997        if let Some(proxy_revalidate) = self.proxy_revalidate
998            && proxy_revalidate
999        {
1000            write!(cache_control, "proxy-revalidate, ")?;
1001        }
1002        if let Some(stale_while_revalidate) = self.stale_while_revalidate
1003            && stale_while_revalidate
1004        {
1005            write!(cache_control, "stale-while-revalidate, ")?;
1006        }
1007        if let Some(private) = self.private
1008            && private
1009        {
1010            write!(cache_control, "private, ")?;
1011        }
1012        if let Some(public) = self.public
1013            && public
1014        {
1015            write!(cache_control, "public, ")?;
1016        }
1017        if let Some(immutable) = self.immutable
1018            && immutable
1019        {
1020            write!(cache_control, "immutable, ")?;
1021        }
1022        if let Some(stale_if_error) = self.stale_if_error
1023            && stale_if_error
1024        {
1025            write!(cache_control, "stale-if-error, ")?;
1026        }
1027
1028        if cache_control.is_empty() {
1029            cache_control.push_str(default);
1030        } else {
1031            cache_control.pop();
1032            cache_control.pop();
1033        }
1034
1035        Ok(())
1036    }
1037}
1038
1039#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1040#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1041#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1042pub struct HttpCache {
1043    #[serde(skip_serializing_if = "Option::is_none")]
1044    #[serde(default)]
1045    pub cache_control: Option<HttpCacheControl>,
1046
1047    /// Corresponds to the HTTP Expires header.
1048    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Expires>
1049    ///
1050    /// Unit: seconds.
1051    #[serde(skip_serializing_if = "Option::is_none")]
1052    #[serde(default)]
1053    pub expires: Option<u64>,
1054
1055    #[serde(skip_serializing_if = "Option::is_none")]
1056    #[serde(default)]
1057    pub etag: Option<HttpEtag>,
1058}
1059
1060/// Field used within the scope of a single template.
1061#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1062#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1063#[derive(Deserialize, Serialize, Debug, Clone)]
1064pub struct TemplateField {
1065    /// Field name
1066    pub name: String,
1067    /// Specifies the type of the value.
1068    pub kind: Kind,
1069    /// JSON value for template field.
1070    pub value: serde_json::Value,
1071}
1072
1073/// Query expression to be used in template bindings.
1074#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1075#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1076#[derive(Deserialize, Serialize, Debug, Clone)]
1077pub enum QueryExpression {
1078    Gte,
1079    Gt,
1080    Lte,
1081    Lt,
1082    Eq,
1083    BeginsWith,
1084}
1085
1086/// Binding options for template field refs.
1087#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1088#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1089#[derive(Deserialize, Serialize, Debug, Clone)]
1090pub enum TemplateRefFieldBind {
1091    /// Bind this property to a token field.
1092    Token {
1093        /// Name of the field that this property should bind to.
1094        /// (i.e "account" if you'd like to have an "/account" route
1095        /// that interprets the request based on the logged-in user's
1096        /// token claims/fields).
1097        field: String,
1098        /// Include if the parent field is queryable.
1099        #[serde(skip_serializing_if = "Option::is_none")]
1100        #[serde(default)]
1101        expression: Option<QueryExpression>,
1102    },
1103    /// Bind this property to a route segment (i.e /{something})
1104    Segment {
1105        /// Specifies the name of the route segment that this property is binding to.
1106        name: String,
1107        /// Include if the parent field is queryable.
1108        #[serde(skip_serializing_if = "Option::is_none")]
1109        #[serde(default)]
1110        expression: Option<QueryExpression>,
1111    },
1112}
1113
1114/// Declaration for which fields from a content definition
1115/// or data model to include.
1116#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1117#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1118#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1119pub struct TemplateRefField {
1120    /// Specifies index for reference field. Index
1121    /// must be unique across fields for a given reference.
1122    pub idx: u8,
1123    /// Name of field to be included.
1124    pub name: String,
1125    /// Option to bind this field's value to a route
1126    /// segment, querystring parameter or token field.
1127    #[serde(skip_serializing_if = "Option::is_none")]
1128    #[serde(default)]
1129    pub bind: Option<TemplateRefFieldBind>,
1130    /// List of any nested subfields to include for this field.
1131    #[cfg_attr(feature = "utoipa", schema(no_recursion))]
1132    #[serde(skip_serializing_if = "Option::is_none")]
1133    #[serde(default)]
1134    pub fields: Option<Vec<TemplateRefField>>,
1135}
1136
1137/// How templates reference Content Definitions
1138/// and Data Models, and the specific fields it
1139/// wants to include.
1140#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1141#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1142#[derive(Deserialize, Serialize, Debug, Clone)]
1143pub struct TemplateRef {
1144    /// Specifies the index position for the referenced
1145    /// data. Index must be unique across flags, params, data models and
1146    /// content definitions.
1147    pub idx: u8,
1148    /// Name of the model or content definition.
1149    pub name: String,
1150    /// Which fields to include.
1151    pub fields: Vec<TemplateRefField>,
1152    /// for requesting multiple top-level items
1153    ///
1154    /// note: only supported for content. models use relationships.
1155    #[serde(skip_serializing_if = "Option::is_none")]
1156    #[serde(default)]
1157    pub all: Option<String>,
1158}
1159
1160/// Server flags can only be used in templates whose cache is
1161/// set to "Never".
1162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1163#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1164#[derive(Deserialize, Serialize, Debug, Clone)]
1165pub struct TemplateFlagRef {
1166    /// Specifies the index position for the referenced
1167    /// data. Index must be unique across flags, data models and
1168    /// content definitions.
1169    pub idx: u8,
1170    /// Name of the flag.
1171    pub name: String,
1172}
1173
1174#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1175#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1176#[derive(Deserialize, Serialize, Debug, Clone)]
1177pub struct TemplateParamRef {
1178    /// Specifies the index position for the referenced
1179    /// data. Index must be unique across flags, data models, params, and
1180    /// content definitions.
1181    pub idx: u8,
1182    /// Name of the param.
1183    pub name: String,
1184}
1185
1186#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1187#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1188#[derive(Deserialize, Serialize, Debug, Clone)]
1189pub struct TemplateCache {
1190    #[serde(skip_serializing_if = "Option::is_none")]
1191    #[serde(default)]
1192    pub stored: Option<StoredCache>,
1193
1194    #[serde(skip_serializing_if = "Option::is_none")]
1195    #[serde(default)]
1196    pub http: Option<HttpCache>,
1197}
1198
1199/// Corresponds to <https://docs.rs/wasm-opt/latest/wasm_opt/struct.OptimizationOptions.html#impl-OptimizationOptions>.
1200///
1201/// Certain levels may not work with `wasmtime`/`cranelift`.
1202#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1203#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1204#[derive(Deserialize, Serialize, Debug, Clone)]
1205pub enum WasmOpt {
1206    Size,
1207    SizeAggressive,
1208    Level0,
1209    Level1,
1210    Level2,
1211    Level3,
1212    Level4,
1213}
1214
1215impl WasmOpt {
1216    #[must_use]
1217    pub fn as_flag(&self) -> &'static str {
1218        match self {
1219            Self::Size => "-Os",
1220            Self::SizeAggressive => "-Oz",
1221            Self::Level0 => "-O0",
1222            Self::Level1 => "-O1",
1223            Self::Level2 => "-O2",
1224            Self::Level3 => "-O3",
1225            Self::Level4 => "-O4",
1226        }
1227    }
1228}
1229
1230#[allow(clippy::derivable_impls)]
1231impl Default for TemplateFfiVersion {
1232    fn default() -> Self {
1233        Self::V1
1234    }
1235}
1236
1237#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1238#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1239#[derive(Deserialize, Serialize, Debug, Clone)]
1240pub enum TemplateFfiVersion {
1241    V1,
1242}
1243
1244#[allow(clippy::derivable_impls)]
1245impl Default for TemplateFfiSerialization {
1246    fn default() -> Self {
1247        Self::FlexBufferVector
1248    }
1249}
1250
1251/// Input serialization format. Output is always an array of bytes.
1252#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1253#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1254#[derive(Deserialize, Serialize, Debug, Clone)]
1255pub enum TemplateFfiSerialization {
1256    FlexBufferVector,
1257}
1258
1259#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1260#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1261#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1262pub struct TemplateFfi {
1263    pub version: TemplateFfiVersion,
1264    pub serialization: TemplateFfiSerialization,
1265}
1266
1267/// Template configuration for Ordinary Applications.
1268#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1269#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1270#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1271pub struct TemplateConfig {
1272    /// Foreign function interface config
1273    pub ffi: TemplateFfi,
1274
1275    /// Unique index for template.
1276    pub idx: u8,
1277    /// Template's name.
1278    pub name: String,
1279    /// Used as the `content-type` header for HTTP responses.
1280    /// Validated against file extension (if `path` is present).
1281    // todo: switch this to an enum of mime types
1282    pub mime: String,
1283    /// Specifies whether the content in the file should
1284    /// be "minified"/have whitespace removed.
1285    #[serde(skip_serializing_if = "Option::is_none")]
1286    #[serde(default)]
1287    pub minify: Option<bool>,
1288    /// Relative path to the template file
1289    #[serde(skip_serializing_if = "Option::is_none")]
1290    #[serde(default)]
1291    pub path: Option<String>,
1292    /// The route used in the HTTP server to serve this template.
1293    /// Can use segments to bind to properties on models or content
1294    pub route: String,
1295    /// What to check the token fields against. If left blank, route is considered public.
1296    #[serde(skip_serializing_if = "Option::is_none")]
1297    #[serde(default)]
1298    pub protected: Option<Check>,
1299    /// Used to specify the cache policy for this template.
1300    #[serde(skip_serializing_if = "Option::is_none")]
1301    #[serde(default)]
1302    pub cache: Option<TemplateCache>,
1303
1304    /// HTTP Content Security Policy configuration.
1305    ///
1306    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP>
1307    ///
1308    /// "Base" defaults to `default-src 'self';` and tacks on SHA-256 integrity
1309    /// hashes for all inlined scripts and styles (generated at build time) to
1310    /// `script-src 'self' sha256-b64` and `style-src 'self' sha256-b64`, respectively.
1311    ///
1312    /// `https:` is used when not running in `--insecure` mode.
1313    #[serde(skip_serializing_if = "Option::is_none")]
1314    #[serde(default)]
1315    pub csp: Option<HttpCsp>,
1316
1317    #[serde(skip_serializing_if = "Option::is_none")]
1318    #[serde(default)]
1319    pub cors: Option<HttpCors>,
1320
1321    /// Max duration for the template.
1322    ///
1323    /// Unit: seconds
1324    #[serde(skip_serializing_if = "Option::is_none")]
1325    #[serde(default)]
1326    pub timeout: Option<u16>,
1327
1328    /// Used for template-specific variables that don't need to be shared
1329    /// beyond the scope of the given template, and don't warrant a content
1330    /// object.
1331    #[serde(skip_serializing_if = "Option::is_none")]
1332    #[serde(default)]
1333    pub fields: Option<Vec<TemplateField>>,
1334    /// List of global variables to be included with the compiled template
1335    /// binary. Globals are excluded by default and have to be explicitly
1336    /// listed in the globals to be accessed from the template.
1337    #[serde(skip_serializing_if = "Option::is_none")]
1338    #[serde(default)]
1339    pub globals: Option<Vec<String>>,
1340    /// List of flags to be referenced by the template.
1341    #[serde(skip_serializing_if = "Option::is_none")]
1342    #[serde(default)]
1343    pub flags: Option<Vec<TemplateFlagRef>>,
1344    /// List of params to be referenced by the template.
1345    #[serde(skip_serializing_if = "Option::is_none")]
1346    #[serde(default)]
1347    pub params: Option<Vec<TemplateParamRef>>,
1348    /// List of models and what fields the template needs from the models.
1349    /// This is effectively a query definition.
1350    #[serde(skip_serializing_if = "Option::is_none")]
1351    #[serde(default)]
1352    pub models: Option<Vec<TemplateRef>>,
1353    /// List of content definitions and the content definition fields
1354    /// that this template will use.
1355    #[serde(skip_serializing_if = "Option::is_none")]
1356    #[serde(default)]
1357    pub content: Option<Vec<TemplateRef>>,
1358
1359    /// Specifies which actions this template triggers, and adds
1360    /// this template's route pattern to the action's list of valid
1361    /// origins.
1362    #[serde(skip_serializing_if = "Option::is_none")]
1363    #[serde(default)]
1364    pub actions: Option<Vec<String>>,
1365
1366    #[serde(skip_serializing_if = "Option::is_none")]
1367    #[serde(default)]
1368    pub wasm_opt: Option<WasmOpt>,
1369
1370    /// List of build time environment variables.
1371    ///
1372    /// format in template: `{{ YOUR_VAR }}`
1373    #[serde(skip_serializing_if = "Option::is_none")]
1374    #[serde(default)]
1375    pub variables: Option<Vec<String>>,
1376
1377    /// list of names of middleware to include for this template
1378    #[serde(skip_serializing_if = "Option::is_none")]
1379    #[serde(default)]
1380    pub middlewares: Option<Vec<String>>,
1381}
1382
1383#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1384#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1385#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1386pub struct CompressionAlgorithms(pub Vec<CompressionAlgorithm>);
1387
1388impl CompressionAlgorithms {
1389    #[must_use]
1390    fn get_list(&self) -> ArrayVec<CompressionAlgorithm, 4> {
1391        let mut list = ArrayVec::<CompressionAlgorithm, 4>::new();
1392        let mut has_all = false;
1393
1394        for alg in &self.0 {
1395            if *alg == CompressionAlgorithm::All {
1396                has_all = true;
1397            } else if !list.contains(alg) {
1398                list.push(alg.clone());
1399            }
1400        }
1401
1402        if has_all {
1403            for alg in [
1404                CompressionAlgorithm::Brotli,
1405                CompressionAlgorithm::Zstd { level: 17 },
1406                CompressionAlgorithm::Deflate,
1407                CompressionAlgorithm::Gzip,
1408            ] {
1409                if !list.contains(&alg) {
1410                    list.push(alg);
1411                }
1412            }
1413        }
1414
1415        list
1416    }
1417}
1418
1419#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1420#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1421#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1422pub struct AssetsConfig {
1423    /// Relative path to assets directory.
1424    #[serde(skip_serializing_if = "Option::is_none")]
1425    #[serde(default)]
1426    pub dir_path: Option<String>,
1427
1428    /// Note: must start with a `/` and cannot end with a `/` unless
1429    /// `/` is the entire route.
1430    #[serde(default = "AssetsConfig::default_base_route")]
1431    pub base_route: String,
1432
1433    /// whether to add `index.html` to the base route
1434    /// and the end of routes having a trailing slash
1435    /// or missing an extension.
1436    ///
1437    /// i.e. `https://example.com/static`, `https://example.com/static/`,
1438    /// `https://example.com/static/{*path}/` and `https://example.com/static/{*path}`
1439    /// (with no extension) would return the contents of
1440    /// `/static/index.html` or `/static/{*path}/index.html`.
1441    ///
1442    /// primarily useful when serving a generated static site.
1443    ///
1444    /// Note: cannot be used with `append_index_ext`
1445    #[serde(skip_serializing_if = "Option::is_none")]
1446    #[serde(default)]
1447    pub append_index_html: Option<bool>,
1448
1449    /// whether the `{base_route}` and `{base_route}/` routes should
1450    /// skip returning the `index.html` at the root of the static dir.
1451    ///
1452    /// useful when the static dir `base_route` is set to `/` but you'd
1453    /// like to have a template use the `/` route.
1454    #[serde(skip_serializing_if = "Option::is_none")]
1455    #[serde(default)]
1456    pub skip_base_route_index_html: Option<bool>,
1457
1458    /// whether to append `.html` to routes that do not
1459    /// include an extension.
1460    ///
1461    /// i.e. `https://example.com/static/about` would return the contents of
1462    /// `/static/about.html`.
1463    ///
1464    /// primarily useful when serving a generated static site.
1465    ///
1466    /// Note: cannot be used with `append_index_html`
1467    #[serde(skip_serializing_if = "Option::is_none")]
1468    #[serde(default)]
1469    pub append_html_ext: Option<bool>,
1470
1471    /// will not strip the exif data from images.
1472    ///
1473    /// Important: only use if you want all metadata (including
1474    /// location data) on your photos.
1475    #[serde(skip_serializing_if = "Option::is_none")]
1476    #[serde(default)]
1477    pub preserve_exif: Option<bool>,
1478
1479    /// content security policy for HTML assets
1480    #[serde(skip_serializing_if = "Option::is_none")]
1481    #[serde(default)]
1482    pub html_csp: Option<HttpCsp>,
1483
1484    /// HTTP cache configuration.
1485    ///
1486    /// TODO: provide a way to pattern match files for which to apply
1487    /// TODO: specific cache controls.
1488    #[serde(skip_serializing_if = "Option::is_none")]
1489    #[serde(default)]
1490    pub http: Option<HttpCache>,
1491
1492    /// Which encodings to use when precompressing assets.
1493    ///
1494    /// Serving priority is dictated by specified order.
1495    #[serde(skip_serializing_if = "Option::is_none")]
1496    #[serde(default)]
1497    pub precompression: Option<CompressionAlgorithms>,
1498
1499    #[serde(skip)]
1500    #[serde(default)]
1501    pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 4>>,
1502
1503    /// Determines whether CSS files should be minified,
1504    /// prior to write.
1505    #[serde(skip_serializing_if = "Option::is_none")]
1506    #[serde(default)]
1507    pub minify_css: Option<bool>,
1508
1509    /// Determines whether JS files should be minified,
1510    /// prior to write.
1511    #[serde(skip_serializing_if = "Option::is_none")]
1512    #[serde(default)]
1513    pub minify_js: Option<bool>,
1514
1515    /// Determines whether HTML files should be minified,
1516    /// prior to write.
1517    #[serde(skip_serializing_if = "Option::is_none")]
1518    #[serde(default)]
1519    pub minify_html: Option<bool>,
1520
1521    #[serde(skip)]
1522    #[serde(default)]
1523    pub internal_cache_control_header_value: Option<String>,
1524
1525    /// list of names of middleware to include for the assets
1526    #[serde(skip_serializing_if = "Option::is_none")]
1527    #[serde(default)]
1528    pub middlewares: Option<Vec<String>>,
1529}
1530
1531impl AssetsConfig {
1532    fn default_base_route() -> String {
1533        "/assets".to_string()
1534    }
1535}
1536
1537impl AssetsConfig {
1538    pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
1539        if let Some(http_cache) = &self.http
1540            && let Some(http_cache_control) = &http_cache.cache_control
1541        {
1542            let mut header = String::new();
1543            http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
1544
1545            self.internal_cache_control_header_value = Some(header);
1546        }
1547
1548        Ok(())
1549    }
1550}
1551
1552#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1553#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1554#[derive(Deserialize, Serialize, Debug, Clone)]
1555pub struct FragmentsConfig {
1556    /// Relative path to fragments directory.
1557    pub dir_path: String,
1558}
1559
1560/// Global constant definitions, for use in [`ordinary_template::Template`]s.
1561#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1562#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1563#[derive(Deserialize, Serialize, Debug, Clone)]
1564pub struct Global {
1565    /// Name of the global constant.
1566    pub name: String,
1567    /// Type definition for the global variable.
1568    pub kind: Kind,
1569    /// JSON value of the global constant.
1570    pub value: serde_json::Value,
1571}
1572
1573/// Where Ordinary will look for the secret.
1574#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1575#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1576#[derive(Deserialize, Serialize, Debug, Clone)]
1577pub enum SecretSource {
1578    /// Name of the host provided environment variable/secret.
1579    ///
1580    /// `Env` secrets are available to every tenant for a given
1581    /// API server (when running in "multi" mode).
1582    ///
1583    /// This is useful in scenarios where a provider running the API
1584    /// server would like to expose convenient integrations with 3rd parties, for
1585    /// which it only wants to maintain a single set of credentials.
1586    ///
1587    /// `Env` secrets are also useful when you're running a standalone
1588    /// application and do not need a more complicated secrets management
1589    /// paradigm.
1590    Env,
1591    /// Name of a stored secret.
1592    ///
1593    /// `Stored` secrets are application scoped, and can be set
1594    /// through an API server on which the application runs.
1595    ///
1596    /// `Stored` secrets live in their own database and are only
1597    /// accessible to components with permissions.
1598    Stored,
1599    // `Manager` mode allows you to select an external secrets
1600    // manager, by setting a `Stored` secret with the external `Manager`'s
1601    // token/password.
1602    // todo: Manager
1603}
1604
1605/// Where the secret is accessible from.
1606#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1607#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1608#[derive(Deserialize, Serialize, Debug, Clone)]
1609pub enum SecretVisibility {
1610    // `Actions` visibility level should only be set in cases
1611    // where you're confident in the code that's being executed,
1612    // or the secret is not shared between trusted/untrusted modules.
1613    //
1614    // Because `Action` visible secrets are passed into the module,
1615    // they can be (intentionally OR unintentionally) leaked if
1616    // included in what's returned by the module.
1617    // todo: Actions,
1618    /// `Integrations` level visibility runs the risk of unintentionally
1619    /// sending a secret to the incorrect endpoint, but does not expose
1620    /// secrets to any user defined modules.
1621    Integrations,
1622}
1623
1624/// Mechanism for exposing secrets to `Integration`s.
1625#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1626#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1627#[derive(Deserialize, Serialize, Debug, Clone)]
1628pub struct Secret {
1629    /// Name of the secret.
1630    pub name: String,
1631    /// Where to retrieve the secret from.
1632    pub source: SecretSource,
1633    /// Where the secret is to be used.
1634    pub visibility: SecretVisibility,
1635}
1636
1637/// Option for the feature flag.
1638#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1639#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1640#[derive(Deserialize, Serialize, Debug, Clone)]
1641pub struct FlagOption {
1642    /// Unique index for this flag option.
1643    pub idx: u8,
1644    /// Name of this feature flag option.
1645    pub name: String,
1646    /// Percentage of users that will have this
1647    /// flag turned on.
1648    pub percentage: u8,
1649}
1650
1651/// Feature flag definition.
1652///
1653/// Note: Flags use cookies for non-logged in users,
1654/// and use fields set on their token after they're logged in
1655/// so that users have the ability to configure their preference
1656/// if they have one.
1657#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1658#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1659#[derive(Deserialize, Serialize, Debug, Clone)]
1660pub struct Flag {
1661    /// Unique index for the feature flag.
1662    pub idx: u8,
1663    /// Name of the feature flag.
1664    pub name: String,
1665    /// Options for this flag. Percentage must
1666    /// total 100.
1667    pub options: Vec<FlagOption>,
1668}
1669
1670#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1671#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1672#[derive(Deserialize, Serialize, Debug, Clone)]
1673pub struct ContentDefinition {
1674    /// Unique index for the content definition.
1675    pub idx: u8,
1676
1677    /// Name of the content definition.
1678    pub name: String,
1679
1680    /// Fields for the content definition.
1681    ///
1682    /// Note: only fields with the `String` and `Uuid` kinds can
1683    /// be indexed (for now).
1684    pub fields: Vec<Field>,
1685
1686    /// configure scripts for responding to object lifecycle events.
1687    #[serde(skip_serializing_if = "Option::is_none")]
1688    #[serde(default)]
1689    pub lifecycle: Option<ContentObjectLifecycle>,
1690}
1691
1692#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1693#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1694#[derive(Deserialize, Serialize, Debug, Clone)]
1695pub struct ContentObjectLifecycle {
1696    /// run script before all commands in the set
1697    #[serde(skip_serializing_if = "Option::is_none")]
1698    #[serde(default)]
1699    pub before_all: Option<Vec<Vec<String>>>,
1700    /// hook for running scripts before/after an object
1701    /// is added via CLI or studio.
1702    ///
1703    /// `after` receives the stringified JSON object as the
1704    /// first stdarg.
1705    #[serde(skip_serializing_if = "Option::is_none")]
1706    #[serde(default)]
1707    pub on_add: Option<LifecycleBeforeAfterScripts>,
1708    /// hook for running scripts before/after an object
1709    /// is edited via CLI or studio.
1710    ///
1711    /// `after` receives the stringified JSON object as the
1712    /// first stdarg.
1713    #[serde(skip_serializing_if = "Option::is_none")]
1714    #[serde(default)]
1715    pub on_edit: Option<LifecycleBeforeAfterScripts>,
1716    /// hook for running scripts before/after an object
1717    /// is deleted via CLI or studio.
1718    ///
1719    /// `after` receives the stringified JSON object as the
1720    /// first stdarg.
1721    #[serde(skip_serializing_if = "Option::is_none")]
1722    #[serde(default)]
1723    pub on_delete: Option<LifecycleBeforeAfterScripts>,
1724}
1725
1726/// Content is used for static values that should be updated
1727/// independent of document structure, stylings or behavior.
1728///
1729/// Content is stored in a denormalized format for indexed fields
1730/// to optimize for maximum read efficiency.
1731#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1732#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1733#[derive(Deserialize, Serialize, Debug, Clone, Default)]
1734pub struct Content {
1735    /// Specifies the path to the JSON file which contains
1736    /// the content objects.
1737    pub file_path: String,
1738    /// Definitions/structure of the content objects in the
1739    /// `content.json`.
1740    pub definitions: Vec<ContentDefinition>,
1741
1742    #[serde(skip_serializing_if = "Option::is_none")]
1743    #[serde(default)]
1744    pub update: Option<ContentUpdateConfig>,
1745}
1746
1747#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1748#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1749#[derive(Deserialize, Serialize, Debug, Clone)]
1750pub struct ContentUpdateConfig {
1751    #[serde(skip_serializing_if = "Option::is_none")]
1752    #[serde(default)]
1753    pub lifecycle: Option<LifecycleBeforeAfterScripts>,
1754}
1755
1756#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1757#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1758#[derive(Deserialize, Serialize, Debug, Clone)]
1759pub enum UuidVersion {
1760    V4,
1761    V7,
1762}
1763
1764/// Defines a model in the Ordinary Database.
1765#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1766#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1767#[derive(Deserialize, Serialize, Debug, Clone)]
1768pub struct ModelConfig {
1769    /// Index of the model. Used for its kind
1770    /// for storage keys. There can be no gaps in
1771    /// index values.
1772    ///
1773    /// Note: the first (0) index is always skipped as it
1774    /// is reserved for the item UUID.
1775    pub idx: u8,
1776    /// Name of the model.
1777    pub name: String,
1778    /// Fields on the model.
1779    pub fields: Vec<Field>,
1780    /// Every model is generated with a UUID key. If this value
1781    /// is blank, it will default to V4. If you'd like records to
1782    /// be ordered by time, V7 is recommended.
1783    #[serde(skip_serializing_if = "Option::is_none")]
1784    #[serde(default)]
1785    pub uuid: Option<UuidVersion>,
1786}
1787
1788#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1789#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1790#[derive(Deserialize, Serialize, Debug, Clone)]
1791pub enum IntegrationProtocolHttpEncoding {
1792    Json,
1793    Text,
1794    None,
1795}
1796
1797/// The protocol for the integration.
1798#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1799#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1800#[derive(Deserialize, Serialize, Debug, Clone)]
1801pub enum IntegrationProtocol {
1802    /// For integrating an external HTTP API.
1803    Http {
1804        /// HTTP method.
1805        method: String,
1806        /// static http headers
1807        headers: Vec<(String, String)>,
1808        /// how to encode the value passed from the action
1809        send_encoding: IntegrationProtocolHttpEncoding,
1810        /// how to decode the value passed back to the action
1811        recv_encoding: IntegrationProtocolHttpEncoding,
1812    },
1813    // When integrating an external gRPC API.
1814    // todo: Grpc { metadata: Vec<(String, String)> },
1815    // When integrating an external Cap'n Proto API.
1816    // todo: CapnProto,
1817    // When integrating an external GraphQL API.
1818    // todo: GraphQL,
1819    // When integrating an external PostgreSQL database.
1820    // todo: Postgres { statement: String },
1821    // When integrating an external OpenSearch database.
1822    // todo: OpenSearch,
1823    // For integrating with SMTP mail server
1824    // todo: Smtp,
1825}
1826
1827/// The mechanism for reverse proxying and calling
1828/// out to external APIs.
1829#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1830#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1831#[derive(Deserialize, Serialize, Debug, Clone)]
1832pub struct IntegrationConfig {
1833    /// Unique index for integration
1834    pub idx: u8,
1835    /// Name of the integration.
1836    pub name: String,
1837    /// Protocol used for communicating with the external service.
1838    pub protocol: IntegrationProtocol,
1839    /// Endpoint that the external service lives at.
1840    pub endpoint: String,
1841    /// Definition for the parameters of the receiving service.
1842    ///
1843    /// (Automatically translated to the service's protocol/encoding format).
1844    pub send: Kind,
1845    /// Definition for the returned value of the external service.
1846    ///
1847    /// (Automatically translated from the service's protocol/encoding format).
1848    pub recv: Kind,
1849    /// Names of secrets to include
1850    #[serde(skip_serializing_if = "Option::is_none")]
1851    #[serde(default)]
1852    pub secrets: Option<Vec<String>>,
1853
1854    /// Max duration for the template.
1855    ///
1856    /// Unit: seconds
1857    #[serde(skip_serializing_if = "Option::is_none")]
1858    #[serde(default)]
1859    pub timeout: Option<u16>,
1860}
1861
1862/// The language in which the action is written.
1863///
1864/// Many more languages will be supported in the future.
1865#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1866#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1867#[derive(Deserialize, Serialize, Debug, Clone)]
1868pub enum ActionLang {
1869    /// Action is written in the Rust programming language.
1870    ///
1871    /// Uses zero-copy `FlexBuffer` vectors for serialization format.
1872    Rust,
1873    /// Action is written in JavaScript.
1874    ///
1875    /// `QuickJS` runtime embedded in a Rust WASM which itself uses
1876    /// `FlexBuffer` vectors for FFI serialization, but then translates
1877    /// to JSON when communicating across the `QuickJS` runtime barrier.
1878    JavaScript,
1879    // todo: Golang,
1880    // todo: DotNet,
1881    // todo: Kotlin,
1882}
1883
1884/// Model operations that an action is allowed to make.
1885#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1886#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1887#[derive(Deserialize, Serialize, Debug, Clone)]
1888pub enum ActionAccessModelOps {
1889    /// Can create an item for this model.
1890    Insert,
1891    /// Can get an item for this model by UUID or Index.
1892    Get,
1893    /// Can query an item for this model by queryable field.
1894    Query,
1895    /// Can search an item for this model by searchable field.
1896    Search,
1897    /// Can update an item for this model.
1898    Update,
1899    /// Can delete an item for this model.
1900    Delete,
1901}
1902
1903/// Auth operations that an action can make.
1904#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1905#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1906#[derive(Deserialize, Serialize, Debug, Clone)]
1907pub enum ActionAccessAuthOps {
1908    /// Allows the action the ability to set
1909    /// a user's access token fields/claims.
1910    SetTokenFields,
1911}
1912
1913/// Defines access permissions that an Ordinary
1914/// Action can configure.
1915#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1916#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1917#[derive(Deserialize, Serialize, Debug, Clone)]
1918pub enum ActionAccessPermission {
1919    /// Provides the action access to a given model.
1920    Model {
1921        /// Name of the model.
1922        name: String,
1923        /// List of allowed operations that the action
1924        /// can take on the model.
1925        ops: Vec<ActionAccessModelOps>,
1926    },
1927    /// Provides the action access to a given content def.
1928    Content {
1929        /// Content definition name.
1930        name: String,
1931    },
1932    /// Provides the action access to a given integration.
1933    Integration {
1934        /// Name of the integration the action can access.
1935        name: String,
1936    },
1937    /// Provides the action access to another action.
1938    Action {
1939        /// Name of the other action.
1940        name: String,
1941    },
1942    /// Provides the action access to Ordinary Auth.
1943    Auth {
1944        /// Which operations the action is allowed to take.
1945        ops: Vec<ActionAccessAuthOps>,
1946    },
1947}
1948
1949#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1950#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1951#[derive(Deserialize, Serialize, Debug, Clone)]
1952pub enum HttpMethod {
1953    PUT,
1954    POST,
1955    GET,
1956    DELETE,
1957}
1958
1959#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1960#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1961#[derive(Deserialize, Serialize, Debug, Clone)]
1962pub enum ActionTriggerModelOps {
1963    Insert,
1964    Update,
1965    Delete,
1966}
1967
1968/// Action trigger options.
1969#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
1970#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
1971#[derive(Deserialize, Serialize, Debug, Clone)]
1972pub enum ActionTrigger {
1973    /// HTTP request with the Ordinary data format
1974    Ordinary,
1975    /// JSON formatted API call
1976    Json {
1977        /// API rout
1978        route: String,
1979        /// HTTP method
1980        method: HttpMethod,
1981    },
1982    /// Web Form Submission
1983    Form {
1984        /// endpoint the form should point at
1985        route: String,
1986        /// method for the form to use
1987        method: HttpMethod,
1988        /// redirect for after submission.
1989        ///
1990        /// note: it is allowed to use return values in the route
1991        /// via {return} or {`return.some_field_name`}
1992        redirect: String,
1993    },
1994    /// Login event from Auth
1995    Login,
1996    /// Registration event from Auth
1997    Registration,
1998    /// For when content updates
1999    Content { name: String },
2000    // Run on model insert/update/delete
2001    // with affected item as argument.
2002    // todo: Model {
2003    //     name: String,
2004    //     op: ActionAccessModelOps,
2005    // },
2006
2007    // Have the action triggered on a regular cadence.
2008    // todo: Job { expression: String },
2009
2010    // gRPC formatted request
2011    // todo: Grpc,
2012}
2013
2014#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2015#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2016#[derive(Deserialize, Serialize, Debug, Clone)]
2017pub enum ActionFfiVersion {
2018    V1,
2019}
2020
2021/// Input/output serialization for module and host functions.
2022#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2023#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2024#[derive(Deserialize, Serialize, Debug, Clone)]
2025pub enum ActionFfiSerialization {
2026    FlexBufferVector,
2027}
2028
2029#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2030#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2031#[derive(Deserialize, Serialize, Debug, Clone)]
2032pub struct ActionFfi {
2033    pub version: ActionFfiVersion,
2034    pub serialization: ActionFfiSerialization,
2035}
2036
2037/// Configuration parameters for Ordinary Actions.
2038#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2039#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2040#[derive(Deserialize, Serialize, Debug, Clone)]
2041pub struct ActionConfig {
2042    /// Foreign function interface config
2043    pub ffi: ActionFfi,
2044    /// Unique index value for action.
2045    pub idx: u8,
2046    /// Action name. Must be unique.
2047    pub name: String,
2048    /// The source language the action is written in.
2049    pub lang: ActionLang,
2050    /// Relative path to the source directory for the action.
2051    #[serde(skip_serializing_if = "Option::is_none")]
2052    #[serde(default)]
2053    pub dir_path: Option<String>,
2054    /// What to check the token fields against. If blank
2055    /// action is public.
2056    #[serde(skip_serializing_if = "Option::is_none")]
2057    #[serde(default)]
2058    pub protected: Option<Check>,
2059    /// whether the storage interactions
2060    /// are executed under a single transaction.
2061    #[serde(skip_serializing_if = "Option::is_none")]
2062    #[serde(default)]
2063    pub transactional: Option<bool>,
2064    /// Which Ordinary Application resources the action has access to.
2065    pub access: Vec<ActionAccessPermission>,
2066    /// Input definition for the action.
2067    pub accepts: Kind,
2068    /// Output definition for the action.
2069    pub returns: Kind,
2070    /// How this action is called (i.e. side effect from DB/Auth,
2071    /// http API call, browser form submission, etc.)
2072    pub triggered_by: Vec<ActionTrigger>,
2073
2074    /// Max duration for the action.
2075    ///
2076    /// Unit: seconds
2077    #[serde(skip_serializing_if = "Option::is_none")]
2078    #[serde(default)]
2079    pub timeout: Option<u16>,
2080
2081    #[serde(skip_serializing_if = "Option::is_none")]
2082    #[serde(default)]
2083    pub cors: Option<HttpCors>,
2084
2085    #[serde(skip_serializing_if = "Option::is_none")]
2086    #[serde(default)]
2087    pub wasm_opt: Option<WasmOpt>,
2088
2089    /// Whether the action should have bindings for API server interaction.
2090    ///
2091    /// Can only be set on applications which have been explicitly
2092    /// allow-listed by the API server administrator via their domain.
2093    #[serde(skip_serializing_if = "Option::is_none")]
2094    #[serde(default)]
2095    pub privileged: Option<bool>,
2096
2097    /// List of build time environment variables.
2098    ///
2099    /// format in template: `{{ YOUR_VAR }}`
2100    #[serde(skip_serializing_if = "Option::is_none")]
2101    #[serde(default)]
2102    pub variables: Option<Vec<String>>,
2103
2104    /// list of names of middleware to include for this action
2105    #[serde(skip_serializing_if = "Option::is_none")]
2106    #[serde(default)]
2107    pub middlewares: Option<Vec<String>>,
2108}
2109
2110#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2111#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2112#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2113pub struct ErrorConfig {
2114    /// Refers to the error template by name.
2115    ///
2116    /// Note: if set, will override the `asset` field
2117    #[serde(skip_serializing_if = "Option::is_none")]
2118    #[serde(default)]
2119    pub template: Option<String>,
2120
2121    /// Refers to the asset by path.
2122    ///
2123    /// Note: if `template` is set it will override this field
2124    #[serde(skip_serializing_if = "Option::is_none")]
2125    #[serde(default)]
2126    pub asset: Option<String>,
2127}
2128
2129#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2130#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2131#[derive(Deserialize, Serialize, Debug, Clone)]
2132pub enum RuntimeMode {
2133    /// Application will run on the shared multithreaded
2134    /// tokio runtime.
2135    Shared,
2136    /// Application will run on a separate thread with its
2137    /// own single-threaded tokio runtime.
2138    SingleThreaded,
2139    /// Application will run on a separate thread with its
2140    /// own multithreaded tokio runtime.
2141    MultiThreaded,
2142}
2143
2144#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2145#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2146#[derive(Deserialize, Serialize, Debug, Clone)]
2147pub enum HttpCorsAllowHeaders {
2148    Any,
2149    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers>
2150    Headers(Vec<String>),
2151}
2152
2153#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2154#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2155#[derive(Deserialize, Serialize, Debug, Clone)]
2156pub enum HttpCorsExposeHeaders {
2157    Any,
2158    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers>
2159    Headers(Vec<String>),
2160}
2161
2162#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2163#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2164#[derive(Deserialize, Serialize, Debug, Clone)]
2165pub enum HttpCorsAllowMethods {
2166    Any,
2167    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods>
2168    Methods(Vec<String>),
2169}
2170
2171#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2172#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2173#[derive(Deserialize, Serialize, Debug, Clone)]
2174pub enum HttpCorsAllowOrigin {
2175    Any,
2176    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin>
2177    Origins(Vec<String>),
2178}
2179
2180#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2181#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2182#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2183pub struct HttpCors {
2184    pub allow_credentials: Option<bool>,
2185
2186    pub allow_headers: Option<HttpCorsAllowHeaders>,
2187
2188    /// unit: Seconds
2189    pub max_age: Option<u32>,
2190
2191    pub allow_methods: Option<HttpCorsAllowMethods>,
2192
2193    pub allow_origin: Option<HttpCorsAllowOrigin>,
2194
2195    pub expose_headers: Option<HttpCorsExposeHeaders>,
2196
2197    pub allow_private_network: Option<bool>,
2198}
2199
2200impl HttpCors {
2201    #[must_use]
2202    pub fn overwrite(&self, base: &Self) -> Self {
2203        Self {
2204            allow_credentials: if self.allow_credentials.is_none() {
2205                base.allow_credentials
2206            } else {
2207                self.allow_credentials
2208            },
2209            allow_headers: if self.allow_headers.is_none() {
2210                base.allow_headers.clone()
2211            } else {
2212                self.allow_headers.clone()
2213            },
2214            max_age: if self.max_age.is_none() {
2215                base.max_age
2216            } else {
2217                self.max_age
2218            },
2219            allow_methods: if self.allow_methods.is_none() {
2220                base.allow_methods.clone()
2221            } else {
2222                self.allow_methods.clone()
2223            },
2224            allow_origin: if self.allow_origin.is_none() {
2225                base.allow_origin.clone()
2226            } else {
2227                self.allow_origin.clone()
2228            },
2229            expose_headers: if self.expose_headers.is_none() {
2230                base.expose_headers.clone()
2231            } else {
2232                self.expose_headers.clone()
2233            },
2234            allow_private_network: if self.allow_private_network.is_none() {
2235                base.allow_private_network
2236            } else {
2237                self.allow_private_network
2238            },
2239        }
2240    }
2241}
2242
2243#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2244#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2245#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2246pub struct HttpCsp {
2247    /// defaults to `'self'` (`default-src ` does not need to be included)
2248    #[serde(skip_serializing_if = "Option::is_none")]
2249    #[serde(default)]
2250    pub default_src: Option<String>,
2251
2252    /// defaults to unset unless inline hashes are included,
2253    /// in which case the directive will start with `'self'` (`script-src ` does not need to be included).
2254    #[serde(skip_serializing_if = "Option::is_none")]
2255    #[serde(default)]
2256    pub script_src: Option<String>,
2257
2258    /// defaults to unset unless inline hashes are included,
2259    /// in which case the directive will start with `'self'` (`style-src ` does not need to be included).
2260    #[serde(skip_serializing_if = "Option::is_none")]
2261    #[serde(default)]
2262    pub style_src: Option<String>,
2263
2264    /// defaults to unset.
2265    ///
2266    /// (`font-src ` does not need to be included).
2267    #[serde(skip_serializing_if = "Option::is_none")]
2268    #[serde(default)]
2269    pub font_src: Option<String>,
2270
2271    /// defaults to unset.
2272    ///
2273    /// (`img-src ` does not need to be included).
2274    #[serde(skip_serializing_if = "Option::is_none")]
2275    #[serde(default)]
2276    pub img_src: Option<String>,
2277
2278    /// defaults to unset.
2279    ///
2280    /// (`frame-src ` does not need to be included).
2281    #[serde(skip_serializing_if = "Option::is_none")]
2282    #[serde(default)]
2283    pub frame_src: Option<String>,
2284
2285    /// defaults to `true`.
2286    #[serde(skip_serializing_if = "Option::is_none")]
2287    #[serde(default)]
2288    pub include_inline_hashes: Option<bool>,
2289}
2290
2291impl HttpCsp {
2292    #[must_use]
2293    #[allow(clippy::too_many_lines)]
2294    pub fn build_string(
2295        &self,
2296        base: &Self,
2297        inline_style_hashes: Option<Vec<String>>,
2298        inline_script_hashes: Option<Vec<String>>,
2299        script_urls: Option<Vec<String>>,
2300        secure: bool,
2301        has_wasm: bool,
2302    ) -> String {
2303        let mut out = String::new();
2304
2305        let include_inline_hashes = self
2306            .include_inline_hashes
2307            .unwrap_or(base.include_inline_hashes.unwrap_or(true));
2308
2309        let default_src = self
2310            .default_src
2311            .clone()
2312            .unwrap_or(base.default_src.clone().unwrap_or("'self'".to_string()));
2313
2314        if !default_src.is_empty() {
2315            out.push_str("default-src ");
2316            out.push_str(default_src.as_str());
2317            out.push_str("; ");
2318        }
2319
2320        let mut script_src = self
2321            .script_src
2322            .clone()
2323            .unwrap_or(base.script_src.clone().unwrap_or_default());
2324
2325        if include_inline_hashes
2326            && let Some(script_hashes) = inline_script_hashes
2327            && !script_hashes.is_empty()
2328        {
2329            if script_src.is_empty() {
2330                script_src.push_str("'self'");
2331                if has_wasm {
2332                    script_src.push_str(" 'wasm-unsafe-eval'");
2333                }
2334            }
2335
2336            for hash in script_hashes {
2337                script_src.push_str(" '");
2338                script_src.push_str(hash.as_str());
2339                script_src.push('\'');
2340            }
2341        }
2342
2343        if let Some(script_urls) = script_urls {
2344            if script_src.is_empty() {
2345                script_src.push_str("'self'");
2346            }
2347
2348            for script_url in script_urls {
2349                script_src.push(' ');
2350                script_src.push_str(&script_url);
2351            }
2352        }
2353
2354        if !script_src.is_empty() {
2355            out.push_str("script-src ");
2356            out.push_str(script_src.as_str());
2357            out.push_str("; ");
2358        }
2359
2360        let mut style_src = self
2361            .style_src
2362            .clone()
2363            .unwrap_or(base.style_src.clone().unwrap_or_default());
2364
2365        if include_inline_hashes
2366            && let Some(style_hashes) = inline_style_hashes
2367            && !style_hashes.is_empty()
2368        {
2369            if style_src.is_empty() {
2370                style_src.push_str("'self'");
2371            }
2372
2373            for hash in style_hashes {
2374                style_src.push_str(" '");
2375                style_src.push_str(hash.as_str());
2376                style_src.push('\'');
2377            }
2378        }
2379
2380        if !style_src.is_empty() {
2381            out.push_str("style-src ");
2382            out.push_str(style_src.as_str());
2383            out.push_str("; ");
2384        }
2385
2386        let font_src = self
2387            .font_src
2388            .clone()
2389            .unwrap_or(base.font_src.clone().unwrap_or_default());
2390        if !font_src.is_empty() {
2391            out.push_str("font-src ");
2392            out.push_str(font_src.as_str());
2393            out.push_str("; ");
2394        }
2395
2396        let img_src = self
2397            .img_src
2398            .clone()
2399            .unwrap_or(base.img_src.clone().unwrap_or_default());
2400        if !img_src.is_empty() {
2401            out.push_str("img-src ");
2402            out.push_str(img_src.as_str());
2403            out.push_str("; ");
2404        }
2405
2406        let frame_src = self
2407            .frame_src
2408            .clone()
2409            .unwrap_or(base.frame_src.clone().unwrap_or_default());
2410        if !frame_src.is_empty() {
2411            out.push_str("frame-src ");
2412            out.push_str(frame_src.as_str());
2413            out.push_str("; ");
2414        }
2415
2416        if secure {
2417            out.push_str("upgrade-insecure-requests; ");
2418        }
2419
2420        out.push_str("report-to csp");
2421
2422        out = out.trim().to_string();
2423        out
2424    }
2425}
2426
2427#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2428#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2429#[derive(Deserialize, Serialize, Debug, Clone)]
2430pub struct LifecycleBeforeAfterScripts {
2431    #[serde(skip_serializing_if = "Option::is_none")]
2432    #[serde(default)]
2433    pub before: Option<Vec<Vec<String>>>,
2434    #[serde(skip_serializing_if = "Option::is_none")]
2435    #[serde(default)]
2436    pub after: Option<Vec<Vec<String>>>,
2437}
2438
2439#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2440#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2441#[derive(Deserialize, Serialize, Debug, Clone)]
2442pub struct TopLevelLifecycle {
2443    /// run before every lifecycle operation
2444    pub before_all: Option<Vec<Vec<String>>>,
2445
2446    /// configure build lifecycle hooks
2447    #[serde(skip_serializing_if = "Option::is_none")]
2448    #[serde(default)]
2449    pub build: Option<LifecycleBeforeAfterScripts>,
2450}
2451
2452#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2453#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2454#[derive(Deserialize, Serialize, Debug, Clone)]
2455pub enum RedirectMethod {
2456    /// 307 <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/307>
2457    Temporary,
2458    /// 308 <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/308>
2459    Permanent,
2460}
2461
2462impl Display for RedirectMethod {
2463    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
2464        match self {
2465            Self::Temporary => write!(f, "TEMPORARY"),
2466            Self::Permanent => write!(f, "PERMANENT"),
2467        }
2468    }
2469}
2470
2471#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2472#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2473#[derive(Deserialize, Serialize, Debug, Clone)]
2474pub struct HostRedirect {
2475    /// from host name
2476    pub from: String,
2477    /// to host name
2478    pub to: String,
2479    /// redirect method
2480    pub method: RedirectMethod,
2481}
2482
2483#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2484#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2485#[derive(Deserialize, Serialize, Debug, Clone)]
2486pub struct RouteRedirect {
2487    /// [axum route](https://docs.rs/axum/latest/axum/struct.Router.html#method.route) pattern
2488    pub condition: String,
2489    /// translation regexes map to [`Regex::replace_all`](https://docs.rs/regex/latest/regex/#example-replacement-with-named-capture-groups)
2490    /// where `rule.0` is body of `Regex::new()` and `rule.1` is second param of `re.replace_all()`.
2491    pub rule: (String, String),
2492    /// redirect method
2493    pub method: RedirectMethod,
2494}
2495
2496#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2497#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2498#[derive(Deserialize, Serialize, Debug, Clone)]
2499pub struct Redirects {
2500    /// host redirects
2501    #[serde(skip_serializing_if = "Option::is_none")]
2502    #[serde(default)]
2503    pub host: Option<Vec<HostRedirect>>,
2504    /// route redirects
2505    #[serde(skip_serializing_if = "Option::is_none")]
2506    #[serde(default)]
2507    pub route: Option<Vec<RouteRedirect>>,
2508}
2509
2510/// proxy configuration.
2511///
2512/// **Note:** `path` should not be set at the same time as either `domain`
2513/// or `port`. `path` takes precedence and the other sources will be ignored.
2514///
2515/// if the `path`, `domain` and `port` are all unset, validation will fail.
2516#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2517#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2518#[derive(Deserialize, Serialize, Debug, Clone)]
2519pub struct ProxyConfig {
2520    /// an `axum` [route](https://docs.rs/axum/latest/axum/routing/struct.Router.html#method.route)
2521    /// with a `*path` wildcard that will be appended to the target.
2522    ///
2523    /// i.e. `"/some/path/{*path}"`
2524    ///
2525    /// **Note:** MUST end in a trailing `/{*path}`
2526    #[serde(skip_serializing_if = "Option::is_none")]
2527    #[serde(default)]
2528    pub path: Option<String>,
2529    /// custom domain with an ALIAS/CNAME pointing at the primary `OrdinaryConfig::domain`
2530    /// and a TXT record indicating ownership (i.e. `ordinary-proxy=your.config.domain`).
2531    ///
2532    /// i.e `example.com`
2533    ///
2534    /// **Note:** if a `path` is specified, the `domain` will be ignored
2535    #[serde(skip_serializing_if = "Option::is_none")]
2536    #[serde(default)]
2537    pub domain: Option<String>,
2538
2539    /// preferred port that the proxy will listen on in a `--dedicated-ports`
2540    /// configured multi-tenant server OR a standalone app instance.
2541    ///
2542    /// i.e `8081`
2543    ///
2544    /// **Note:** if a `path` is specified, the `port` will be ignored,
2545    /// and `port` will not be tried without `domain` being present.
2546    #[serde(skip_serializing_if = "Option::is_none")]
2547    #[serde(default)]
2548    pub port: Option<u16>,
2549
2550    /// url for resource to proxy.
2551    ///
2552    /// i.e. `https://example.com`
2553    pub target: String,
2554
2555    /// list of names of middleware to include for this proxy
2556    #[serde(skip_serializing_if = "Option::is_none")]
2557    #[serde(default)]
2558    pub middlewares: Option<Vec<String>>,
2559}
2560
2561#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2562#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2563#[derive(Deserialize, Serialize, Debug, Clone)]
2564pub enum MiddlewareMechanism {
2565    Request { endpoint: String },
2566}
2567
2568#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2569#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2570#[derive(Deserialize, Serialize, Debug, Clone)]
2571pub enum MiddlewareValidationRule {
2572    /// if any of the rules included pass it is valid.
2573    Any(Vec<Box<MiddlewareValidationRule>>),
2574    /// only if all the rules pass is it valid.
2575    All(Vec<Box<MiddlewareValidationRule>>),
2576    /// if the rule is not true then it is valid.
2577    Not(Box<MiddlewareValidationRule>),
2578
2579    /// status code that will allow the
2580    /// request to proceed.
2581    StatusCode(u16),
2582}
2583
2584#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2585#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2586#[derive(Deserialize, Serialize, Debug, Clone, PartialOrd, PartialEq, Ord, Eq)]
2587pub enum MiddlewareValidationComponent {
2588    /// pass headers through to the validator
2589    Headers,
2590    /// append the path to the endpoint's path
2591    Path,
2592    /// append query string params from the request
2593    Query,
2594}
2595
2596#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2597#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2598#[derive(Deserialize, Serialize, Debug, Clone)]
2599pub enum MiddlewareOperation {
2600    Validate {
2601        rule: MiddlewareValidationRule,
2602        components: BTreeSet<MiddlewareValidationComponent>,
2603    },
2604}
2605
2606/// configuration structure for HTTP middleware
2607#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2608#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2609#[derive(Deserialize, Serialize, Debug, Clone)]
2610pub struct MiddlewareConfig {
2611    /// name by which this middleware will be referenced
2612    /// on other resources
2613    pub name: String,
2614
2615    /// the operation the middleware will perform
2616    pub operation: MiddlewareOperation,
2617
2618    /// how the middleware operation will be performed
2619    pub mechanism: MiddlewareMechanism,
2620}
2621
2622/// Config definition for an Ordinary Application
2623#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
2624#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
2625#[derive(Deserialize, Serialize, Debug, Clone, Default)]
2626pub struct OrdinaryConfig {
2627    #[serde(skip_serializing_if = "Option::is_none")]
2628    #[serde(default)]
2629    pub lifecycle: Option<TopLevelLifecycle>,
2630
2631    /// Domain name for the application to be run from the
2632    /// deployment environment.
2633    pub domain: String,
2634
2635    /// additional domains with a CNAME or ALIAS records
2636    /// pointing at the primary `OrdinaryConfig::domain`.
2637    ///
2638    /// add a TXT record in the following format:
2639    ///`ordinary=your.config.domain`
2640    #[serde(skip_serializing_if = "Option::is_none")]
2641    #[serde(default)]
2642    pub cnames: Option<Vec<String>>,
2643
2644    /// specify which of the `domain` or `cnames` is
2645    /// the "canonical" location.
2646    ///
2647    /// this is useful for [indexing](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)
2648    /// and situations where you want to display the primary
2649    /// URL as text on the page itself (i.e. pick one of `example.some.host`, `example.com`, and `www.example.com`).
2650    ///
2651    /// defaults to `domain` if `cnames` is empty. defaults to first `cname` in list if `cnames`
2652    /// are not empty.
2653    #[serde(skip_serializing_if = "Option::is_none")]
2654    #[serde(default)]
2655    pub canonical: Option<String>,
2656
2657    /// configuration of internal redirects
2658    #[serde(skip_serializing_if = "Option::is_none")]
2659    #[serde(default)]
2660    pub redirects: Option<Redirects>,
2661
2662    /// configuration of proxied services
2663    #[serde(skip_serializing_if = "Option::is_none")]
2664    #[serde(default)]
2665    pub proxies: Option<Vec<ProxyConfig>>,
2666
2667    /// list of middleware configurations that can be applied
2668    /// to templates, assets, actions and proxies
2669    #[serde(skip_serializing_if = "Option::is_none")]
2670    #[serde(default)]
2671    pub middlewares: Option<Vec<MiddlewareConfig>>,
2672
2673    #[serde(skip)]
2674    #[serde(default)]
2675    pub internal_middlewares: Option<HashMap<String, MiddlewareConfig>>,
2676
2677    /// list of email addresses that can be used to contact
2678    /// the application owner or administrators.
2679    #[serde(skip_serializing_if = "Option::is_none")]
2680    #[serde(default)]
2681    pub contacts: Option<Vec<String>>,
2682
2683    /// whether contacts should be hidden (defaults to `true`)
2684    #[serde(skip_serializing_if = "Option::is_none")]
2685    #[serde(default)]
2686    pub hide_contacts: Option<bool>,
2687
2688    /// Version of the site build.
2689    pub version: String,
2690
2691    /// Storage size in bytes (rounded up to nearest OS page size).
2692    #[serde(skip_serializing_if = "Option::is_none")]
2693    #[serde(default = "OrdinaryConfig::default_storage_size")]
2694    pub storage_size: Option<u64>,
2695
2696    /// Default request timeout.
2697    ///
2698    /// Unit (seconds).
2699    #[serde(skip_serializing_if = "Option::is_none")]
2700    #[serde(default)]
2701    pub default_timeout: Option<u16>,
2702
2703    /// HTTP Content Security Policy configuration.
2704    ///
2705    /// <https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP>
2706    ///
2707    /// "Base" defaults to `default-src 'self';` and tacks on SHA-256 integrity
2708    /// hashes for all inlined scripts and styles (generated at build time) to
2709    /// `script-src 'self' sha256-b64` and `style-src 'self' sha256-b64`, respectively.
2710    ///
2711    /// `https:` is used when not running in `--insecure` mode.
2712    #[serde(skip_serializing_if = "Option::is_none")]
2713    #[serde(default)]
2714    pub csp: Option<HttpCsp>,
2715
2716    #[serde(skip_serializing_if = "Option::is_none")]
2717    #[serde(default)]
2718    pub cors: Option<HttpCors>,
2719
2720    /// Specifies runtime mode for application on the host.
2721    ///
2722    /// If none is specified, defaults to Shared (or host default).
2723    #[serde(skip_serializing_if = "Option::is_none")]
2724    #[serde(default)]
2725    pub runtime: Option<RuntimeMode>,
2726
2727    /// When set to true, `{{ domain }}/.ordinary/schema`
2728    /// is not addressable.
2729    ///
2730    /// Note: this can break applications which depend on
2731    /// flags, and `action`/`template` query descriptors.
2732    #[serde(skip_serializing_if = "Option::is_none")]
2733    #[serde(default)]
2734    pub hide_schema: Option<bool>,
2735
2736    /// Include template rendering code in the client WASM.
2737    #[serde(skip_serializing_if = "Option::is_none")]
2738    #[serde(default)]
2739    pub client_rendering: Option<bool>,
2740
2741    /// Include E2EE handler code in the client WASM.
2742    #[serde(skip_serializing_if = "Option::is_none")]
2743    #[serde(default)]
2744    pub obfuscation: Option<bool>,
2745
2746    /// Include E2EE handler code in the client WASM.
2747    #[serde(skip_serializing_if = "Option::is_none")]
2748    #[serde(default)]
2749    pub client_events: Option<bool>,
2750
2751    /// Port to be used for standalone "run" instances.
2752    #[serde(skip_serializing_if = "Option::is_none")]
2753    #[serde(default)]
2754    pub port: Option<u16>,
2755    /// port used for redirecting from http when
2756    /// standalone is running in secure mode.
2757    #[serde(skip_serializing_if = "Option::is_none")]
2758    #[serde(default)]
2759    pub redirect_port: Option<u16>,
2760
2761    #[serde(skip_serializing_if = "Option::is_none")]
2762    #[serde(default)]
2763    pub logging: Option<LoggingConfig>,
2764    /// Configures error handling.
2765    ///
2766    /// Note: If not included just the error message will be
2767    /// sent back as text.
2768    #[serde(skip_serializing_if = "Option::is_none")]
2769    #[serde(default)]
2770    pub error: Option<ErrorConfig>,
2771    /// Auth config for the Ordinary application.
2772    #[serde(skip_serializing_if = "Option::is_none")]
2773    #[serde(default)]
2774    pub auth: Option<AuthConfig>,
2775    /// Global constants that can be accessed from templates
2776    #[serde(skip_serializing_if = "Option::is_none")]
2777    #[serde(default)]
2778    pub globals: Option<Vec<Global>>,
2779    /// Secrets that can be used by actions or integrations.
2780    #[serde(skip_serializing_if = "Option::is_none")]
2781    #[serde(default)]
2782    pub secrets: Option<Vec<Secret>>,
2783    /// Feature flags which can be referenced from templates
2784    /// to inform application behavior, and run experiments.
2785    #[serde(skip_serializing_if = "Option::is_none")]
2786    #[serde(default)]
2787    pub flags: Option<Vec<Flag>>,
2788    /// Definitions for static content "types"/object structure
2789    /// that can be used to inform template/page development (i.e.
2790    /// one might define a "post" content definition, and then create
2791    /// a template for their blog).
2792    #[serde(skip_serializing_if = "Option::is_none")]
2793    #[serde(default)]
2794    pub content: Option<Content>,
2795    /// Definitions for the models that will be stored in the Ordinary database.
2796    #[serde(skip_serializing_if = "Option::is_none")]
2797    #[serde(default)]
2798    pub models: Option<Vec<ModelConfig>>,
2799    /// Definitions for the external APIs that will be integrated
2800    /// into the Ordinary application.
2801    #[serde(skip_serializing_if = "Option::is_none")]
2802    #[serde(default)]
2803    pub integrations: Option<Vec<IntegrationConfig>>,
2804    /// IO, access and language configuration for actions that
2805    /// are compiled to and executed as WebAssembly modules.
2806    #[serde(skip_serializing_if = "Option::is_none")]
2807    #[serde(default)]
2808    pub actions: Option<Vec<ActionConfig>>,
2809    /// Specifies the asset directory and per-path configuration
2810    /// details for assets that require preprocessing (TypeScript, SCSS,
2811    /// JavaScript minification, etc.)
2812    #[serde(skip_serializing_if = "Option::is_none")]
2813    #[serde(default)]
2814    pub assets: Option<AssetsConfig>,
2815    /// Configuration for the template fragments
2816    #[serde(skip_serializing_if = "Option::is_none")]
2817    #[serde(default)]
2818    pub fragments: Option<FragmentsConfig>,
2819    /// Configuration for the templates/pages that the application
2820    /// will render. Each template is compiled to a WebAssembly module
2821    /// which accepts runtime arguments for models/content/integrations, and can be
2822    /// executed on either the server or the client.
2823    ///
2824    /// With the option to render on the client, only the result of the server
2825    /// query needs to be sent, in a compact, optimized, format.
2826    ///
2827    /// Currently, all rendering is happening server-side, and only HTML is being sent.
2828    ///
2829    /// In an ideal/future state multiple modes will be supported, even up to a full
2830    /// 'noscript' config.
2831    #[serde(skip_serializing_if = "Option::is_none")]
2832    #[serde(default)]
2833    pub templates: Option<Vec<TemplateConfig>>,
2834}
2835
2836impl OrdinaryConfig {
2837    /// gets ordinary.json from project path and deserializes to struct.
2838    pub fn get(proj_path: &str) -> anyhow::Result<OrdinaryConfig> {
2839        let path = Path::new(proj_path).join("ordinary.json");
2840        let config_json = fs_err::read_to_string(&path)?;
2841
2842        let mut config = match serde_json::from_str::<OrdinaryConfig>(config_json.as_str()) {
2843            Ok(config) => config,
2844            Err(err) => bail!("{}: {err}", path.display()),
2845        };
2846
2847        config.load_internal();
2848
2849        Ok(config)
2850    }
2851
2852    pub fn load_internal(&mut self) {
2853        self.load_internal_middlewares();
2854        self.load_internal_compression();
2855    }
2856
2857    fn load_internal_middlewares(&mut self) {
2858        if let Some(middlewares) = &self.middlewares {
2859            let mut map = HashMap::new();
2860
2861            for middleware in middlewares {
2862                map.insert(middleware.name.clone(), middleware.clone());
2863            }
2864
2865            self.internal_middlewares = Some(map);
2866        }
2867    }
2868
2869    fn load_internal_compression(&mut self) {
2870        if let Some(assets) = self.assets.as_mut()
2871            && let Some(precompression) = &assets.precompression
2872        {
2873            assets.internal_precompression = Some(precompression.get_list());
2874        }
2875
2876        if let Some(templates) = self.templates.as_mut() {
2877            for template in templates {
2878                if let Some(cache) = template.cache.as_mut()
2879                    && let Some(stored) = cache.stored.as_mut()
2880                    && let Some(compression) = &stored.compression
2881                {
2882                    stored.internal_compression = Some(compression.get_list());
2883                }
2884            }
2885        }
2886    }
2887
2888    #[must_use]
2889    pub fn get_middlewares(&self, middleware_names: &Vec<String>) -> Option<Vec<MiddlewareConfig>> {
2890        let mut middleware_configs = vec![];
2891
2892        if let Some(middleware_map) = &self.internal_middlewares {
2893            for middleware in middleware_names {
2894                if let Some(middleware_config) = middleware_map.get(middleware) {
2895                    middleware_configs.push(middleware_config.clone());
2896                }
2897            }
2898        }
2899
2900        if !middleware_configs.is_empty() {
2901            return Some(middleware_configs);
2902        }
2903
2904        None
2905    }
2906
2907    /// gets ordinary.json from project path, deserializes to struct and
2908    /// strips out all client-only values.
2909    pub fn for_send(&self) -> anyhow::Result<OrdinaryConfig> {
2910        let mut config = self.clone();
2911
2912        config.lifecycle = None;
2913
2914        if let Some(assets) = config.assets.as_mut() {
2915            assets.dir_path = None;
2916        }
2917
2918        if let Some(content) = config.content.as_mut() {
2919            content.update = None;
2920
2921            for def in &mut content.definitions {
2922                def.lifecycle = None;
2923            }
2924        }
2925
2926        if let Some(templates) = config.templates.as_mut() {
2927            for template in templates {
2928                template.path = None;
2929                template.wasm_opt = None;
2930                template.minify = None;
2931            }
2932        }
2933
2934        if let Some(actions) = config.actions.as_mut() {
2935            for action in actions {
2936                action.wasm_opt = None;
2937                action.dir_path = None;
2938            }
2939        }
2940
2941        Ok(config)
2942    }
2943
2944    /// check that all configuration values are internally consistent
2945    /// and no non-existent properties or fields are used.
2946    #[instrument(skip_all, err, level = "debug")]
2947    pub fn validate(&self) -> anyhow::Result<()> {
2948        validate(self)
2949    }
2950
2951    // defaults
2952    #[must_use]
2953    #[allow(clippy::unnecessary_wraps)]
2954    pub fn default_storage_size() -> Option<u64> {
2955        Some(5_000_000)
2956    }
2957    // end defaults
2958
2959    #[must_use]
2960    pub fn has_ordinary_actions(&self) -> bool {
2961        if let Some(actions) = &self.actions {
2962            actions
2963                .iter()
2964                .find(|a| {
2965                    for trigger in &a.triggered_by {
2966                        if let ActionTrigger::Ordinary = trigger {
2967                            return true;
2968                        }
2969                    }
2970
2971                    false
2972                })
2973                .is_some()
2974        } else {
2975            false
2976        }
2977    }
2978
2979    /// Check that all the configuration properties are within API specified
2980    /// limits.
2981    ///
2982    /// Note: privileged domains are not subject to limitations checks.
2983    #[allow(clippy::too_many_lines)]
2984    pub fn check_config_against_limits(
2985        &self,
2986        limits: &OrdinaryApiLimits,
2987        privileged_domains: &HashSet<String>,
2988    ) -> anyhow::Result<()> {
2989        check_config_against_limits(self, limits, privileged_domains)
2990    }
2991
2992    pub fn exec_lifecycle_script(
2993        proj_path: &Path,
2994        argument: &Option<String>,
2995        name: &str,
2996        when: &str,
2997        scripts: &Vec<Vec<String>>,
2998    ) -> anyhow::Result<()> {
2999        let span = tracing::info_span!("lifecycle", %when, %name);
3000
3001        span.in_scope(|| {
3002            let curr_dir = env::current_dir()?;
3003            env::set_current_dir(proj_path)?;
3004
3005            for script in scripts {
3006                let mut script_iter = script.iter();
3007
3008                if let Some(command) = script_iter.nth(0) {
3009                    let mut command_str = command.clone();
3010                    let mut command = Command::new(command);
3011
3012                    for arg in script_iter {
3013                        write!(command_str, " {arg}")?;
3014                        command.arg(arg);
3015                    }
3016
3017                    tracing::info!(cmd = %command_str, "exec");
3018
3019                    let output = match &argument {
3020                        Some(arg) => command.arg(arg).output()?,
3021                        None => command.output()?,
3022                    };
3023
3024                    if output.status.success() {
3025                        tracing::info!("success");
3026                    } else {
3027                        let stderr = str::from_utf8(&output.stderr)?;
3028                        let stdout = str::from_utf8(&output.stdout)?;
3029
3030                        tracing::error!(%stderr, %stdout, "failed");
3031                        bail!(stderr.to_string());
3032                    }
3033                }
3034            }
3035
3036            env::set_current_dir(curr_dir)?;
3037
3038            anyhow::Ok(())
3039        })
3040    }
3041}