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
1 change: 1 addition & 0 deletions graphql/node-type-registry/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './data';
export * from './relation';
export * from './view';
export * from './blueprint-types.generated';
export * from './module-presets';

import type { NodeTypeDefinition } from './types';
import * as authz from './authz';
Expand Down
58 changes: 58 additions & 0 deletions graphql/node-type-registry/src/module-presets/auth-email-magic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ModulePreset } from './types';

/**
* `auth:email+magic` — `auth:email` plus passwordless email flows.
*
* Adds `session_secrets_module`, which is where one-time nonces for magic
* links and email OTPs are stored. Once installed, the `user_auth_module`
* emits `sign_up_magic_link`, `sign_in_magic_link`, and `sign_in_email_otp`
* procedures (gated on the equivalent `allow_*` toggles in
* `app_settings_auth`).
*
* Choose this over `auth:email` when you want users to be able to log in
* without ever setting a password — but still only over email (no SMS, no
* SSO).
*/
export const PresetAuthEmailMagic: ModulePreset = {
name: 'auth:email+magic',
display_name: 'Email + Magic Link / OTP',
summary: 'Everything in `auth:email` plus magic-link and email-OTP passwordless flows.',
description:
'Same password-based auth as `auth:email`, with `session_secrets_module` added so the ' +
'generator emits the passwordless procedures: `sign_up_magic_link`, `sign_in_magic_link`, ' +
'`sign_in_email_otp`. Password flows still exist — you opt into passwordless-only by ' +
'flipping the `allow_password_sign_*` toggles off in `app_settings_auth` after install. ' +
"This is the right step up from `auth:email` when you want to ship magic links without yet " +
"taking on SSO or passkeys.",
good_for: [
'Consumer apps that want passwordless from day one',
'Apps targeting users who forget passwords (newsletters, one-off tools)',
'Hardening path from `auth:email` without jumping all the way to `auth:hardened`'
],
not_for: [
'Apps that need SSO or passkeys — use `auth:sso` or `auth:passkey`',
'Production at scale — use `auth:hardened` for rate limiting'
],
modules: [
'users_module',
'membership_types_module',
'memberships_module:app',
'sessions_module',
'secrets_module',
'encrypted_secrets_module',
'emails_module',
'rls_module',
'user_auth_module',
'session_secrets_module'
],
includes_notes: {
session_secrets_module: 'Stores nonces for magic-link and email-OTP flows. Without it those procedures are not emitted.'
},
omits_notes: {
rate_limits_module: 'Same reasoning as `auth:email` — add later via `auth:hardened`.',
connected_accounts_module: 'No OAuth / SSO in this preset.',
webauthn_credentials_module: 'No passkeys — add `auth:passkey`.',
phone_numbers_module: 'No SMS — add `auth:hardened`.'
},
extends: ['auth:email']
};
72 changes: 72 additions & 0 deletions graphql/node-type-registry/src/module-presets/auth-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { ModulePreset } from './types';

/**
* `auth:email` — email + password sign_up/sign_in. No orgs, no SSO, no SMS,
* no passkeys, no rate limits.
*
* This is the "working consumer login in one step" preset. It installs the
* `user_auth_module` and all the tables its insert trigger hard-requires,
* giving you the standard procedures: `sign_up`, `sign_in`, `sign_out`,
* `set_password`, `reset_password`, `forgot_password`, `verify_email`,
* `delete_account`, `my_sessions`, API-key CRUD. Nothing more.
*
* It deliberately excludes rate limits, connected accounts / identity
* providers (OAuth), WebAuthn (passkeys), phone numbers (SMS), invites,
* permissions, and org-scoped memberships. Bolt those on by moving to a
* richer preset (`auth:hardened`, `b2b`) when you actually need them.
*/
export const PresetAuthEmail: ModulePreset = {
name: 'auth:email',
display_name: 'Email + Password',
summary: 'Standard email/password auth flow. No orgs, no SSO, no MFA, no rate limits.',
description:
"Installs `user_auth_module` with exactly the table dependencies its insert trigger " +
"hard-requires: users, app-scoped memberships, emails, secrets, encrypted secrets, " +
"sessions, plus RLS. You get the standard password-based auth procedures (sign_up, " +
"sign_in, reset_password, verify_email, delete_account, ...) and that's it. " +
"Everything else in the module catalog — SSO, passkeys, SMS, rate limits, orgs, " +
"invites, permissions — is deliberately omitted. This is the right shape for single-tenant " +
"consumer apps in the first weeks, internal tools that need a real login, or anything " +
"where you want the lightest possible working auth and will add complexity only when " +
"forced to.",
good_for: [
'Single-tenant consumer apps in the first week of development',
'Internal tools where one simple login is enough',
'Demos and hobby projects that need real password auth',
'B2C SaaS before org/team features are needed'
],
not_for: [
'Apps with org/team/workspace structure — use `b2b`',
'Apps that need SSO or passkeys from day one — use `auth:sso` or `auth:passkey`',
'Production apps at scale — use `auth:hardened` (adds rate limits, SSO, passkeys, SMS)'
],
modules: [
'users_module',
'membership_types_module',
'memberships_module:app',
'sessions_module',
'secrets_module',
'encrypted_secrets_module',
'emails_module',
'rls_module',
'user_auth_module'
],
includes_notes: {
'memberships_module:app': 'Required by `user_auth_module`: every user gets an app-level membership row at sign-up.',
membership_types_module: "Required by `memberships_module:app`; defines the 'app' scope.",
emails_module: 'Required by the `user_auth_module` insert trigger (`RAISE EXCEPTION REQUIRES emails_module`).',
encrypted_secrets_module: 'Required for password hashing; referenced by `set_password`, `verify_password`, and reset flows.',
secrets_module: 'API-key storage (`create_api_key`, `revoke_api_key`, `my_api_keys`).'
},
omits_notes: {
rate_limits_module: 'Omitted intentionally; throttle_* helpers are null-safe and the auth procs compile without it. Add later via `auth:hardened`.',
connected_accounts_module: 'No OAuth / SSO in this preset — add `auth:sso`.',
identity_providers_module: 'No OAuth provider configs without connected_accounts.',
webauthn_credentials_module: 'No passkeys — add `auth:passkey`.',
phone_numbers_module: 'No SMS login — add `auth:hardened` or the SMS-only refactor path.',
'memberships_module:org': 'No org/team structure — move to `b2b` when you need one.',
'permissions_module:app': 'No fine-grained RBAC; the `is_admin` flag on users is the only gate.',
invites_module: 'Self-serve signup only.',
session_secrets_module: 'No magic-link / email-OTP nonces; add `auth:email+magic`.'
}
};
68 changes: 68 additions & 0 deletions graphql/node-type-registry/src/module-presets/auth-hardened.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { ModulePreset } from './types';

/**
* `auth:hardened` — `auth:email` with rate limiting, SSO, passkeys, SMS,
* and magic-link / OTP infrastructure all installed. Production-ready
* consumer auth with the full identifier matrix.
*
* Still single-tenant (no orgs / teams / invites / permissions). For
* multi-tenant B2B, step up to `b2b`.
*/
export const PresetAuthHardened: ModulePreset = {
name: 'auth:hardened',
display_name: 'Hardened (all auth surfaces)',
summary: 'Rate limits + SSO + passkeys + SMS + magic links. Production-grade consumer auth.',
description:
'All of `auth:email`, plus every optional auth module that fits inside the single-tenant ' +
'model: `rate_limits_module` for throttling (protects sign-in, password reset, and ' +
'signup flows), `connected_accounts_module` + `identity_providers_module` for SSO, ' +
'`webauthn_credentials_module` + `webauthn_auth_module` for passkeys, ' +
'`session_secrets_module` for magic-link / email-OTP nonces, and ' +
'`phone_numbers_module` for SMS flows. Every login identifier is available; ' +
'toggle whichever ones you want off via `app_settings_auth.allow_*` columns. ' +
'Choose this for any production consumer app; step up to `b2b` once you need orgs.',
good_for: [
'Production consumer apps with a serious user base',
'Apps that need every identifier available (email, SSO, passkey, SMS) with throttling',
'Apps doing a progressive rollout of auth methods — everything is installed, you toggle per method'
],
not_for: [
'Hobby projects / demos — way too much infrastructure; use `auth:email`',
'Multi-tenant B2B apps — use `b2b`, which layers orgs + invites + permissions on top'
],
modules: [
'users_module',
'membership_types_module',
'memberships_module:app',
'sessions_module',
'secrets_module',
'encrypted_secrets_module',
'emails_module',
'rls_module',
'user_auth_module',
'session_secrets_module',
'rate_limits_module',
'connected_accounts_module',
'identity_providers_module',
'webauthn_credentials_module',
'webauthn_auth_module',
'phone_numbers_module'
],
includes_notes: {
rate_limits_module: 'Throttling for sign-in, password reset, sign-up, and IP-based gates.',
connected_accounts_module: 'OAuth / SSO linkage.',
identity_providers_module: 'OAuth provider configs (required for `connected_accounts_module`).',
webauthn_credentials_module: 'Per-user passkey storage.',
webauthn_auth_module: 'Passkey challenge + assertion runtime.',
session_secrets_module: 'Nonces for magic links, email OTP, and WebAuthn challenges.',
phone_numbers_module: 'SMS sign-in / MFA support.'
},
omits_notes: {
'memberships_module:org': 'No orgs / teams — use `b2b` when you need multi-tenancy.',
'permissions_module:app': 'No RBAC beyond the `is_admin` flag — add via `b2b`.',
invites_module: 'No invite flow — add via `b2b`.',
storage_module: 'Add separately if you need file uploads.',
crypto_addresses_module: 'Not a web3 preset; omit unless doing wallet sign-in.'
},
extends: ['auth:email', 'auth:email+magic', 'auth:sso', 'auth:passkey']
};
59 changes: 59 additions & 0 deletions graphql/node-type-registry/src/module-presets/auth-passkey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { ModulePreset } from './types';

/**
* `auth:passkey` — `auth:email` plus WebAuthn / passkeys.
*
* Adds `webauthn_credentials_module` (stores each user's registered public
* keys and credential IDs), `webauthn_auth_module` (the auth-time challenge
* storage + flow), and `session_secrets_module` (where the one-time
* challenge nonces live). The generator then emits WebAuthn registration
* and assertion procedures.
*
* Password flows stay on by default as a recovery path; toggle them off in
* `app_settings_auth` if you want strictly-passkey.
*/
export const PresetAuthPasskey: ModulePreset = {
name: 'auth:passkey',
display_name: 'Passkeys (WebAuthn)',
summary: '`auth:email` plus WebAuthn passkey registration and assertion.',
description:
"Installs the three modules WebAuthn needs: `webauthn_credentials_module` for each user's " +
"registered public keys, `webauthn_auth_module` for the runtime challenge/assertion flow, " +
"and `session_secrets_module` for the one-time challenge nonces. With these installed, " +
"the generator emits WebAuthn registration/login procs. Keep password flows as a recovery " +
"path, or disable them in `app_settings_auth` for passkey-only deployments.",
good_for: [
'Apps where you want users to adopt phishing-resistant auth',
'Consumer apps with a tech-forward audience',
'Internal tools protecting sensitive data where FIDO2 is a requirement'
],
not_for: [
'Apps that also need SSO or SMS — use `auth:hardened` for everything',
'Apps where the end-user device mix is heavy on old browsers that lack WebAuthn'
],
modules: [
'users_module',
'membership_types_module',
'memberships_module:app',
'sessions_module',
'secrets_module',
'encrypted_secrets_module',
'emails_module',
'rls_module',
'user_auth_module',
'session_secrets_module',
'webauthn_credentials_module',
'webauthn_auth_module'
],
includes_notes: {
webauthn_credentials_module: 'Per-user WebAuthn credential storage. Without it, passkey registration does not compile.',
webauthn_auth_module: 'Runtime challenge + assertion flow.',
session_secrets_module: 'Challenge nonces for registration and assertion.'
},
omits_notes: {
rate_limits_module: 'Add via `auth:hardened` for production.',
connected_accounts_module: 'No OAuth / SSO — add via `auth:hardened`.',
phone_numbers_module: 'No SMS.'
},
extends: ['auth:email']
};
68 changes: 68 additions & 0 deletions graphql/node-type-registry/src/module-presets/auth-sso.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { ModulePreset } from './types';

/**
* `auth:sso` — `auth:email` plus OAuth / OpenID Connect sign-in.
*
* Adds `connected_accounts_module` (the junction table mapping a user to
* `(provider, external_id)`) and `identity_providers_module` (the provider
* config: URLs, client_id, encrypted client_secret, scopes, PKCE/nonce
* knobs). The generator then emits `sign_in_identity` / `sign_up_identity`
* procedures which rely on `encrypted_secrets_module` to decrypt the client
* secret at auth time.
*
* Password fallback stays on by default (break-glass for admins); flip the
* `allow_password_sign_*` toggles off in `app_settings_auth` for strictly
* SSO-only.
*
* Note: `emails_module` is still required — the `user_auth_module` insert
* trigger hard-requires it today. A pure SSO-only install without emails
* is a separate refactor (see `docs/architecture/module-presets.md` in
* constructive-db).
*/
export const PresetAuthSso: ModulePreset = {
name: 'auth:sso',
display_name: 'OAuth / OpenID Connect',
summary: '`auth:email` plus OAuth providers and connected-account linkage.',
description:
"Adds the two modules that make SSO work: `identity_providers_module` (where provider " +
"definitions live — Google, GitHub, Okta, etc., with their URLs, client IDs, and " +
"encrypted client secrets) and `connected_accounts_module` (the junction mapping a " +
"Constructive user to a `(provider, external_id)` pair). The generator emits " +
"`sign_in_identity` and `sign_up_identity` procedures which decrypt the client secret " +
"through `encrypted_secrets_module` at auth time. Keep password flows as break-glass, or " +
"disable them via `app_settings_auth` toggles for strictly-SSO deployments.",
good_for: [
'B2B apps where end users sign in via their employer IdP',
'Consumer apps that want "Sign in with Google / GitHub"',
'Apps that need to federate identity with a specific provider ecosystem'
],
not_for: [
'Apps that also need passkeys and rate limits — use `auth:hardened`',
'Strictly-SSO apps that want NO email storage — needs the emails-optional refactor; not supported by a preset today'
],
modules: [
'users_module',
'membership_types_module',
'memberships_module:app',
'sessions_module',
'secrets_module',
'encrypted_secrets_module',
'emails_module',
'rls_module',
'user_auth_module',
'connected_accounts_module',
'identity_providers_module'
],
includes_notes: {
connected_accounts_module: 'Junction table for (user, provider, external_id). Without it, `sign_in_identity` does not compile.',
identity_providers_module: 'Provider config table (URLs, client_id, encrypted client_secret, scopes, PKCE knobs).',
encrypted_secrets_module: 'Required by `auth:email` already; also used by SSO to decrypt the provider client_secret at auth time.'
},
omits_notes: {
webauthn_credentials_module: 'No passkeys — add `auth:passkey` or move to `auth:hardened`.',
rate_limits_module: 'Omitted; add via `auth:hardened` for production.',
session_secrets_module: "Not required for authorization-code OAuth; add if you also want magic-link flows. PKCE doesn't require it for stateless OAuth flows today.",
phone_numbers_module: 'No SMS in this preset.'
},
extends: ['auth:email']
};
Loading
Loading