@seifer-webapp-factory/account
v0.2.0
Published
account — derde capability-module: verticale, pluggable full-stack account-feature (profiel/instellingen, e-mail-/wachtwoordwijziging, data-export, consent, accountverwijdering) die de kits samenbindt via één contract. Componeert de privacy-kit (export/er
Downloads
96
Readme
@seifer-webapp-factory/account
The account capability module — a vertical, full-stack self-service feature: profile & settings,
email/password change, data export, consent, and account deletion. It composes the foundation kits behind
one front↔back contract, and it is the third capability module (after authentication and
authorization).
- Design doc / decisions:
../account.md - Tier model:
../README.md - Manifest spec:
@seifer-webapp-factory/capability-spec(this module'smanifest.tsis a facade over it)
What it is
| Part | Contents |
|---|---|
| contract/ | the seam: endpoints, DTOs (zod), error taxonomy, events, config schema — both halves generate types from it |
| backend/src | mechanism (pinned dependency): framework-free flow services over the privacy + auth kits |
| backend/templates | surface (materialized, project-owned): NestJS controller/module, migrations, pg adapters, config fragment, privacy data-provider |
| frontend/src | mechanism: the typed client + Vue composables (useProfile / useSecurity / usePrivacy) |
| frontend/templates | surface: profile.vue / security.vue / privacy.vue (seeded from the folded-in account-module-profile), i18n, routes |
| manifest.ts | provides / requires (+modules: ['authentication']) / config / migrations / contract / templateVersion |
| scaffolder/ | version-aware materializer + the module→module presence-check |
| tests/ | contract, backend unit, frontend component, scaffolder, and (infra-gated) backend + vertical e2e |
Requires
- Module dependency:
authentication(session/subject + the one-time-token flow for email change). The scaffolder refuses to assemble account if authentication is absent (checkRequiredModules). - Kits: backend
privacy,persistence,http-kernel,mailer,audit-log,config,i18n; frontendforms,http-client,data,notifications,i18n,app-kernel. - Requires-ports (host provides):
ProfileStore,SubjectProvider,Mailer,Database,DesignSystem.
Install (backend)
import { AccountModule } from '@seifer-webapp-factory/account/backend/templates/nestjs/account.module.js';
AccountModule.forRoot({
pool, // persistence-kit pool
userStore, tokenService, // from the authentication module
singleUseTokenService, hasher,
mailer,
domainProviders: [/* your DataProviders — so export/erasure covers YOUR tables too */],
config: { policy: { erasureMode: 'anonymize', exportDelivery: 'async_mailed_link' } },
});Run the migrations (account_profiles, account_consents, account_exports) via the persistence-kit
runner (accountMigrations).
Config (divergence level 1)
erasureMode (anonymize | hard_delete) · exportDelivery (async_mailed_link | sync_download) +
exportLinkTtlSeconds · requirePasswordReprompt · consentPurposes · postErasureRedirect. Safe
defaults resolve to a working, GDPR-aligned module (anonymize + async export + password reprompt).
Divergence & eject
- Configure — the knobs above.
- Extend — register a domain
DataProvider(so export/erasure cover app tables); add a profile field; add a consent purpose. - Materialize & edit — eject any
*/templatesfile (e.g.privacy.vue) and rewrite it; the mechanism keeps upgrading via semver, template upgrades become a three-way merge. - Fork — replace the
ProfileStoreor the export/erasure orchestration (last resort).
The contract + the shipped e2e are the guardrail across levels 1–3.
Tests
npm test # contract + manifest + backend unit + frontend + scaffolder (42 tests, no Docker)
npm run test:e2e # backend testcontainer e2e — REQUIRES Docker
npx playwright test # vertical e2e (authentication + account) — REQUIRES a running sample app + browserSecurity & compliance invariants
Verified destructive actions (email re-verify to the new address; password reprompt on delete); erasure
completeness (an incomplete privacy-provider report → erasure_blocked, never silently "done"); export
authenticity (short-lived authenticated link); no email enumeration; session hygiene (password change +
deletion revoke sessions); audit on every lifecycle event; provable consent.
Production defaults & scaling
AccountModule.forRoot defaults the audit store to inMemoryAuditStore() and the consent registry
to inMemoryConsentStore() — both in-memory / per-process. Fine for a single node, but in a multi-node
deployment each process keeps its own copy, so the audit trail fragments across nodes and consent state is
not shared (and both are lost on restart). Inject durable, shared stores (a persistent audit-log kit
adapter via auditLog, and a persistent consent store via consentStore) before running more than one node.
Accessibility
The frontend/templates pages (profile.vue / security.vue / privacy.vue) ship unstyled (0 CSS —
styling is delegated to the host DesignSystem). The host must meet the WCAG 2.2 AA obligations in
../ACCESSIBILITY.md (contrast, visible focus, reduced motion, <main> landmark).
Known debt
The scaffolder/core (materialize / presence / ports / errors) is copied-with-renames per module
(materializeAccountModule ↔ materializeAuthzModule ↔ materializeAuthModule; Account*/Authz*/
Auth* error classes), and the three-way-merge / config / extend layers that authentication /
authorization also ship follow the same pattern. Deduplicating this into a shared generic package is a
separate breaking refactor (the symbols are public exports) — tracked as capability-module tooling
Layer 1b. This module flags it rather than silently reproducing the whole core.
