Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ my-platform/
└── main.rs
```

The generated `config.toml` is seeded with `[schema_forge] project_name = "my-platform"` — the human-facing name shown to users in invitation emails and used as the default email `From` display-name. Edit it to whatever your users should recognize.

### Define a Schema

Create a file at `schemas/crm.schema`:
Expand Down Expand Up @@ -930,6 +932,7 @@ SchemaForge is under active development. All seven crates compile and pass 1123
- Axum JSON API with dynamic CRUD routes and schema management
- React site generator (`schemaforge site generate`) producing a Vite + Tailwind + shadcn app against the JSON API
- Token-based authentication (PASETO) with an auth-store-backed login endpoint
- Email-based user invitation and onboarding with single-use PASETO invite links and SMTP delivery (see [`docs/invitations-reference.md`](docs/invitations-reference.md))
- Cedar authorization policy generation
- Schema-level and field-level access control via `@access` and `@field_access` annotations
- Record-level ownership-based access control
Expand Down
21 changes: 21 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,27 @@ issuer = "schema-forge"
per_user_rpm = 6000
per_client_rpm = 60000

# Human-facing name of this deployment. Shown to onboarding users in
# invitation emails (body + subject) and used as the default email From
# display-name when the `from` below is a bare address. Defaults to
# "SchemaForge" when unset — set it to your application's name.
# [schema_forge]
# project_name = "Bob's Dog Scheduling"

# Outbound email (user invitations, issue #71). Disabled by default.
# The SMTP password is NEVER read from this file — supply it at runtime via
# the SCHEMAFORGE_SMTP_PASSWORD environment variable so the secret stays out
# of git. (acton-service's ACTON_-prefixed env layering can't target the
# `[schema_forge]` section, so this dedicated env var is the supported path.)
# [schema_forge.email]
# enabled = true
# host = "mail.example.gov"
# port = 465 # 465 = implicit TLS (default); 587 = STARTTLS
# tls = "implicit" # or "start_tls"
# from = "Example <noreply@example.gov>"
# username = "noreply@example.gov"
# public_base_url = "https://app.example.gov" # used to build invite-accept links

# Webhook notification settings
# [schema_forge.webhooks]
# enabled = true
Expand Down
3 changes: 2 additions & 1 deletion crates/schema-forge-acton/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "schema-forge-acton"
version = "0.31.0"
version = "0.32.0"
edition = "2021"

[dependencies]
Expand Down Expand Up @@ -43,6 +43,7 @@ toml = "1"
aws-lc-rs = { version = "1", features = ["fips"], optional = true }
rustls = { version = "0.23", default-features = false, features = ["std", "aws_lc_rs", "logging"] }
schema-forge-signing = { version = "0.1.0", path = "../schema-forge-signing" }
lettre = { version = "0.11.22", default-features = false, features = ["tokio1-rustls", "aws-lc-rs", "webpki-roots", "smtp-transport", "builder", "pool", "hostname"] }


[features]
Expand Down
41 changes: 41 additions & 0 deletions crates/schema-forge-acton/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ pub struct SchemaForgeConfig {
/// SchemaForge settings.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaForgeSettings {
/// Human-facing name of this deployment, used wherever an end user sees
/// the application by name rather than seeing the SchemaForge engine.
/// Today that is the invitation email — its body and subject — and the
/// default `From` display-name when [`crate::email::EmailConfig::from`] is
/// a bare address. Defaults to `"SchemaForge"` so existing deployments are
/// unchanged; a "Bob's Dog Scheduling" deployment sets this once and every
/// onboarding touchpoint reads correctly.
#[serde(default = "default_project_name")]
pub project_name: String,

/// The URL path prefix for SchemaForge routes (default: "/forge").
#[serde(default = "default_route_prefix")]
pub route_prefix: String,
Expand All @@ -39,6 +49,12 @@ pub struct SchemaForgeSettings {
#[serde(default)]
pub storage: crate::storage::StorageConfig,

/// Outbound email (SMTP) settings, used by operational flows that must
/// reach a human out-of-band — currently user invitations. Disabled by
/// default; see [`crate::email::EmailConfig`].
#[serde(default)]
pub email: crate::email::EmailConfig,

/// Authorization configuration. Currently exposes operator-defined
/// PASETO custom-claim → Cedar `Forge::Principal` attribute mappings;
/// see [`crate::authz::principal_claims`].
Expand Down Expand Up @@ -123,14 +139,20 @@ fn default_route_prefix() -> String {
"/forge".to_string()
}

fn default_project_name() -> String {
"SchemaForge".to_string()
}

impl Default for SchemaForgeSettings {
fn default() -> Self {
Self {
project_name: default_project_name(),
route_prefix: default_route_prefix(),
auto_generate_cedar_policies: false,
webhooks: crate::webhook::WebhookConfig::default(),
hooks: crate::hooks::HooksConfig::default(),
storage: crate::storage::StorageConfig::default(),
email: crate::email::EmailConfig::default(),
authz: AuthzConfig::default(),
signing: SigningConfig::default(),
client: ClientConfig::default(),
Expand All @@ -153,11 +175,13 @@ mod tests {
fn serde_roundtrip_preserves_all_fields() {
let config = SchemaForgeConfig {
schema_forge: SchemaForgeSettings {
project_name: "Bob's Dog Scheduling".to_string(),
route_prefix: "/api/forge".to_string(),
auto_generate_cedar_policies: true,
webhooks: crate::webhook::WebhookConfig::default(),
hooks: crate::hooks::HooksConfig::default(),
storage: crate::storage::StorageConfig::default(),
email: crate::email::EmailConfig::default(),
authz: AuthzConfig::default(),
signing: SigningConfig::default(),
client: ClientConfig::default(),
Expand All @@ -166,10 +190,27 @@ mod tests {
let json = serde_json::to_string(&config).unwrap();
let back: SchemaForgeConfig = serde_json::from_str(&json).unwrap();
assert_eq!(back.schema_forge.route_prefix, "/api/forge");
assert_eq!(back.schema_forge.project_name, "Bob's Dog Scheduling");
assert!(back.schema_forge.auto_generate_cedar_policies);
assert!(back.schema_forge.authz.principal_claims.is_empty());
}

#[test]
fn project_name_defaults_to_schemaforge() {
let config: SchemaForgeConfig = serde_json::from_str("{}").unwrap();
assert_eq!(config.schema_forge.project_name, "SchemaForge");
}

#[test]
fn project_name_deserialises_from_toml() {
let toml = r#"
[schema_forge]
project_name = "Bob's Dog Scheduling"
"#;
let config: SchemaForgeConfig = toml::from_str(toml).unwrap();
assert_eq!(config.schema_forge.project_name, "Bob's Dog Scheduling");
}

#[test]
fn principal_claims_section_deserialises() {
let toml = r#"
Expand Down
Loading
Loading