feat(spaces): validate space metadata on Better Auth organization hooks

Moves the canonical SpaceType + SPACE_MODULE_ALLOWLIST to @mana/shared-types
(framework-free) so the Bun services can consume them without pulling in
Svelte. shared-branding keeps only the UI-facing labels and descriptions
and re-exports the canonical types for frontend convenience.

Wires two Better Auth organization hooks in mana-auth:
- beforeCreateOrganization asserts metadata.type is a valid SpaceType,
  rejecting the create with a BAD_REQUEST otherwise.
- beforeDeleteOrganization rejects deletion of the personal space.

Covered by bun tests (11 assertions) for the helper module.

No migration and no schema change — type lives in the existing
organization.metadata jsonb column.

Plan: docs/plans/spaces-foundation.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-20 16:05:38 +02:00
parent 9d69e4419d
commit 166d6c6ffb
11 changed files with 683 additions and 331 deletions

View file

@ -38,6 +38,7 @@ import {
} from '../email/send';
import { sourceAppStore, passwordResetRedirectStore } from './stores';
import { TRUSTED_ORIGINS } from './sso-origins';
import { assertValidSpaceMetadataForCreate, assertSpaceIsDeletable } from '../spaces';
// Re-export so existing imports (`import { TRUSTED_ORIGINS } from './better-auth.config'`)
// keep working. New code should import from './sso-origins' directly.
@ -281,6 +282,21 @@ export function createBetterAuth(databaseUrl: string) {
);
},
/**
* Spaces enforce that every organization carries a valid
* `metadata.type` (the Space type), and block deletion of the
* user's personal space. See docs/plans/spaces-foundation.md
* and ../spaces/metadata.ts.
*/
organizationHooks: {
beforeCreateOrganization: async ({ organization }) => {
assertValidSpaceMetadataForCreate(organization.metadata);
},
beforeDeleteOrganization: async ({ organization }) => {
assertSpaceIsDeletable(organization.metadata);
},
},
// Custom roles and permissions
organizationRole: {
owner: {