Preset Framework
Provider presets allow Arcan's setup wizards to auto-fill configuration for common identity providers without requiring a binary upgrade when providers change their URLs, endpoints, or recommended settings.
Architecture
┌──────────────────────┐
│ registry.getarcan.dev│
│ /presets/sso.json │
└──────────┬───────────┘
│ HTTP GET (5s timeout)
▼
┌───────────────┐ ┌──────────────────────┐ ┌───────────────────┐
│ Embedded JSON │◄───│ presets.Load() │───►│ ~/.arcan/presets/ │
│ (in binary) │ │ │ │ sso.json (cache) │
│ Fallback #3 │ │ 1. Check cache (24h) │ │ Priority #1 │
└───────────────┘ │ 2. Fetch registry │ └───────────────────┘
│ 3. Embedded fallback │
└──────────────────────┘
Resolution order:
- Local cache (
~/.arcan/presets/sso.json) -- if file exists and is less than 24 hours old - Remote registry (
registry.getarcan.dev/presets/sso.json) -- fetched with 5-second timeout, then cached - Embedded defaults (
internal/presets/defaults.json) -- compiled into the binary, always available
This design ensures the wizard works offline (embedded fallback), stays current when connected (registry fetch), and remains fast on repeat runs (local cache).
JSON Format
The preset file is a single JSON object with three arrays:
{
"version": "2026-04-02",
"oidc": [ ... ],
"saml": [ ... ],
"ldap": [ ... ]
}
OIDC Preset Schema
{
"name": "okta",
"display_name": "Okta",
"issuer": "",
"issuer_template": "https://{{domain}}.okta.com",
"prompts": {
"domain": "Okta domain (e.g., mycompany from mycompany.okta.com)"
},
"console_url": "https://{{domain}}-admin.okta.com/admin/apps",
"docs_url": "https://docs.getarcan.dev/guides/sso/okta",
"default_scopes": ["openid", "email", "profile"]
}
| Field | Required | Description |
|---|---|---|
name | Yes | Machine identifier (e.g., okta, google) |
display_name | Yes | Human-readable name shown in wizard menu |
issuer | One of | Fixed issuer URL (e.g., Google's https://accounts.google.com) |
issuer_template | One of | Template with {{placeholder}} values for dynamic issuers |
prompts | If template | Map of placeholder name to prompt label shown to user |
console_url | No | URL to the provider's admin console for credential creation |
docs_url | No | URL to Arcan's setup guide for this provider |
default_scopes | No | Default OAuth2 scopes (omit to use openid, email, profile) |
SAML Preset Schema
{
"name": "azure",
"display_name": "Azure AD (Entra ID)",
"metadata_template": "https://login.microsoftonline.com/{{tenant_id}}/federationmetadata/2007-06/federationmetadata.xml",
"prompts": {
"tenant_id": "Azure Tenant ID"
},
"console_url": "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps",
"docs_url": "https://docs.getarcan.dev/guides/sso/azure-saml"
}
| Field | Required | Description |
|---|---|---|
name | Yes | Machine identifier |
display_name | Yes | Human-readable name shown in wizard menu |
metadata_template | No | Template for metadata URL with {{placeholder}} values |
prompts | If template | Map of placeholder name to prompt label. Use metadata_url key for full URL prompts (no template). |
console_url | No | URL to the provider's admin console |
docs_url | No | URL to Arcan's setup guide |
config_hints | No | Map of hint keys to example values |
LDAP Preset Schema
{
"name": "active-directory",
"display_name": "Active Directory",
"default_port": 636,
"default_filter": "(&(objectClass=person)(sAMAccountName=%s))",
"default_attrs": {
"user_attr": "sAMAccountName",
"email_attr": "mail",
"name_attr": "displayName",
"group_attr": "memberOf"
},
"config_hints": {
"bind_dn": "cn=arcan,ou=Service Accounts,dc=example,dc=com",
"base_dn": "dc=example,dc=com"
},
"docs_url": "https://docs.getarcan.dev/guides/sso/active-directory"
}
| Field | Required | Description |
|---|---|---|
name | Yes | Machine identifier |
display_name | Yes | Human-readable name shown in wizard menu |
default_port | Yes | Default LDAP port (typically 636 for LDAPS) |
default_filter | Yes | Default user search filter (%s = username) |
default_attrs | Yes | Map with keys: user_attr, email_attr, name_attr, group_attr |
config_hints | No | Map with keys: bind_dn, base_dn (example values shown in prompts) |
docs_url | No | URL to Arcan's setup guide |
Template Expansion
Templates use {{placeholder}} syntax (double curly braces). The prompts map
defines which placeholders exist and what label to show the user.
Templates are expanded everywhere they appear -- issuer_template, metadata_template,
console_url, etc. This means a console URL like https://{{domain}}-admin.okta.com/admin/apps
gets the same domain value the user entered for the issuer template.
Adding a New Provider
- Edit
internal/presets/defaults.json - Add an entry to the appropriate array (
oidc,saml, orldap) - Bump the
versionfield (use date format:YYYY-MM-DD) - Run
go build ./cmd/arcan/to verify the JSON embeds correctly - Submit a PR
The new provider will appear in the wizard menu automatically. No code changes to the wizard functions are needed.
Cache Behavior
| Scenario | Behavior |
|---|---|
| First run, online | Fetches from registry, caches locally, uses result |
| First run, offline | Uses embedded defaults (no cache written) |
| Repeat run, cache fresh (under 24h) | Uses local cache (no network call) |
| Repeat run, cache stale (over 24h) | Fetches from registry, updates cache |
| Repeat run, cache stale, offline | Uses embedded defaults |
arcan auth update-presets | Forces fetch from registry |
The cache file is stored at ~/.arcan/presets/sso.json with 0644 permissions.
The presets directory is created with 0700 permissions.
Registry Endpoint
The registry serves presets at:
GET https://registry.getarcan.dev/presets/sso.json
The response must be a valid JSON object matching the preset schema. The version
field is used for logging and display purposes only -- cache expiry is based on
file modification time, not version comparison.
The fetch timeout is 5 seconds. If the registry is unreachable or returns a non-200
status, the loader silently falls back to the next tier without error messages
(debug logging only via slog.Debug).