@seifer-webapp-factory/authentication 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/backend/templates/config/config-fragment.ts +73 -0
- package/backend/templates/mail/templates.ts +84 -0
- package/backend/templates/nestjs/auth.controller.ts +274 -0
- package/backend/templates/nestjs/auth.module.ts +207 -0
- package/backend/templates/nestjs/tokens.ts +24 -0
- package/backend/templates/persistence/migrations/0001_auth.sql +36 -0
- package/backend/templates/persistence/migrations/index.ts +75 -0
- package/backend/templates/persistence/pg-single-use-store.ts +64 -0
- package/backend/templates/persistence/pg-token-store.ts +75 -0
- package/backend/templates/persistence/pg-user-store.ts +53 -0
- package/backend/templates/security/cookies.ts +89 -0
- package/backend/templates/security/csrf.ts +44 -0
- package/backend/templates/security/headers.ts +30 -0
- package/backend/templates/security/redaction.ts +38 -0
- package/dist/backend/src/errors.d.ts +12 -0
- package/dist/backend/src/errors.d.ts.map +1 -0
- package/dist/backend/src/errors.js +55 -0
- package/dist/backend/src/errors.js.map +1 -0
- package/dist/backend/src/index.d.ts +9 -0
- package/dist/backend/src/index.d.ts.map +1 -0
- package/dist/backend/src/index.js +8 -0
- package/dist/backend/src/index.js.map +1 -0
- package/dist/backend/src/ports.d.ts +60 -0
- package/dist/backend/src/ports.d.ts.map +1 -0
- package/dist/backend/src/ports.js +2 -0
- package/dist/backend/src/ports.js.map +1 -0
- package/dist/backend/src/services.d.ts +49 -0
- package/dist/backend/src/services.d.ts.map +1 -0
- package/dist/backend/src/services.js +178 -0
- package/dist/backend/src/services.js.map +1 -0
- package/dist/contract/endpoints.d.ts +259 -0
- package/dist/contract/endpoints.d.ts.map +1 -0
- package/dist/contract/endpoints.js +42 -0
- package/dist/contract/endpoints.js.map +1 -0
- package/dist/contract/errors.d.ts +23 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/errors.js +31 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/events.d.ts +40 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/events.js +14 -0
- package/dist/contract/events.js.map +1 -0
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +9 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/schemas.d.ts +150 -0
- package/dist/contract/schemas.d.ts.map +1 -0
- package/dist/contract/schemas.js +43 -0
- package/dist/contract/schemas.js.map +1 -0
- package/dist/frontend/src/client.d.ts +38 -0
- package/dist/frontend/src/client.d.ts.map +1 -0
- package/dist/frontend/src/client.js +88 -0
- package/dist/frontend/src/client.js.map +1 -0
- package/dist/frontend/src/composables.d.ts +46 -0
- package/dist/frontend/src/composables.d.ts.map +1 -0
- package/dist/frontend/src/composables.js +111 -0
- package/dist/frontend/src/composables.js.map +1 -0
- package/dist/frontend/src/guards.d.ts +10 -0
- package/dist/frontend/src/guards.d.ts.map +1 -0
- package/dist/frontend/src/guards.js +9 -0
- package/dist/frontend/src/guards.js.map +1 -0
- package/dist/frontend/src/index.d.ts +12 -0
- package/dist/frontend/src/index.d.ts.map +1 -0
- package/dist/frontend/src/index.js +9 -0
- package/dist/frontend/src/index.js.map +1 -0
- package/dist/manifest.d.ts +80 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +126 -0
- package/dist/manifest.js.map +1 -0
- package/dist/scaffolder/core/config.d.ts +213 -0
- package/dist/scaffolder/core/config.d.ts.map +1 -0
- package/dist/scaffolder/core/config.js +132 -0
- package/dist/scaffolder/core/config.js.map +1 -0
- package/dist/scaffolder/core/errors.d.ts +37 -0
- package/dist/scaffolder/core/errors.d.ts.map +1 -0
- package/dist/scaffolder/core/errors.js +46 -0
- package/dist/scaffolder/core/errors.js.map +1 -0
- package/dist/scaffolder/core/extend.d.ts +115 -0
- package/dist/scaffolder/core/extend.d.ts.map +1 -0
- package/dist/scaffolder/core/extend.js +116 -0
- package/dist/scaffolder/core/extend.js.map +1 -0
- package/dist/scaffolder/core/materialize.d.ts +71 -0
- package/dist/scaffolder/core/materialize.d.ts.map +1 -0
- package/dist/scaffolder/core/materialize.js +47 -0
- package/dist/scaffolder/core/materialize.js.map +1 -0
- package/dist/scaffolder/core/ports.d.ts +39 -0
- package/dist/scaffolder/core/ports.d.ts.map +1 -0
- package/dist/scaffolder/core/ports.js +33 -0
- package/dist/scaffolder/core/ports.js.map +1 -0
- package/dist/scaffolder/core/three-way-merge.d.ts +113 -0
- package/dist/scaffolder/core/three-way-merge.d.ts.map +1 -0
- package/dist/scaffolder/core/three-way-merge.js +184 -0
- package/dist/scaffolder/core/three-way-merge.js.map +1 -0
- package/dist/scaffolder/index.d.ts +21 -0
- package/dist/scaffolder/index.d.ts.map +1 -0
- package/dist/scaffolder/index.js +20 -0
- package/dist/scaffolder/index.js.map +1 -0
- package/frontend/templates/components/AuthField.vue +68 -0
- package/frontend/templates/i18n/en.json +70 -0
- package/frontend/templates/i18n/nl.json +70 -0
- package/frontend/templates/middleware/auth.ts +25 -0
- package/frontend/templates/middleware/guest.ts +25 -0
- package/frontend/templates/pages/forgot-password.vue +89 -0
- package/frontend/templates/pages/login.vue +90 -0
- package/frontend/templates/pages/logout.vue +46 -0
- package/frontend/templates/pages/register.vue +100 -0
- package/frontend/templates/pages/reset-password.vue +105 -0
- package/frontend/templates/pages/verify-email.vue +76 -0
- package/frontend/templates/plugins/auth.client.ts +111 -0
- package/frontend/templates/runtime.ts +60 -0
- package/package.json +71 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- US-A0604 — authentication capability, migratie 0001_auth (raw SQL, DBA-review).
|
|
2
|
+
-- Idempotent (IF NOT EXISTS). De TypeScript-variant in index.ts is de bron die de MigrationRunner draait.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
5
|
+
id text PRIMARY KEY,
|
|
6
|
+
identifier text NOT NULL UNIQUE,
|
|
7
|
+
password_hash text,
|
|
8
|
+
verified boolean NOT NULL DEFAULT false,
|
|
9
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
10
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
-- Single-use tokens voor e-mailverificatie (US-A0202) en wachtwoord-reset (US-A0501/0502).
|
|
14
|
+
CREATE TABLE IF NOT EXISTS auth_one_time_tokens (
|
|
15
|
+
id text PRIMARY KEY, -- SHA-256-hash van het klare token
|
|
16
|
+
purpose text NOT NULL, -- 'email-verify' | 'password-reset'
|
|
17
|
+
payload jsonb, -- { userId }
|
|
18
|
+
expires_at bigint NOT NULL, -- ms sinds epoch
|
|
19
|
+
consumed_at bigint -- ms sinds epoch; NULL zolang onverbruikt
|
|
20
|
+
);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS auth_one_time_tokens_purpose_idx ON auth_one_time_tokens(purpose);
|
|
22
|
+
|
|
23
|
+
-- Roterende refresh-tokens (US-A0301/0401/0403); used_at markeert rotatie/hergebruik.
|
|
24
|
+
CREATE TABLE IF NOT EXISTS auth_refresh_tokens (
|
|
25
|
+
id text PRIMARY KEY, -- SHA-256-hash van het klare refresh-token
|
|
26
|
+
subject text NOT NULL, -- user id
|
|
27
|
+
expires_at bigint NOT NULL,
|
|
28
|
+
used_at bigint
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS auth_refresh_tokens_subject_idx ON auth_refresh_tokens(subject);
|
|
31
|
+
|
|
32
|
+
-- Access-token-denylist (revocatie op jti).
|
|
33
|
+
CREATE TABLE IF NOT EXISTS auth_denied_access (
|
|
34
|
+
jti text PRIMARY KEY,
|
|
35
|
+
expires_at bigint NOT NULL
|
|
36
|
+
);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-A0604 — Schema-migraties voor de authentication-capability, in het persistence-kit-{@link Migration}-
|
|
3
|
+
* formaat (stabiele id + idempotente `up`/`down`). De MigrationRunner past ze in volgorde toe en houdt
|
|
4
|
+
* een ledger bij. De raw SQL staat naast dit bestand in `0001_auth.sql` (documentatie/DBA-review).
|
|
5
|
+
*
|
|
6
|
+
* `0001_auth` legt vast:
|
|
7
|
+
* - `users` — identiteit + wachtwoord-hash + verificatiestatus (US-A0201/0203)
|
|
8
|
+
* - `auth_one_time_tokens` — single-use verify/reset-tokens (US-A0202/0501/0502)
|
|
9
|
+
* - `auth_refresh_tokens` — roterende refresh-tokens (US-A0301/0401/0403)
|
|
10
|
+
* - `auth_denied_access` — access-token-denylist (revocatie)
|
|
11
|
+
* Ids zijn `text` (UUID's en SHA-256-hashes); tijd-kolommen voor tokens zijn `bigint` (ms sinds epoch).
|
|
12
|
+
*/
|
|
13
|
+
import type { Migration } from '@seifer-webapp-factory/kits/backend/persistence';
|
|
14
|
+
|
|
15
|
+
const authInit: Migration = {
|
|
16
|
+
id: '0001_auth',
|
|
17
|
+
async up(ctx) {
|
|
18
|
+
await ctx.execute(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
20
|
+
id text PRIMARY KEY,
|
|
21
|
+
identifier text NOT NULL UNIQUE,
|
|
22
|
+
password_hash text,
|
|
23
|
+
verified boolean NOT NULL DEFAULT false,
|
|
24
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
await ctx.execute(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS auth_one_time_tokens (
|
|
30
|
+
id text PRIMARY KEY,
|
|
31
|
+
purpose text NOT NULL,
|
|
32
|
+
payload jsonb,
|
|
33
|
+
expires_at bigint NOT NULL,
|
|
34
|
+
consumed_at bigint
|
|
35
|
+
)
|
|
36
|
+
`);
|
|
37
|
+
await ctx.execute(
|
|
38
|
+
'CREATE INDEX IF NOT EXISTS auth_one_time_tokens_purpose_idx ON auth_one_time_tokens(purpose)',
|
|
39
|
+
);
|
|
40
|
+
await ctx.execute(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS auth_refresh_tokens (
|
|
42
|
+
id text PRIMARY KEY,
|
|
43
|
+
subject text NOT NULL,
|
|
44
|
+
expires_at bigint NOT NULL,
|
|
45
|
+
used_at bigint
|
|
46
|
+
)
|
|
47
|
+
`);
|
|
48
|
+
await ctx.execute(
|
|
49
|
+
'CREATE INDEX IF NOT EXISTS auth_refresh_tokens_subject_idx ON auth_refresh_tokens(subject)',
|
|
50
|
+
);
|
|
51
|
+
await ctx.execute(`
|
|
52
|
+
CREATE TABLE IF NOT EXISTS auth_denied_access (
|
|
53
|
+
jti text PRIMARY KEY,
|
|
54
|
+
expires_at bigint NOT NULL
|
|
55
|
+
)
|
|
56
|
+
`);
|
|
57
|
+
},
|
|
58
|
+
async down(ctx) {
|
|
59
|
+
await ctx.execute('DROP TABLE IF EXISTS auth_denied_access');
|
|
60
|
+
await ctx.execute('DROP TABLE IF EXISTS auth_refresh_tokens');
|
|
61
|
+
await ctx.execute('DROP TABLE IF EXISTS auth_one_time_tokens');
|
|
62
|
+
await ctx.execute('DROP TABLE IF EXISTS users');
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Alle auth-migraties in toepassingsvolgorde. */
|
|
67
|
+
export const authMigrations: Migration[] = [authInit];
|
|
68
|
+
|
|
69
|
+
/** Tabellen die een test-reset-helper mag leegmaken (exclusief de migratie-ledger). */
|
|
70
|
+
export const AUTH_MANAGED_TABLES = [
|
|
71
|
+
'users',
|
|
72
|
+
'auth_one_time_tokens',
|
|
73
|
+
'auth_refresh_tokens',
|
|
74
|
+
'auth_denied_access',
|
|
75
|
+
];
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-A0601 — Postgres-backed {@link SingleUseStore} voor e-mailverificatie- en wachtwoord-reset-tokens
|
|
3
|
+
* (tabel `auth_one_time_tokens`). `markConsumed` verbruikt atomair exact één keer (WHERE consumed_at IS
|
|
4
|
+
* NULL). Alleen de SHA-256-hash van het klare token wordt opgeslagen; het klare token is nooit
|
|
5
|
+
* persistent. `payload` is `jsonb` (reeds geparsed door `pg`).
|
|
6
|
+
*/
|
|
7
|
+
import type { Pool, Row } from '@seifer-webapp-factory/kits/backend/persistence';
|
|
8
|
+
import type { SingleUseStore } from '@seifer-webapp-factory/kits/backend/auth';
|
|
9
|
+
|
|
10
|
+
type OneTimeRow = Row & {
|
|
11
|
+
id: string;
|
|
12
|
+
purpose: string;
|
|
13
|
+
payload: unknown;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
consumedAt: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function pgSingleUseStore(pool: Pool): SingleUseStore {
|
|
19
|
+
return {
|
|
20
|
+
async save(record) {
|
|
21
|
+
await pool.execute(
|
|
22
|
+
`INSERT INTO auth_one_time_tokens (id, purpose, payload, expires_at, consumed_at)
|
|
23
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
24
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
25
|
+
purpose = EXCLUDED.purpose,
|
|
26
|
+
payload = EXCLUDED.payload,
|
|
27
|
+
expires_at = EXCLUDED.expires_at,
|
|
28
|
+
consumed_at = EXCLUDED.consumed_at`,
|
|
29
|
+
[
|
|
30
|
+
record.id,
|
|
31
|
+
record.purpose,
|
|
32
|
+
JSON.stringify(record.payload ?? null),
|
|
33
|
+
record.expiresAt,
|
|
34
|
+
record.consumedAt ?? null,
|
|
35
|
+
],
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
async find(id) {
|
|
39
|
+
const { rows } = await pool.execute<OneTimeRow>(
|
|
40
|
+
`SELECT id, purpose, payload, expires_at AS "expiresAt", consumed_at AS "consumedAt"
|
|
41
|
+
FROM auth_one_time_tokens WHERE id = $1`,
|
|
42
|
+
[id],
|
|
43
|
+
);
|
|
44
|
+
const r = rows[0];
|
|
45
|
+
if (!r) return null;
|
|
46
|
+
return {
|
|
47
|
+
id: r.id,
|
|
48
|
+
purpose: r.purpose,
|
|
49
|
+
payload: r.payload,
|
|
50
|
+
expiresAt: Number(r.expiresAt),
|
|
51
|
+
consumedAt: r.consumedAt == null ? undefined : Number(r.consumedAt),
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async markConsumed(id) {
|
|
55
|
+
const { rowCount } = await pool.execute(
|
|
56
|
+
`UPDATE auth_one_time_tokens
|
|
57
|
+
SET consumed_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
58
|
+
WHERE id = $1 AND consumed_at IS NULL`,
|
|
59
|
+
[id],
|
|
60
|
+
);
|
|
61
|
+
return rowCount === 1;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-A0601 — Postgres-backed {@link TokenStore}: refresh-token-rotatie (met hergebruik-detectie) +
|
|
3
|
+
* access-denylist. `markRefreshUsed` is atomair (alleen markeren als nog niet gebruikt) zodat
|
|
4
|
+
* hergebruik van een reeds-ingewisseld token betrouwbaar wordt gedetecteerd. `revokeAllForSubject`
|
|
5
|
+
* trekt in één statement ALLE nog-ongebruikte tokens van een subject in (US-A0502 reset → sessies weg).
|
|
6
|
+
* bigint-kolommen (ms sinds epoch) komen als string terug uit `pg`; we normaliseren naar `number`.
|
|
7
|
+
*/
|
|
8
|
+
import type { Pool, Row } from '@seifer-webapp-factory/kits/backend/persistence';
|
|
9
|
+
import type { TokenStore } from '@seifer-webapp-factory/kits/backend/auth';
|
|
10
|
+
|
|
11
|
+
type RefreshRow = Row & {
|
|
12
|
+
id: string;
|
|
13
|
+
subject: string;
|
|
14
|
+
expiresAt: string;
|
|
15
|
+
usedAt: string | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function pgTokenStore(pool: Pool): TokenStore {
|
|
19
|
+
return {
|
|
20
|
+
async saveRefresh(record) {
|
|
21
|
+
await pool.execute(
|
|
22
|
+
`INSERT INTO auth_refresh_tokens (id, subject, expires_at, used_at)
|
|
23
|
+
VALUES ($1, $2, $3, $4)
|
|
24
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
25
|
+
subject = EXCLUDED.subject,
|
|
26
|
+
expires_at = EXCLUDED.expires_at,
|
|
27
|
+
used_at = EXCLUDED.used_at`,
|
|
28
|
+
[record.id, record.subject, record.expiresAt, record.usedAt ?? null],
|
|
29
|
+
);
|
|
30
|
+
},
|
|
31
|
+
async findRefresh(id) {
|
|
32
|
+
const { rows } = await pool.execute<RefreshRow>(
|
|
33
|
+
`SELECT id, subject, expires_at AS "expiresAt", used_at AS "usedAt"
|
|
34
|
+
FROM auth_refresh_tokens WHERE id = $1`,
|
|
35
|
+
[id],
|
|
36
|
+
);
|
|
37
|
+
const r = rows[0];
|
|
38
|
+
if (!r) return null;
|
|
39
|
+
return {
|
|
40
|
+
id: r.id,
|
|
41
|
+
subject: r.subject,
|
|
42
|
+
expiresAt: Number(r.expiresAt),
|
|
43
|
+
usedAt: r.usedAt == null ? undefined : Number(r.usedAt),
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
async markRefreshUsed(id) {
|
|
47
|
+
const { rowCount } = await pool.execute(
|
|
48
|
+
`UPDATE auth_refresh_tokens
|
|
49
|
+
SET used_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
50
|
+
WHERE id = $1 AND used_at IS NULL`,
|
|
51
|
+
[id],
|
|
52
|
+
);
|
|
53
|
+
return rowCount === 1;
|
|
54
|
+
},
|
|
55
|
+
async revokeAllForSubject(subject) {
|
|
56
|
+
await pool.execute(
|
|
57
|
+
`UPDATE auth_refresh_tokens
|
|
58
|
+
SET used_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
|
|
59
|
+
WHERE subject = $1 AND used_at IS NULL`,
|
|
60
|
+
[subject],
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
async denyAccess(jti, expiresAt) {
|
|
64
|
+
await pool.execute(
|
|
65
|
+
`INSERT INTO auth_denied_access (jti, expires_at) VALUES ($1, $2)
|
|
66
|
+
ON CONFLICT (jti) DO NOTHING`,
|
|
67
|
+
[jti, expiresAt],
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
async isAccessDenied(jti) {
|
|
71
|
+
const { rowCount } = await pool.execute(`SELECT 1 FROM auth_denied_access WHERE jti = $1`, [jti]);
|
|
72
|
+
return rowCount > 0;
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* US-A0601 — Postgres-backed {@link UserStore} bovenop de persistence-kit-{@link Pool}. `save` is een
|
|
3
|
+
* upsert op de `id` (registratie én verify/reset-updates lopen hier doorheen). De kit interpreteert
|
|
4
|
+
* `identifier` niet; wij bewaren het e-mailadres als login-identifier. Geen credential-materiaal in
|
|
5
|
+
* queries dat verder reikt dan de hash-kolom.
|
|
6
|
+
*/
|
|
7
|
+
import type { Pool, Row } from '@seifer-webapp-factory/kits/backend/persistence';
|
|
8
|
+
import type { UserRecord, UserStore } from '@seifer-webapp-factory/kits/backend/auth';
|
|
9
|
+
|
|
10
|
+
type UserRow = Row & {
|
|
11
|
+
id: string;
|
|
12
|
+
identifier: string;
|
|
13
|
+
passwordHash: string | null;
|
|
14
|
+
verified: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const SELECT = 'SELECT id, identifier, password_hash AS "passwordHash", verified FROM users';
|
|
18
|
+
|
|
19
|
+
function toRecord(r: UserRow): UserRecord {
|
|
20
|
+
return {
|
|
21
|
+
id: r.id,
|
|
22
|
+
identifier: r.identifier,
|
|
23
|
+
passwordHash: r.passwordHash ?? undefined,
|
|
24
|
+
verified: r.verified,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function pgUserStore(pool: Pool): UserStore {
|
|
29
|
+
return {
|
|
30
|
+
async findByIdentifier(identifier) {
|
|
31
|
+
const { rows } = await pool.execute<UserRow>(`${SELECT} WHERE identifier = $1`, [identifier]);
|
|
32
|
+
return rows[0] ? toRecord(rows[0]) : null;
|
|
33
|
+
},
|
|
34
|
+
async findById(id) {
|
|
35
|
+
const { rows } = await pool.execute<UserRow>(`${SELECT} WHERE id = $1`, [id]);
|
|
36
|
+
return rows[0] ? toRecord(rows[0]) : null;
|
|
37
|
+
},
|
|
38
|
+
async save(user) {
|
|
39
|
+
const { rows } = await pool.execute<UserRow>(
|
|
40
|
+
`INSERT INTO users (id, identifier, password_hash, verified, updated_at)
|
|
41
|
+
VALUES ($1, $2, $3, $4, now())
|
|
42
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
43
|
+
identifier = EXCLUDED.identifier,
|
|
44
|
+
password_hash = EXCLUDED.password_hash,
|
|
45
|
+
verified = EXCLUDED.verified,
|
|
46
|
+
updated_at = now()
|
|
47
|
+
RETURNING id, identifier, password_hash AS "passwordHash", verified`,
|
|
48
|
+
[user.id, user.identifier, user.passwordHash ?? null, user.verified ?? false],
|
|
49
|
+
);
|
|
50
|
+
return toRecord(rows[0]!);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cookie-helpers voor de auth-surface (US-A0703). Het roterende refresh-token gaat NOOIT in de
|
|
3
|
+
* response-body maar in een `httpOnly`+`Secure`+`SameSite`-cookie; het CSRF-token gaat in een
|
|
4
|
+
* *leesbare* (niet-httpOnly) cookie zodat de client het via een header kan terugspiegelen
|
|
5
|
+
* (double-submit). Deze module bevat pure string-helpers — geen framework-afhankelijkheid.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Naam van de httpOnly refresh-token-cookie. */
|
|
9
|
+
export const REFRESH_COOKIE = 'auth_refresh_token';
|
|
10
|
+
/** Naam van de leesbare CSRF-cookie (double-submit). */
|
|
11
|
+
export const CSRF_COOKIE = 'auth_csrf';
|
|
12
|
+
/** Header waarin de client het CSRF-token terugspiegelt. */
|
|
13
|
+
export const CSRF_HEADER = 'x-csrf-token';
|
|
14
|
+
|
|
15
|
+
/** Instelbaar cookie-gedrag; `secure` staat productie-side altijd aan (achter TLS). */
|
|
16
|
+
export interface CookieOptions {
|
|
17
|
+
secure: boolean;
|
|
18
|
+
sameSite: 'Strict' | 'Lax' | 'None';
|
|
19
|
+
/** Pad waarop het refresh-token geldig is; standaard de auth-routes. */
|
|
20
|
+
refreshPath: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DEFAULT_COOKIE_OPTIONS: CookieOptions = {
|
|
24
|
+
secure: true,
|
|
25
|
+
sameSite: 'Strict',
|
|
26
|
+
refreshPath: '/auth',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Parse een rauwe `Cookie`-header naar een naam→waarde-map (leeg bij afwezigheid). */
|
|
30
|
+
export function parseCookies(header: string | undefined): Record<string, string> {
|
|
31
|
+
const out: Record<string, string> = {};
|
|
32
|
+
if (!header) return out;
|
|
33
|
+
for (const part of header.split(';')) {
|
|
34
|
+
const eq = part.indexOf('=');
|
|
35
|
+
if (eq < 0) continue;
|
|
36
|
+
const name = part.slice(0, eq).trim();
|
|
37
|
+
const value = part.slice(eq + 1).trim();
|
|
38
|
+
if (name) out[name] = decodeURIComponent(value);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Bouw de `Set-Cookie`-string voor het refresh-token (httpOnly + Secure + SameSite). */
|
|
44
|
+
export function refreshSetCookie(value: string, ttlSeconds: number, opts: CookieOptions): string {
|
|
45
|
+
return serializeCookie(REFRESH_COOKIE, value, {
|
|
46
|
+
httpOnly: true,
|
|
47
|
+
secure: opts.secure,
|
|
48
|
+
sameSite: opts.sameSite,
|
|
49
|
+
path: opts.refreshPath,
|
|
50
|
+
maxAge: ttlSeconds,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Bouw de `Set-Cookie`-string die het refresh-token wist (logout). */
|
|
55
|
+
export function clearRefreshSetCookie(opts: CookieOptions): string {
|
|
56
|
+
return serializeCookie(REFRESH_COOKIE, '', {
|
|
57
|
+
httpOnly: true,
|
|
58
|
+
secure: opts.secure,
|
|
59
|
+
sameSite: opts.sameSite,
|
|
60
|
+
path: opts.refreshPath,
|
|
61
|
+
maxAge: 0,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Bouw de `Set-Cookie`-string voor het (leesbare) CSRF-token. */
|
|
66
|
+
export function csrfSetCookie(value: string, opts: CookieOptions): string {
|
|
67
|
+
return serializeCookie(CSRF_COOKIE, value, {
|
|
68
|
+
httpOnly: false,
|
|
69
|
+
secure: opts.secure,
|
|
70
|
+
sameSite: opts.sameSite,
|
|
71
|
+
path: '/',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface SerializeOptions {
|
|
76
|
+
httpOnly: boolean;
|
|
77
|
+
secure: boolean;
|
|
78
|
+
sameSite: 'Strict' | 'Lax' | 'None';
|
|
79
|
+
path: string;
|
|
80
|
+
maxAge?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function serializeCookie(name: string, value: string, o: SerializeOptions): string {
|
|
84
|
+
const parts = [`${name}=${encodeURIComponent(value)}`, `Path=${o.path}`, `SameSite=${o.sameSite}`];
|
|
85
|
+
if (o.httpOnly) parts.push('HttpOnly');
|
|
86
|
+
if (o.secure) parts.push('Secure');
|
|
87
|
+
if (o.maxAge !== undefined) parts.push(`Max-Age=${o.maxAge}`);
|
|
88
|
+
return parts.join('; ');
|
|
89
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSRF-bescherming via double-submit (US-A0703). De server zet een leesbare CSRF-cookie; de client
|
|
3
|
+
* spiegelt die waarde terug in de `X-CSRF-Token`-header. Op state-changing routes moeten cookie én
|
|
4
|
+
* header aanwezig zijn én exact overeenkomen. De vergelijking is timing-safe (geen lek via response-tijd).
|
|
5
|
+
*/
|
|
6
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
7
|
+
import { CSRF_COOKIE, CSRF_HEADER, parseCookies } from './cookies.js';
|
|
8
|
+
|
|
9
|
+
/** Genereer een onraadbaar CSRF-token (url-safe). */
|
|
10
|
+
export function generateCsrfToken(): string {
|
|
11
|
+
return randomBytes(32).toString('base64url');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Minimale request-vorm die de CSRF-check nodig heeft. */
|
|
15
|
+
export interface CsrfRequestLike {
|
|
16
|
+
headers: Record<string, string | string[] | undefined>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Lees het CSRF-token uit de cookie (of `undefined`). */
|
|
20
|
+
export function csrfCookieValue(req: CsrfRequestLike): string | undefined {
|
|
21
|
+
const raw = req.headers['cookie'];
|
|
22
|
+
const header = Array.isArray(raw) ? raw.join('; ') : raw;
|
|
23
|
+
return parseCookies(header)[CSRF_COOKIE];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Lees het teruggespiegelde CSRF-token uit de header (of `undefined`). */
|
|
27
|
+
export function csrfHeaderValue(req: CsrfRequestLike): string | undefined {
|
|
28
|
+
const raw = req.headers[CSRF_HEADER];
|
|
29
|
+
return Array.isArray(raw) ? raw[0] : raw;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Valideer double-submit: cookie én header aanwezig en gelijk. Retourneert `false` bij ontbreken of
|
|
34
|
+
* mismatch — de aanroeper mapt dat naar `csrf_failed` (403).
|
|
35
|
+
*/
|
|
36
|
+
export function isCsrfValid(req: CsrfRequestLike): boolean {
|
|
37
|
+
const cookie = csrfCookieValue(req);
|
|
38
|
+
const header = csrfHeaderValue(req);
|
|
39
|
+
if (!cookie || !header) return false;
|
|
40
|
+
const a = Buffer.from(cookie);
|
|
41
|
+
const b = Buffer.from(header);
|
|
42
|
+
if (a.length !== b.length) return false;
|
|
43
|
+
return timingSafeEqual(a, b);
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security-headers (US-A0704). Een defensieve, framework-neutrale set die op elke auth-response wordt
|
|
3
|
+
* gezet. Doel: geen MIME-sniffing, geen clickjacking, geen referrer-lek, en caches die geen
|
|
4
|
+
* gevoelige auth-responses bewaren. De redactie van secrets/tokens uit foutbodies gebeurt in de
|
|
5
|
+
* controller-fout-mapping (zie ook `redaction.ts`).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Naam→waarde van de standaard security-headers. */
|
|
9
|
+
export const SECURITY_HEADERS: Readonly<Record<string, string>> = Object.freeze({
|
|
10
|
+
'X-Content-Type-Options': 'nosniff',
|
|
11
|
+
'X-Frame-Options': 'DENY',
|
|
12
|
+
'Referrer-Policy': 'no-referrer',
|
|
13
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
14
|
+
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
15
|
+
// Auth-responses bevatten tokens/identiteit → nooit cachen.
|
|
16
|
+
'Cache-Control': 'no-store',
|
|
17
|
+
'Pragma': 'no-cache',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Minimale response-vorm om headers te zetten (express-compatibel). */
|
|
21
|
+
export interface HeaderResponseLike {
|
|
22
|
+
setHeader(name: string, value: string): unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Zet de volledige security-headerset op de response. */
|
|
26
|
+
export function applySecurityHeaders(res: HeaderResponseLike): void {
|
|
27
|
+
for (const [name, value] of Object.entries(SECURITY_HEADERS)) {
|
|
28
|
+
res.setHeader(name, value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-secret-leak redactie (US-A0704). De contract-foutbody is bewust smal: `{ code, message, fields? }`
|
|
3
|
+
* — geen stacktraces, geen tokens, geen wachtwoorden, geen "bestaat dit e-mailadres"-signaal. Deze
|
|
4
|
+
* helper is een laatste vangnet: hij verwijdert per ongeluk meegelifte gevoelige sleutels uit een
|
|
5
|
+
* object voordat het ooit de wire raakt. De primaire verdediging is dat de mechanism-laag generieke
|
|
6
|
+
* `AuthModuleError`-codes gooit en de controller ALLEEN `{ code, message, fields? }` serialiseert.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Sleutels die nooit in een (fout)response mogen belanden. */
|
|
10
|
+
const SENSITIVE_KEYS = [
|
|
11
|
+
'password',
|
|
12
|
+
'passwordhash',
|
|
13
|
+
'password_hash',
|
|
14
|
+
'token',
|
|
15
|
+
'refreshtoken',
|
|
16
|
+
'refresh_token',
|
|
17
|
+
'accesstoken',
|
|
18
|
+
'access_token',
|
|
19
|
+
'secret',
|
|
20
|
+
'authorization',
|
|
21
|
+
'cookie',
|
|
22
|
+
'set-cookie',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/** Redigeer gevoelige sleutels (recursief) uit een willekeurig object voor safe logging/serialisatie. */
|
|
26
|
+
export function redactSecrets<T>(value: T): T {
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map((v) => redactSecrets(v)) as unknown as T;
|
|
29
|
+
}
|
|
30
|
+
if (value && typeof value === 'object') {
|
|
31
|
+
const out: Record<string, unknown> = {};
|
|
32
|
+
for (const [key, val] of Object.entries(value)) {
|
|
33
|
+
out[key] = SENSITIVE_KEYS.includes(key.toLowerCase()) ? '[redacted]' : redactSecrets(val);
|
|
34
|
+
}
|
|
35
|
+
return out as unknown as T;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AuthErrorCode } from '../../contract/index.js';
|
|
2
|
+
/** Fout van de mechanism-laag, gedragen door een contract-`AuthErrorCode`. */
|
|
3
|
+
export declare class AuthModuleError extends Error {
|
|
4
|
+
readonly code: AuthErrorCode;
|
|
5
|
+
constructor(code: AuthErrorCode, message?: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Vertaal een kit-fout naar een {@link AuthModuleError}, of `undefined` als de fout niet uit de kit komt
|
|
9
|
+
* (dan laat de aanroeper 'm bubbelen). Reeds-vertaalde fouten worden ongewijzigd doorgegeven.
|
|
10
|
+
*/
|
|
11
|
+
export declare function asModuleError(err: unknown): AuthModuleError | undefined;
|
|
12
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../backend/src/errors.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAgB7D,8EAA8E;AAC9E,qBAAa,eAAgB,SAAQ,KAAK;IACxC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;gBACjB,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,MAAuB;CAMlE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,CAsBvE"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Foutvertaling: kit-fouten → de gedeelde contract-`AuthErrorCode`-taxonomie.
|
|
3
|
+
*
|
|
4
|
+
* De boodschappen blijven generiek: ze lekken nooit secrets/tokens of of een e-mail bestaat
|
|
5
|
+
* (no-enumeration). Onbekende (niet-kit) fouten worden NIET geslikt — die laten we bubbelen zodat de
|
|
6
|
+
* surface-laag ze als 500 afhandelt, zonder details te lekken.
|
|
7
|
+
*/
|
|
8
|
+
import { AuthError, InvalidCredentialsError, SingleUseTokenError, TokenError } from '@seifer-webapp-factory/kits/backend/auth';
|
|
9
|
+
/** Generieke, veilige boodschappen per code (nooit rauwe details). */
|
|
10
|
+
const MESSAGES = {
|
|
11
|
+
invalid_credentials: 'Ongeldige inloggegevens',
|
|
12
|
+
email_taken: 'E-mailadres is al in gebruik',
|
|
13
|
+
weak_password: 'Wachtwoord voldoet niet aan het beleid',
|
|
14
|
+
token_invalid: 'Token is ongeldig',
|
|
15
|
+
token_expired: 'Token is verlopen',
|
|
16
|
+
email_not_verified: 'E-mailadres is nog niet geverifieerd',
|
|
17
|
+
rate_limited: 'Te veel verzoeken',
|
|
18
|
+
csrf_failed: 'CSRF-controle mislukt',
|
|
19
|
+
unauthenticated: 'Niet geauthenticeerd',
|
|
20
|
+
validation_failed: 'Validatie mislukt',
|
|
21
|
+
};
|
|
22
|
+
/** Fout van de mechanism-laag, gedragen door een contract-`AuthErrorCode`. */
|
|
23
|
+
export class AuthModuleError extends Error {
|
|
24
|
+
code;
|
|
25
|
+
constructor(code, message = MESSAGES[code]) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'AuthModuleError';
|
|
28
|
+
this.code = code;
|
|
29
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Vertaal een kit-fout naar een {@link AuthModuleError}, of `undefined` als de fout niet uit de kit komt
|
|
34
|
+
* (dan laat de aanroeper 'm bubbelen). Reeds-vertaalde fouten worden ongewijzigd doorgegeven.
|
|
35
|
+
*/
|
|
36
|
+
export function asModuleError(err) {
|
|
37
|
+
if (err instanceof AuthModuleError)
|
|
38
|
+
return err;
|
|
39
|
+
if (err instanceof InvalidCredentialsError) {
|
|
40
|
+
return new AuthModuleError('invalid_credentials');
|
|
41
|
+
}
|
|
42
|
+
if (err instanceof TokenError) {
|
|
43
|
+
// 'invalid' | 'revoked' | 'reuse' → token_invalid; 'expired' → token_expired.
|
|
44
|
+
return new AuthModuleError(err.reason === 'expired' ? 'token_expired' : 'token_invalid');
|
|
45
|
+
}
|
|
46
|
+
if (err instanceof SingleUseTokenError) {
|
|
47
|
+
// 'invalid' | 'consumed' → token_invalid; 'expired' → token_expired.
|
|
48
|
+
return new AuthModuleError(err.reason === 'expired' ? 'token_expired' : 'token_invalid');
|
|
49
|
+
}
|
|
50
|
+
if (err instanceof AuthError && err.code === 'IDENTIFIER_TAKEN') {
|
|
51
|
+
return new AuthModuleError('email_taken');
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../backend/src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAG/H,sEAAsE;AACtE,MAAM,QAAQ,GAAkC;IAC9C,mBAAmB,EAAE,yBAAyB;IAC9C,WAAW,EAAE,8BAA8B;IAC3C,aAAa,EAAE,wCAAwC;IACvD,aAAa,EAAE,mBAAmB;IAClC,aAAa,EAAE,mBAAmB;IAClC,kBAAkB,EAAE,sCAAsC;IAC1D,YAAY,EAAE,mBAAmB;IACjC,WAAW,EAAE,uBAAuB;IACpC,eAAe,EAAE,sBAAsB;IACvC,iBAAiB,EAAE,mBAAmB;CACvC,CAAC;AAEF,8EAA8E;AAC9E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAgB;IAC7B,YAAY,IAAmB,EAAE,UAAkB,QAAQ,CAAC,IAAI,CAAC;QAC/D,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,IAAI,GAAG,YAAY,eAAe;QAAE,OAAO,GAAG,CAAC;IAE/C,IAAI,GAAG,YAAY,uBAAuB,EAAE,CAAC;QAC3C,OAAO,IAAI,eAAe,CAAC,qBAAqB,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,GAAG,YAAY,UAAU,EAAE,CAAC;QAC9B,8EAA8E;QAC9E,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,GAAG,YAAY,mBAAmB,EAAE,CAAC;QACvC,qEAAqE;QACrE,OAAO,IAAI,eAAe,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;IAC3F,CAAC;IAED,IAAI,GAAG,YAAY,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;QAChE,OAAO,IAAI,eAAe,CAAC,aAAa,CAAC,CAAC;IAC5C,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* authentication — backend-mechanism (flow-services). De verticale, framework-vrije servicelaag die de
|
|
3
|
+
* `auth`-kit componeert achter het gedeelde contract. Surface (NestJS-controllers/guards) leeft elders en
|
|
4
|
+
* consumeert deze barrel.
|
|
5
|
+
*/
|
|
6
|
+
export { registerUser, verifyEmail, login, refresh, logout, forgotPassword, resetPassword, type SessionResult, } from './services.js';
|
|
7
|
+
export { AuthModuleError, asModuleError } from './errors.js';
|
|
8
|
+
export type { AuthModuleDeps, AuthModuleConfig, AuthPurposes, PasswordPolicy, TokenServicePort, SingleUseServicePort, } from './ports.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../backend/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,EACL,OAAO,EACP,MAAM,EACN,cAAc,EACd,aAAa,EACb,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* authentication — backend-mechanism (flow-services). De verticale, framework-vrije servicelaag die de
|
|
3
|
+
* `auth`-kit componeert achter het gedeelde contract. Surface (NestJS-controllers/guards) leeft elders en
|
|
4
|
+
* consumeert deze barrel.
|
|
5
|
+
*/
|
|
6
|
+
export { registerUser, verifyEmail, login, refresh, logout, forgotPassword, resetPassword, } from './services.js';
|
|
7
|
+
export { AuthModuleError, asModuleError } from './errors.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../backend/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,EACL,OAAO,EACP,MAAM,EACN,cAAc,EACd,aAAa,GAEd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|