Skip to content

Commit a81e845

Browse files
committed
feat(node-type-registry): add module presets
Ships eight curated Constructive module presets as TypeScript metadata in the node-type-registry package: minimal, auth:email, auth:email+magic, auth:sso, auth:passkey, auth:hardened, b2b, full. Each preset is a ModulePreset record with a display_name, a summary, a narrative description, good_for / not_for scenarios, a flat module list, and per-module includes_notes / omits_notes explaining the choices. This is metadata only — passing preset.modules to provision_database_modules is what actually installs them. Exposed via allModulePresets and getModulePreset(name).
1 parent d4360ca commit a81e845

11 files changed

Lines changed: 616 additions & 0 deletions

File tree

graphql/node-type-registry/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from './data';
44
export * from './relation';
55
export * from './view';
66
export * from './blueprint-types.generated';
7+
export * from './module-presets';
78

89
import type { NodeTypeDefinition } from './types';
910
import * as authz from './authz';
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { ModulePreset } from './types';
2+
3+
/**
4+
* `auth:email+magic` — `auth:email` plus passwordless email flows.
5+
*
6+
* Adds `session_secrets_module`, which is where one-time nonces for magic
7+
* links and email OTPs are stored. Once installed, the `user_auth_module`
8+
* emits `sign_up_magic_link`, `sign_in_magic_link`, and `sign_in_email_otp`
9+
* procedures (gated on the equivalent `allow_*` toggles in
10+
* `app_settings_auth`).
11+
*
12+
* Choose this over `auth:email` when you want users to be able to log in
13+
* without ever setting a password — but still only over email (no SMS, no
14+
* SSO).
15+
*/
16+
export const PresetAuthEmailMagic: ModulePreset = {
17+
name: 'auth:email+magic',
18+
display_name: 'Email + Magic Link / OTP',
19+
summary: 'Everything in `auth:email` plus magic-link and email-OTP passwordless flows.',
20+
description:
21+
'Same password-based auth as `auth:email`, with `session_secrets_module` added so the ' +
22+
'generator emits the passwordless procedures: `sign_up_magic_link`, `sign_in_magic_link`, ' +
23+
'`sign_in_email_otp`. Password flows still exist — you opt into passwordless-only by ' +
24+
'flipping the `allow_password_sign_*` toggles off in `app_settings_auth` after install. ' +
25+
"This is the right step up from `auth:email` when you want to ship magic links without yet " +
26+
"taking on SSO or passkeys.",
27+
good_for: [
28+
'Consumer apps that want passwordless from day one',
29+
'Apps targeting users who forget passwords (newsletters, one-off tools)',
30+
'Hardening path from `auth:email` without jumping all the way to `auth:hardened`'
31+
],
32+
not_for: [
33+
'Apps that need SSO or passkeys — use `auth:sso` or `auth:passkey`',
34+
'Production at scale — use `auth:hardened` for rate limiting'
35+
],
36+
modules: [
37+
'users_module',
38+
'membership_types_module',
39+
'memberships_module:app',
40+
'sessions_module',
41+
'secrets_module',
42+
'encrypted_secrets_module',
43+
'emails_module',
44+
'rls_module',
45+
'user_auth_module',
46+
'session_secrets_module'
47+
],
48+
includes_notes: {
49+
session_secrets_module: 'Stores nonces for magic-link and email-OTP flows. Without it those procedures are not emitted.'
50+
},
51+
omits_notes: {
52+
rate_limits_module: 'Same reasoning as `auth:email` — add later via `auth:hardened`.',
53+
connected_accounts_module: 'No OAuth / SSO in this preset.',
54+
webauthn_credentials_module: 'No passkeys — add `auth:passkey`.',
55+
phone_numbers_module: 'No SMS — add `auth:hardened`.'
56+
},
57+
extends: ['auth:email']
58+
};
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { ModulePreset } from './types';
2+
3+
/**
4+
* `auth:email` — email + password sign_up/sign_in. No orgs, no SSO, no SMS,
5+
* no passkeys, no rate limits.
6+
*
7+
* This is the "working consumer login in one step" preset. It installs the
8+
* `user_auth_module` and all the tables its insert trigger hard-requires,
9+
* giving you the standard procedures: `sign_up`, `sign_in`, `sign_out`,
10+
* `set_password`, `reset_password`, `forgot_password`, `verify_email`,
11+
* `delete_account`, `my_sessions`, API-key CRUD. Nothing more.
12+
*
13+
* It deliberately excludes rate limits, connected accounts / identity
14+
* providers (OAuth), WebAuthn (passkeys), phone numbers (SMS), invites,
15+
* permissions, and org-scoped memberships. Bolt those on by moving to a
16+
* richer preset (`auth:hardened`, `b2b`) when you actually need them.
17+
*/
18+
export const PresetAuthEmail: ModulePreset = {
19+
name: 'auth:email',
20+
display_name: 'Email + Password',
21+
summary: 'Standard email/password auth flow. No orgs, no SSO, no MFA, no rate limits.',
22+
description:
23+
"Installs `user_auth_module` with exactly the table dependencies its insert trigger " +
24+
"hard-requires: users, app-scoped memberships, emails, secrets, encrypted secrets, " +
25+
"sessions, plus RLS. You get the standard password-based auth procedures (sign_up, " +
26+
"sign_in, reset_password, verify_email, delete_account, ...) and that's it. " +
27+
"Everything else in the module catalog — SSO, passkeys, SMS, rate limits, orgs, " +
28+
"invites, permissions — is deliberately omitted. This is the right shape for single-tenant " +
29+
"consumer apps in the first weeks, internal tools that need a real login, or anything " +
30+
"where you want the lightest possible working auth and will add complexity only when " +
31+
"forced to.",
32+
good_for: [
33+
'Single-tenant consumer apps in the first week of development',
34+
'Internal tools where one simple login is enough',
35+
'Demos and hobby projects that need real password auth',
36+
'B2C SaaS before org/team features are needed'
37+
],
38+
not_for: [
39+
'Apps with org/team/workspace structure — use `b2b`',
40+
'Apps that need SSO or passkeys from day one — use `auth:sso` or `auth:passkey`',
41+
'Production apps at scale — use `auth:hardened` (adds rate limits, SSO, passkeys, SMS)'
42+
],
43+
modules: [
44+
'users_module',
45+
'membership_types_module',
46+
'memberships_module:app',
47+
'sessions_module',
48+
'secrets_module',
49+
'encrypted_secrets_module',
50+
'emails_module',
51+
'rls_module',
52+
'user_auth_module'
53+
],
54+
includes_notes: {
55+
'memberships_module:app': 'Required by `user_auth_module`: every user gets an app-level membership row at sign-up.',
56+
membership_types_module: "Required by `memberships_module:app`; defines the 'app' scope.",
57+
emails_module: 'Required by the `user_auth_module` insert trigger (`RAISE EXCEPTION REQUIRES emails_module`).',
58+
encrypted_secrets_module: 'Required for password hashing; referenced by `set_password`, `verify_password`, and reset flows.',
59+
secrets_module: 'API-key storage (`create_api_key`, `revoke_api_key`, `my_api_keys`).'
60+
},
61+
omits_notes: {
62+
rate_limits_module: 'Omitted intentionally; throttle_* helpers are null-safe and the auth procs compile without it. Add later via `auth:hardened`.',
63+
connected_accounts_module: 'No OAuth / SSO in this preset — add `auth:sso`.',
64+
identity_providers_module: 'No OAuth provider configs without connected_accounts.',
65+
webauthn_credentials_module: 'No passkeys — add `auth:passkey`.',
66+
phone_numbers_module: 'No SMS login — add `auth:hardened` or the SMS-only refactor path.',
67+
'memberships_module:org': 'No org/team structure — move to `b2b` when you need one.',
68+
'permissions_module:app': 'No fine-grained RBAC; the `is_admin` flag on users is the only gate.',
69+
invites_module: 'Self-serve signup only.',
70+
session_secrets_module: 'No magic-link / email-OTP nonces; add `auth:email+magic`.'
71+
}
72+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { ModulePreset } from './types';
2+
3+
/**
4+
* `auth:hardened` — `auth:email` with rate limiting, SSO, passkeys, SMS,
5+
* and magic-link / OTP infrastructure all installed. Production-ready
6+
* consumer auth with the full identifier matrix.
7+
*
8+
* Still single-tenant (no orgs / teams / invites / permissions). For
9+
* multi-tenant B2B, step up to `b2b`.
10+
*/
11+
export const PresetAuthHardened: ModulePreset = {
12+
name: 'auth:hardened',
13+
display_name: 'Hardened (all auth surfaces)',
14+
summary: 'Rate limits + SSO + passkeys + SMS + magic links. Production-grade consumer auth.',
15+
description:
16+
'All of `auth:email`, plus every optional auth module that fits inside the single-tenant ' +
17+
'model: `rate_limits_module` for throttling (protects sign-in, password reset, and ' +
18+
'signup flows), `connected_accounts_module` + `identity_providers_module` for SSO, ' +
19+
'`webauthn_credentials_module` + `webauthn_auth_module` for passkeys, ' +
20+
'`session_secrets_module` for magic-link / email-OTP nonces, and ' +
21+
'`phone_numbers_module` for SMS flows. Every login identifier is available; ' +
22+
'toggle whichever ones you want off via `app_settings_auth.allow_*` columns. ' +
23+
'Choose this for any production consumer app; step up to `b2b` once you need orgs.',
24+
good_for: [
25+
'Production consumer apps with a serious user base',
26+
'Apps that need every identifier available (email, SSO, passkey, SMS) with throttling',
27+
'Apps doing a progressive rollout of auth methods — everything is installed, you toggle per method'
28+
],
29+
not_for: [
30+
'Hobby projects / demos — way too much infrastructure; use `auth:email`',
31+
'Multi-tenant B2B apps — use `b2b`, which layers orgs + invites + permissions on top'
32+
],
33+
modules: [
34+
'users_module',
35+
'membership_types_module',
36+
'memberships_module:app',
37+
'sessions_module',
38+
'secrets_module',
39+
'encrypted_secrets_module',
40+
'emails_module',
41+
'rls_module',
42+
'user_auth_module',
43+
'session_secrets_module',
44+
'rate_limits_module',
45+
'connected_accounts_module',
46+
'identity_providers_module',
47+
'webauthn_credentials_module',
48+
'webauthn_auth_module',
49+
'phone_numbers_module'
50+
],
51+
includes_notes: {
52+
rate_limits_module: 'Throttling for sign-in, password reset, sign-up, and IP-based gates.',
53+
connected_accounts_module: 'OAuth / SSO linkage.',
54+
identity_providers_module: 'OAuth provider configs (required for `connected_accounts_module`).',
55+
webauthn_credentials_module: 'Per-user passkey storage.',
56+
webauthn_auth_module: 'Passkey challenge + assertion runtime.',
57+
session_secrets_module: 'Nonces for magic links, email OTP, and WebAuthn challenges.',
58+
phone_numbers_module: 'SMS sign-in / MFA support.'
59+
},
60+
omits_notes: {
61+
'memberships_module:org': 'No orgs / teams — use `b2b` when you need multi-tenancy.',
62+
'permissions_module:app': 'No RBAC beyond the `is_admin` flag — add via `b2b`.',
63+
invites_module: 'No invite flow — add via `b2b`.',
64+
storage_module: 'Add separately if you need file uploads.',
65+
crypto_addresses_module: 'Not a web3 preset; omit unless doing wallet sign-in.'
66+
},
67+
extends: ['auth:email', 'auth:email+magic', 'auth:sso', 'auth:passkey']
68+
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { ModulePreset } from './types';
2+
3+
/**
4+
* `auth:passkey` — `auth:email` plus WebAuthn / passkeys.
5+
*
6+
* Adds `webauthn_credentials_module` (stores each user's registered public
7+
* keys and credential IDs), `webauthn_auth_module` (the auth-time challenge
8+
* storage + flow), and `session_secrets_module` (where the one-time
9+
* challenge nonces live). The generator then emits WebAuthn registration
10+
* and assertion procedures.
11+
*
12+
* Password flows stay on by default as a recovery path; toggle them off in
13+
* `app_settings_auth` if you want strictly-passkey.
14+
*/
15+
export const PresetAuthPasskey: ModulePreset = {
16+
name: 'auth:passkey',
17+
display_name: 'Passkeys (WebAuthn)',
18+
summary: '`auth:email` plus WebAuthn passkey registration and assertion.',
19+
description:
20+
"Installs the three modules WebAuthn needs: `webauthn_credentials_module` for each user's " +
21+
"registered public keys, `webauthn_auth_module` for the runtime challenge/assertion flow, " +
22+
"and `session_secrets_module` for the one-time challenge nonces. With these installed, " +
23+
"the generator emits WebAuthn registration/login procs. Keep password flows as a recovery " +
24+
"path, or disable them in `app_settings_auth` for passkey-only deployments.",
25+
good_for: [
26+
'Apps where you want users to adopt phishing-resistant auth',
27+
'Consumer apps with a tech-forward audience',
28+
'Internal tools protecting sensitive data where FIDO2 is a requirement'
29+
],
30+
not_for: [
31+
'Apps that also need SSO or SMS — use `auth:hardened` for everything',
32+
'Apps where the end-user device mix is heavy on old browsers that lack WebAuthn'
33+
],
34+
modules: [
35+
'users_module',
36+
'membership_types_module',
37+
'memberships_module:app',
38+
'sessions_module',
39+
'secrets_module',
40+
'encrypted_secrets_module',
41+
'emails_module',
42+
'rls_module',
43+
'user_auth_module',
44+
'session_secrets_module',
45+
'webauthn_credentials_module',
46+
'webauthn_auth_module'
47+
],
48+
includes_notes: {
49+
webauthn_credentials_module: 'Per-user WebAuthn credential storage. Without it, passkey registration does not compile.',
50+
webauthn_auth_module: 'Runtime challenge + assertion flow.',
51+
session_secrets_module: 'Challenge nonces for registration and assertion.'
52+
},
53+
omits_notes: {
54+
rate_limits_module: 'Add via `auth:hardened` for production.',
55+
connected_accounts_module: 'No OAuth / SSO — add via `auth:hardened`.',
56+
phone_numbers_module: 'No SMS.'
57+
},
58+
extends: ['auth:email']
59+
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import type { ModulePreset } from './types';
2+
3+
/**
4+
* `auth:sso` — `auth:email` plus OAuth / OpenID Connect sign-in.
5+
*
6+
* Adds `connected_accounts_module` (the junction table mapping a user to
7+
* `(provider, external_id)`) and `identity_providers_module` (the provider
8+
* config: URLs, client_id, encrypted client_secret, scopes, PKCE/nonce
9+
* knobs). The generator then emits `sign_in_identity` / `sign_up_identity`
10+
* procedures which rely on `encrypted_secrets_module` to decrypt the client
11+
* secret at auth time.
12+
*
13+
* Password fallback stays on by default (break-glass for admins); flip the
14+
* `allow_password_sign_*` toggles off in `app_settings_auth` for strictly
15+
* SSO-only.
16+
*
17+
* Note: `emails_module` is still required — the `user_auth_module` insert
18+
* trigger hard-requires it today. A pure SSO-only install without emails
19+
* is a separate refactor (see `docs/architecture/module-presets.md` in
20+
* constructive-db).
21+
*/
22+
export const PresetAuthSso: ModulePreset = {
23+
name: 'auth:sso',
24+
display_name: 'OAuth / OpenID Connect',
25+
summary: '`auth:email` plus OAuth providers and connected-account linkage.',
26+
description:
27+
"Adds the two modules that make SSO work: `identity_providers_module` (where provider " +
28+
"definitions live — Google, GitHub, Okta, etc., with their URLs, client IDs, and " +
29+
"encrypted client secrets) and `connected_accounts_module` (the junction mapping a " +
30+
"Constructive user to a `(provider, external_id)` pair). The generator emits " +
31+
"`sign_in_identity` and `sign_up_identity` procedures which decrypt the client secret " +
32+
"through `encrypted_secrets_module` at auth time. Keep password flows as break-glass, or " +
33+
"disable them via `app_settings_auth` toggles for strictly-SSO deployments.",
34+
good_for: [
35+
'B2B apps where end users sign in via their employer IdP',
36+
'Consumer apps that want "Sign in with Google / GitHub"',
37+
'Apps that need to federate identity with a specific provider ecosystem'
38+
],
39+
not_for: [
40+
'Apps that also need passkeys and rate limits — use `auth:hardened`',
41+
'Strictly-SSO apps that want NO email storage — needs the emails-optional refactor; not supported by a preset today'
42+
],
43+
modules: [
44+
'users_module',
45+
'membership_types_module',
46+
'memberships_module:app',
47+
'sessions_module',
48+
'secrets_module',
49+
'encrypted_secrets_module',
50+
'emails_module',
51+
'rls_module',
52+
'user_auth_module',
53+
'connected_accounts_module',
54+
'identity_providers_module'
55+
],
56+
includes_notes: {
57+
connected_accounts_module: 'Junction table for (user, provider, external_id). Without it, `sign_in_identity` does not compile.',
58+
identity_providers_module: 'Provider config table (URLs, client_id, encrypted client_secret, scopes, PKCE knobs).',
59+
encrypted_secrets_module: 'Required by `auth:email` already; also used by SSO to decrypt the provider client_secret at auth time.'
60+
},
61+
omits_notes: {
62+
webauthn_credentials_module: 'No passkeys — add `auth:passkey` or move to `auth:hardened`.',
63+
rate_limits_module: 'Omitted; add via `auth:hardened` for production.',
64+
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.",
65+
phone_numbers_module: 'No SMS in this preset.'
66+
},
67+
extends: ['auth:email']
68+
};

0 commit comments

Comments
 (0)