diff --git a/packages/node-type-registry/src/module-presets/auth-email-magic.ts b/packages/node-type-registry/src/module-presets/auth-email-magic.ts index 7887e6339..23abf3444 100644 --- a/packages/node-type-registry/src/module-presets/auth-email-magic.ts +++ b/packages/node-type-registry/src/module-presets/auth-email-magic.ts @@ -36,6 +36,9 @@ export const PresetAuthEmailMagic: ModulePreset = { modules: [ 'users_module', 'membership_types_module', + 'permissions_module:app', + 'limits_module:app', + 'levels_module:app', 'memberships_module:app', 'sessions_module', 'secrets_module', diff --git a/packages/node-type-registry/src/module-presets/auth-email.ts b/packages/node-type-registry/src/module-presets/auth-email.ts index b49dd1ef7..02dd2be09 100644 --- a/packages/node-type-registry/src/module-presets/auth-email.ts +++ b/packages/node-type-registry/src/module-presets/auth-email.ts @@ -10,25 +10,29 @@ import type { ModulePreset } from './types'; * `set_password`, `reset_password`, `forgot_password`, `verify_email`, * `delete_account`, `my_sessions`, API-key CRUD. Nothing more. * + * Includes `permissions_module:app`, `limits_module:app`, and + * `levels_module:app` because `memberships_module:app` has NOT NULL + * foreign keys to the tables they create (grants, caps, levels). + * * 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. + * 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.', + summary: 'Standard email/password auth flow with app-level permissions. No orgs, no SSO, no MFA.', 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.', + 'hard-requires: users, app-scoped memberships (plus their permissions/limits/levels ' + + 'dependencies), 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 — 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', @@ -43,6 +47,9 @@ export const PresetAuthEmail: ModulePreset = { modules: [ 'users_module', 'membership_types_module', + 'permissions_module:app', + 'limits_module:app', + 'levels_module:app', 'memberships_module:app', 'sessions_module', 'secrets_module', @@ -54,6 +61,9 @@ export const PresetAuthEmail: ModulePreset = { 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.", + 'permissions_module:app': 'Required by `memberships_module:app`: NOT NULL FK to grants table.', + 'limits_module:app': 'Required by `memberships_module:app`: NOT NULL FK to caps table.', + 'levels_module:app': 'Required by `memberships_module:app`: NOT NULL FK to levels table.', 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`).' @@ -65,7 +75,6 @@ export const PresetAuthEmail: ModulePreset = { 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`.' } diff --git a/packages/node-type-registry/src/module-presets/auth-hardened.ts b/packages/node-type-registry/src/module-presets/auth-hardened.ts index c1a67c8a5..ffbfeb32c 100644 --- a/packages/node-type-registry/src/module-presets/auth-hardened.ts +++ b/packages/node-type-registry/src/module-presets/auth-hardened.ts @@ -33,6 +33,9 @@ export const PresetAuthHardened: ModulePreset = { modules: [ 'users_module', 'membership_types_module', + 'permissions_module:app', + 'limits_module:app', + 'levels_module:app', 'memberships_module:app', 'sessions_module', 'secrets_module', @@ -59,7 +62,6 @@ export const PresetAuthHardened: ModulePreset = { }, 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.' diff --git a/packages/node-type-registry/src/module-presets/auth-passkey.ts b/packages/node-type-registry/src/module-presets/auth-passkey.ts index 82fa5b392..7058eb799 100644 --- a/packages/node-type-registry/src/module-presets/auth-passkey.ts +++ b/packages/node-type-registry/src/module-presets/auth-passkey.ts @@ -34,6 +34,9 @@ export const PresetAuthPasskey: ModulePreset = { modules: [ 'users_module', 'membership_types_module', + 'permissions_module:app', + 'limits_module:app', + 'levels_module:app', 'memberships_module:app', 'sessions_module', 'secrets_module', diff --git a/packages/node-type-registry/src/module-presets/auth-sso.ts b/packages/node-type-registry/src/module-presets/auth-sso.ts index 361601d06..050d5767b 100644 --- a/packages/node-type-registry/src/module-presets/auth-sso.ts +++ b/packages/node-type-registry/src/module-presets/auth-sso.ts @@ -43,6 +43,9 @@ export const PresetAuthSso: ModulePreset = { modules: [ 'users_module', 'membership_types_module', + 'permissions_module:app', + 'limits_module:app', + 'levels_module:app', 'memberships_module:app', 'sessions_module', 'secrets_module', diff --git a/packages/node-type-registry/src/module-presets/b2b-storage.ts b/packages/node-type-registry/src/module-presets/b2b-storage.ts new file mode 100644 index 000000000..6a463e17f --- /dev/null +++ b/packages/node-type-registry/src/module-presets/b2b-storage.ts @@ -0,0 +1,73 @@ +import type { ModulePreset } from './types'; + +/** + * `b2b:storage` — everything in `b2b` plus `storage_module` for file uploads. + * + * This is the common shape for B2B SaaS apps that need file upload + * infrastructure tied to their org/workspace structure. The storage module + * creates `app_buckets` and `app_files` tables with RLS policies, and + * entity-type-level storage scopes can be provisioned on top. + * + * If you don't need orgs, use a lighter preset and add `storage_module` + * separately via provisioning options. + */ +export const PresetB2bStorage: ModulePreset = { + name: 'b2b:storage', + display_name: 'B2B SaaS + File Storage', + summary: '`b2b` + file upload infrastructure (buckets, files, RLS).', + description: + 'Everything in `b2b` (auth:hardened + orgs + invites + permissions + levels + profiles + ' + + 'hierarchy), plus `storage_module` for file uploads. The storage module creates ' + + '`app_buckets` and `app_files` tables with full RLS: AuthzPublishable for public reads, ' + + 'AuthzAppMembership for member access, AuthzDirectOwner for uploader-only modify/delete. ' + + 'Entity-type provisioning with `has_storage=true` adds per-scope storage tables ' + + 'automatically. Choose this when your B2B app needs file uploads, avatars, attachments, ' + + 'or any object storage tied to workspaces.', + good_for: [ + 'B2B SaaS with file uploads (documents, avatars, attachments)', + 'Apps where storage is scoped to orgs/workspaces', + 'Apps that need per-entity-type file storage (e.g., project files, team assets)' + ], + not_for: [ + 'Single-tenant consumer apps — use `auth:email` or `auth:hardened` and add storage separately', + 'Apps without file upload needs — use `b2b` to avoid the storage table overhead' + ], + modules: [ + 'users_module', + 'membership_types_module', + 'permissions_module:app', + 'permissions_module:org', + 'limits_module:app', + 'limits_module:org', + 'levels_module:app', + 'levels_module:org', + 'memberships_module:app', + 'memberships_module:org', + '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', + 'profiles_module:app', + 'profiles_module:org', + 'hierarchy_module:org', + 'invites_module:app', + 'invites_module:org', + 'storage_module' + ], + includes_notes: { + storage_module: 'File upload infrastructure: app_buckets + app_files tables with RLS. Entity-type storage scopes layered on top via `has_storage=true`.' + }, + omits_notes: { + crypto_addresses_module: 'Not a web3 preset.' + }, + extends: ['b2b'] +}; diff --git a/packages/node-type-registry/src/module-presets/index.ts b/packages/node-type-registry/src/module-presets/index.ts index 1265cc9c5..962ca6c62 100644 --- a/packages/node-type-registry/src/module-presets/index.ts +++ b/packages/node-type-registry/src/module-presets/index.ts @@ -6,6 +6,7 @@ import { PresetAuthHardened } from './auth-hardened'; import { PresetAuthPasskey } from './auth-passkey'; import { PresetAuthSso } from './auth-sso'; import { PresetB2b } from './b2b'; +import { PresetB2bStorage } from './b2b-storage'; import { PresetFull } from './full'; import { PresetMinimal } from './minimal'; import type { ModulePreset } from './types'; @@ -17,6 +18,7 @@ export { PresetAuthPasskey, PresetAuthSso, PresetB2b, + PresetB2bStorage, PresetFull, PresetMinimal}; @@ -32,6 +34,7 @@ export const allModulePresets: ModulePreset[] = [ PresetAuthPasskey, PresetAuthHardened, PresetB2b, + PresetB2bStorage, PresetFull ];