@cogenta/auth 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.
Files changed (57) hide show
  1. package/dist/audit.d.ts +17 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +113 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/credentials.d.ts +33 -0
  6. package/dist/credentials.d.ts.map +1 -0
  7. package/dist/credentials.js +95 -0
  8. package/dist/credentials.js.map +1 -0
  9. package/dist/index.d.ts +32 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +23 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/login.d.ts +66 -0
  14. package/dist/login.d.ts.map +1 -0
  15. package/dist/login.js +300 -0
  16. package/dist/login.js.map +1 -0
  17. package/dist/mfa.d.ts +4 -0
  18. package/dist/mfa.d.ts.map +1 -0
  19. package/dist/mfa.js +28 -0
  20. package/dist/mfa.js.map +1 -0
  21. package/dist/password.d.ts +11 -0
  22. package/dist/password.d.ts.map +1 -0
  23. package/dist/password.js +108 -0
  24. package/dist/password.js.map +1 -0
  25. package/dist/rate-limit.d.ts +10 -0
  26. package/dist/rate-limit.d.ts.map +1 -0
  27. package/dist/rate-limit.js +47 -0
  28. package/dist/rate-limit.js.map +1 -0
  29. package/dist/sessions.d.ts +20 -0
  30. package/dist/sessions.d.ts.map +1 -0
  31. package/dist/sessions.js +82 -0
  32. package/dist/sessions.js.map +1 -0
  33. package/dist/store.d.ts +30 -0
  34. package/dist/store.d.ts.map +1 -0
  35. package/dist/store.js +27 -0
  36. package/dist/store.js.map +1 -0
  37. package/dist/tables.d.ts +17 -0
  38. package/dist/tables.d.ts.map +1 -0
  39. package/dist/tables.js +103 -0
  40. package/dist/tables.js.map +1 -0
  41. package/dist/totp.d.ts +22 -0
  42. package/dist/totp.d.ts.map +1 -0
  43. package/dist/totp.js +115 -0
  44. package/dist/totp.js.map +1 -0
  45. package/dist/types.d.ts +64 -0
  46. package/dist/types.d.ts.map +1 -0
  47. package/dist/types.js +11 -0
  48. package/dist/types.js.map +1 -0
  49. package/dist/users.d.ts +12 -0
  50. package/dist/users.d.ts.map +1 -0
  51. package/dist/users.js +69 -0
  52. package/dist/users.js.map +1 -0
  53. package/dist/webauthn.d.ts +40 -0
  54. package/dist/webauthn.d.ts.map +1 -0
  55. package/dist/webauthn.js +81 -0
  56. package/dist/webauthn.js.map +1 -0
  57. package/package.json +44 -0
@@ -0,0 +1,17 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { AuditEntry, RecordAuditInput } from './types.js';
3
+ export interface AuditFilter {
4
+ readonly actorId?: string;
5
+ readonly action?: string;
6
+ readonly collection?: string;
7
+ readonly since?: string;
8
+ readonly limit?: number;
9
+ }
10
+ export interface AuditLog {
11
+ record(input: RecordAuditInput): Promise<AuditEntry>;
12
+ list(filter?: AuditFilter): Promise<readonly AuditEntry[]>;
13
+ /** Recomputes every hash and compares. Throws `AUDIT_CHAIN_BROKEN` naming the first mismatch. */
14
+ verify(): Promise<void>;
15
+ }
16
+ export declare function createAuditLog(db: DatabaseHandle, now?: () => number): AuditLog;
17
+ //# sourceMappingURL=audit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,cAAc,EAA0B,MAAM,eAAe,CAAA;AAEzF,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AA0D9D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;IACpD,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,UAAU,EAAE,CAAC,CAAA;IAC1D,iGAAiG;IACjG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACxB;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,GAAE,MAAM,MAAiB,GAAG,QAAQ,CA2FzF"}
package/dist/audit.js ADDED
@@ -0,0 +1,113 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { CogentaError, identifier, newId, sql } from '@cogenta/core';
3
+ import { TABLES } from './tables.js';
4
+ function fromRow(row) {
5
+ return {
6
+ id: row.id,
7
+ at: row.at,
8
+ actorId: row.actor_id,
9
+ actorRoles: JSON.parse(row.actor_roles),
10
+ action: row.action,
11
+ collection: row.collection_name,
12
+ entryId: row.entry_id,
13
+ diff: row.diff === null ? null : JSON.parse(row.diff),
14
+ hash: row.hash,
15
+ previousHash: row.previous_hash,
16
+ };
17
+ }
18
+ function computeHash(previousHash, fields) {
19
+ const canonical = JSON.stringify([
20
+ previousHash,
21
+ fields.id,
22
+ fields.at,
23
+ fields.actor_id,
24
+ fields.actor_roles,
25
+ fields.action,
26
+ fields.collection_name,
27
+ fields.entry_id,
28
+ fields.diff,
29
+ ]);
30
+ return createHash('sha256').update(canonical).digest('hex');
31
+ }
32
+ export function createAuditLog(db, now = Date.now) {
33
+ const table = identifier(TABLES.auditLog, db.dialect);
34
+ // Serialises appends within this process. Two concurrent writers computing
35
+ // "the current last hash" from a read that has not committed yet would
36
+ // both chain to the same previous hash and fork the chain — a database
37
+ // transaction alone does not stop that on every dialect's default isolation
38
+ // level, so the invariant is enforced here, once, rather than trusted to
39
+ // whichever caller remembers to lock.
40
+ let appendQueue = Promise.resolve();
41
+ async function lastHash() {
42
+ const result = await db.query(sql `select hash from ${table} order by at desc, id desc limit ${1}`);
43
+ return result.rows[0]?.hash ?? null;
44
+ }
45
+ return {
46
+ record: (input) => {
47
+ const task = appendQueue.then(async () => {
48
+ const previousHash = await lastHash();
49
+ const base = {
50
+ id: newId(now),
51
+ at: new Date(now()).toISOString(),
52
+ actor_id: input.actorId,
53
+ actor_roles: JSON.stringify(input.actorRoles),
54
+ action: input.action,
55
+ collection_name: input.collection ?? null,
56
+ entry_id: input.entryId ?? null,
57
+ diff: input.diff === undefined ? null : JSON.stringify(input.diff),
58
+ previous_hash: previousHash,
59
+ };
60
+ const hash = computeHash(previousHash, base);
61
+ await db.query(sql `
62
+ insert into ${table} (id, at, actor_id, actor_roles, action, collection_name, entry_id, diff, hash, previous_hash)
63
+ values (${base.id}, ${base.at}, ${base.actor_id}, ${base.actor_roles}, ${base.action},
64
+ ${base.collection_name}, ${base.entry_id}, ${base.diff}, ${hash}, ${base.previous_hash})`);
65
+ return fromRow({ ...base, hash });
66
+ });
67
+ // The queue must advance even if this append fails, or every append
68
+ // after a failure would wait on a promise that never resolves.
69
+ appendQueue = task.catch(() => undefined);
70
+ return task;
71
+ },
72
+ list: async (filter = {}) => {
73
+ const conditions = [sql `1 = 1`];
74
+ if (filter.actorId !== undefined)
75
+ conditions.push(sql `actor_id = ${filter.actorId}`);
76
+ if (filter.action !== undefined)
77
+ conditions.push(sql `action = ${filter.action}`);
78
+ if (filter.collection !== undefined)
79
+ conditions.push(sql `collection_name = ${filter.collection}`);
80
+ if (filter.since !== undefined)
81
+ conditions.push(sql `at >= ${filter.since}`);
82
+ const where = conditions.reduce((left, right) => sql `${left} and ${right}`);
83
+ const result = await db.query(sql `
84
+ select * from ${table} where ${where} order by at desc, id desc limit ${filter.limit ?? 200}`);
85
+ return result.rows.map(fromRow);
86
+ },
87
+ verify: async () => {
88
+ const result = await db.query(sql `select * from ${table} order by at asc, id asc`);
89
+ let expectedPrevious = null;
90
+ for (const row of result.rows) {
91
+ if (row.previous_hash !== expectedPrevious) {
92
+ throw new CogentaError({
93
+ code: 'AUDIT_CHAIN_BROKEN',
94
+ message: `Audit entry ${row.id} does not chain from the entry before it.`,
95
+ hint: 'The audit log has been edited or reordered outside of record(). Treat every entry from this point on as untrusted.',
96
+ details: { entryId: row.id },
97
+ });
98
+ }
99
+ const { hash, ...rest } = row;
100
+ if (computeHash(row.previous_hash, rest) !== hash) {
101
+ throw new CogentaError({
102
+ code: 'AUDIT_CHAIN_BROKEN',
103
+ message: `Audit entry ${row.id} has been modified since it was recorded.`,
104
+ hint: 'Its stored hash no longer matches its content. Treat every entry from this point on as untrusted.',
105
+ details: { entryId: row.id },
106
+ });
107
+ }
108
+ expectedPrevious = hash;
109
+ }
110
+ },
111
+ };
112
+ }
113
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { CredentialKind } from './types.js';
3
+ export interface WebAuthnCredentialData {
4
+ readonly credentialId: string;
5
+ readonly publicKey: string;
6
+ readonly counter: number;
7
+ readonly transports: readonly string[];
8
+ readonly label: string | undefined;
9
+ }
10
+ export interface CredentialStore {
11
+ setPassword(userId: string, password: string): Promise<void>;
12
+ verifyPassword(userId: string, password: string): Promise<boolean>;
13
+ hasPassword(userId: string): Promise<boolean>;
14
+ setTotpSecret(userId: string, secret: string): Promise<void>;
15
+ /** A secret exists but has not completed the "scan and confirm one code" step. */
16
+ totpSecret(userId: string): Promise<{
17
+ secret: string;
18
+ verified: boolean;
19
+ } | null>;
20
+ confirmTotp(userId: string): Promise<void>;
21
+ removeTotp(userId: string): Promise<void>;
22
+ addWebAuthnCredential(userId: string, data: WebAuthnCredentialData): Promise<void>;
23
+ webAuthnCredentials(userId: string): Promise<readonly WebAuthnCredentialData[]>;
24
+ webAuthnCredentialByExternalId(credentialId: string): Promise<{
25
+ userId: string;
26
+ data: WebAuthnCredentialData;
27
+ } | null>;
28
+ updateWebAuthnCounter(credentialId: string, counter: number): Promise<void>;
29
+ /** What second factors, if any, this user has set up. */
30
+ kinds(userId: string): Promise<readonly CredentialKind[]>;
31
+ }
32
+ export declare function createCredentialStore(db: DatabaseHandle, now?: () => number): CredentialStore;
33
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,cAAc,EAA0B,MAAM,eAAe,CAAA;AAGzF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAUhD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;IACtC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE7C,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,kFAAkF;IAClF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACjF,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1C,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEzC,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClF,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,sBAAsB,EAAE,CAAC,CAAA;IAC/E,8BAA8B,CAC5B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,sBAAsB,CAAA;KAAE,GAAG,IAAI,CAAC,CAAA;IACnE,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3E,yDAAyD;IACzD,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAAA;CAC1D;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,cAAc,EAClB,GAAG,GAAE,MAAM,MAAiB,GAC3B,eAAe,CAkHjB"}
@@ -0,0 +1,95 @@
1
+ import { CogentaError, identifier, newId, sql } from '@cogenta/core';
2
+ import { hashPassword, verifyPassword } from './password.js';
3
+ import { TABLES } from './tables.js';
4
+ export function createCredentialStore(db, now = Date.now) {
5
+ const table = identifier(TABLES.credentials, db.dialect);
6
+ async function findOne(userId, kind) {
7
+ const result = await db.query(sql `select * from ${table} where user_id = ${userId} and kind = ${kind} limit ${1}`);
8
+ return result.rows[0];
9
+ }
10
+ async function upsert(userId, kind, data) {
11
+ const existing = await findOne(userId, kind);
12
+ const payload = JSON.stringify(data);
13
+ if (existing === undefined) {
14
+ await db.query(sql `
15
+ insert into ${table} (id, user_id, kind, data, created_at)
16
+ values (${newId(now)}, ${userId}, ${kind}, ${payload}, ${new Date(now()).toISOString()})`);
17
+ }
18
+ else {
19
+ await db.query(sql `update ${table} set data = ${payload} where id = ${existing.id}`);
20
+ }
21
+ }
22
+ return {
23
+ setPassword: async (userId, password) => {
24
+ await upsert(userId, 'password', { hash: await hashPassword(password) });
25
+ },
26
+ verifyPassword: async (userId, password) => {
27
+ const row = await findOne(userId, 'password');
28
+ if (row === undefined)
29
+ return false;
30
+ const { hash } = JSON.parse(row.data);
31
+ return verifyPassword(password, hash);
32
+ },
33
+ hasPassword: async (userId) => (await findOne(userId, 'password')) !== undefined,
34
+ setTotpSecret: async (userId, secret) => {
35
+ await upsert(userId, 'totp', { secret, verified: false });
36
+ },
37
+ totpSecret: async (userId) => {
38
+ const row = await findOne(userId, 'totp');
39
+ if (row === undefined)
40
+ return null;
41
+ return JSON.parse(row.data);
42
+ },
43
+ confirmTotp: async (userId) => {
44
+ const row = await findOne(userId, 'totp');
45
+ if (row === undefined) {
46
+ throw new CogentaError({
47
+ code: 'AUTH_TOTP_INVALID',
48
+ message: 'No TOTP secret was set up for this user.',
49
+ hint: 'Call setTotpSecret before confirmTotp — there is nothing to confirm yet.',
50
+ });
51
+ }
52
+ const current = JSON.parse(row.data);
53
+ await db.query(sql `update ${table} set data = ${JSON.stringify({ ...current, verified: true })} where id = ${row.id}`);
54
+ },
55
+ removeTotp: async (userId) => {
56
+ await db.query(sql `delete from ${table} where user_id = ${userId} and kind = ${'totp'}`);
57
+ },
58
+ addWebAuthnCredential: async (userId, data) => {
59
+ await db.query(sql `
60
+ insert into ${table} (id, user_id, kind, data, created_at)
61
+ values (${newId(now)}, ${userId}, ${'webauthn'}, ${JSON.stringify(data)}, ${new Date(now()).toISOString()})`);
62
+ },
63
+ webAuthnCredentials: async (userId) => {
64
+ const result = await db.query(sql `select * from ${table} where user_id = ${userId} and kind = ${'webauthn'}`);
65
+ return result.rows.map((row) => JSON.parse(row.data));
66
+ },
67
+ webAuthnCredentialByExternalId: async (credentialId) => {
68
+ // Every WebAuthn credential is scanned rather than indexed by its wire id:
69
+ // one admin has a handful of passkeys, not thousands, and an index on a
70
+ // JSON-embedded field would need a fourth table for one lookup.
71
+ const result = await db.query(sql `select * from ${table} where kind = ${'webauthn'}`);
72
+ for (const row of result.rows) {
73
+ const data = JSON.parse(row.data);
74
+ if (data.credentialId === credentialId)
75
+ return { userId: row.user_id, data };
76
+ }
77
+ return null;
78
+ },
79
+ updateWebAuthnCounter: async (credentialId, counter) => {
80
+ const result = await db.query(sql `select * from ${table} where kind = ${'webauthn'}`);
81
+ for (const row of result.rows) {
82
+ const data = JSON.parse(row.data);
83
+ if (data.credentialId === credentialId) {
84
+ await db.query(sql `update ${table} set data = ${JSON.stringify({ ...data, counter })} where id = ${row.id}`);
85
+ return;
86
+ }
87
+ }
88
+ },
89
+ kinds: async (userId) => {
90
+ const result = await db.query(sql `select distinct kind from ${table} where user_id = ${userId}`);
91
+ return result.rows.map((row) => row.kind);
92
+ },
93
+ };
94
+ }
95
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `@cogenta/auth` — identity, sessions, credentials and the audit log.
3
+ *
4
+ * Contract A stops at "roles are an open set of names a collection's
5
+ * permissions declare" and leaves who attaches a role to a real person to L2.
6
+ * This package is that attachment: users, password/TOTP/WebAuthn credentials,
7
+ * bearer sessions, login-attempt backoff, MFA enforcement for sensitive
8
+ * roles, and the hash-chained audit log L4's agents will write to as well.
9
+ */
10
+ export type { AuditFilter, AuditLog } from './audit.js';
11
+ export { createAuditLog } from './audit.js';
12
+ export type { CredentialStore, WebAuthnCredentialData } from './credentials.js';
13
+ export { createCredentialStore } from './credentials.js';
14
+ export type { AuthService, AuthServiceOptions, LoginResult } from './login.js';
15
+ export { createAuthService } from './login.js';
16
+ export { requiresMfa, sensitiveRoles } from './mfa.js';
17
+ export { hashPassword, verifyPassword } from './password.js';
18
+ export type { RateLimiter } from './rate-limit.js';
19
+ export { createRateLimiter } from './rate-limit.js';
20
+ export type { SessionStore } from './sessions.js';
21
+ export { createSessionStore } from './sessions.js';
22
+ export type { AuthStore, AuthStoreOptions } from './store.js';
23
+ export { createAuthStore } from './store.js';
24
+ export { ensureAuthTables, TABLES as AUTH_TABLES } from './tables.js';
25
+ export type { VerifyTotpOptions } from './totp.js';
26
+ export { assertTotpSecretFormat, generateTotpSecret, totpUri, verifyTotp, } from './totp.js';
27
+ export * from './types.js';
28
+ export type { UserStore } from './users.js';
29
+ export { createUserStore } from './users.js';
30
+ export type { AuthenticationOptions, RegistrationOptions, WebAuthnAuthenticationResult, WebAuthnConfig, } from './webauthn.js';
31
+ export { beginWebAuthnAuthentication, beginWebAuthnRegistration, completeWebAuthnAuthentication, completeWebAuthnRegistration, } from './webauthn.js';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3C,YAAY,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAC/E,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACxD,YAAY,EAAE,WAAW,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC5D,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AACnD,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAClD,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,aAAa,CAAA;AACrE,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,OAAO,EACP,UAAU,GACX,MAAM,WAAW,CAAA;AAClB,cAAc,YAAY,CAAA;AAC1B,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,YAAY,EACV,qBAAqB,EACrB,mBAAmB,EACnB,4BAA4B,EAC5B,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,4BAA4B,GAC7B,MAAM,eAAe,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@cogenta/auth` — identity, sessions, credentials and the audit log.
3
+ *
4
+ * Contract A stops at "roles are an open set of names a collection's
5
+ * permissions declare" and leaves who attaches a role to a real person to L2.
6
+ * This package is that attachment: users, password/TOTP/WebAuthn credentials,
7
+ * bearer sessions, login-attempt backoff, MFA enforcement for sensitive
8
+ * roles, and the hash-chained audit log L4's agents will write to as well.
9
+ */
10
+ export { createAuditLog } from './audit.js';
11
+ export { createCredentialStore } from './credentials.js';
12
+ export { createAuthService } from './login.js';
13
+ export { requiresMfa, sensitiveRoles } from './mfa.js';
14
+ export { hashPassword, verifyPassword } from './password.js';
15
+ export { createRateLimiter } from './rate-limit.js';
16
+ export { createSessionStore } from './sessions.js';
17
+ export { createAuthStore } from './store.js';
18
+ export { ensureAuthTables, TABLES as AUTH_TABLES } from './tables.js';
19
+ export { assertTotpSecretFormat, generateTotpSecret, totpUri, verifyTotp, } from './totp.js';
20
+ export * from './types.js';
21
+ export { createUserStore } from './users.js';
22
+ export { beginWebAuthnAuthentication, beginWebAuthnRegistration, completeWebAuthnAuthentication, completeWebAuthnRegistration, } from './webauthn.js';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAE5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAEnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAElD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,IAAI,WAAW,EAAE,MAAM,aAAa,CAAA;AAErE,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,OAAO,EACP,UAAU,GACX,MAAM,WAAW,CAAA;AAClB,cAAc,YAAY,CAAA;AAE1B,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAO5C,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,8BAA8B,EAC9B,4BAA4B,GAC7B,MAAM,eAAe,CAAA"}
@@ -0,0 +1,66 @@
1
+ import { type DatabaseHandle } from '@cogenta/core';
2
+ import type { CollectionDefinition } from '@cogenta/schema';
3
+ import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from '@simplewebauthn/server';
4
+ import type { IssuedSession, User } from './types.js';
5
+ import { type WebAuthnConfig } from './webauthn.js';
6
+ export type LoginResult = {
7
+ readonly status: 'session';
8
+ readonly session: IssuedSession;
9
+ readonly user: User;
10
+ } | {
11
+ readonly status: 'mfa_required';
12
+ readonly ticket: string;
13
+ readonly availableFactors: readonly ('totp' | 'webauthn')[];
14
+ } | {
15
+ /**
16
+ * A sensitive role with no second factor set up yet. Not an error: the
17
+ * password was correct, and the only thing standing between this
18
+ * attempt and a session is enrolling TOTP right now, with the ticket
19
+ * this result carries.
20
+ */
21
+ readonly status: 'totp_setup_required';
22
+ readonly ticket: string;
23
+ };
24
+ export interface TotpSetup {
25
+ readonly secret: string;
26
+ /** `otpauth://` URI, for a QR code the authenticator app scans. */
27
+ readonly uri: string;
28
+ }
29
+ export interface WebAuthnRegistrationChallenge {
30
+ readonly options: PublicKeyCredentialCreationOptionsJSON;
31
+ readonly ticket: string;
32
+ }
33
+ export interface WebAuthnAuthenticationChallenge {
34
+ readonly options: PublicKeyCredentialRequestOptionsJSON;
35
+ readonly ticket: string;
36
+ }
37
+ export interface AuthServiceOptions {
38
+ readonly db: DatabaseHandle;
39
+ /** From `COGENTA_AUTH_SIGNING_KEY`, never from a config file (rule R7). */
40
+ readonly signingKey: string;
41
+ /** What decides whether a role needs a second factor (`sensitiveRoles`). */
42
+ readonly collections: readonly CollectionDefinition[];
43
+ /** Shown in the authenticator app next to the account name. Defaults to "Cogenta". */
44
+ readonly issuer?: string;
45
+ /** Absent means passkeys are off: registration and login both refuse with a clear reason. */
46
+ readonly webauthn?: WebAuthnConfig;
47
+ readonly now?: () => number;
48
+ }
49
+ export interface AuthService {
50
+ passwordLogin(email: string, password: string): Promise<LoginResult>;
51
+ totpLogin(ticket: string, token: string): Promise<LoginResult>;
52
+ /** Issues a session directly: a verified passkey already proved a strong second factor. */
53
+ sessionForVerifiedUser(userId: string): Promise<LoginResult>;
54
+ /** Generates a fresh TOTP secret for a `totp_setup_required` ticket and stores it, unconfirmed. */
55
+ beginTotpSetup(ticket: string): Promise<TotpSetup>;
56
+ /** Confirms the code from the authenticator app and, on success, signs the user in. */
57
+ confirmTotpSetup(ticket: string, token: string): Promise<LoginResult>;
58
+ /** Adding a passkey to an already-identified account — `userId` comes from an existing session. */
59
+ beginWebAuthnRegistration(userId: string): Promise<WebAuthnRegistrationChallenge>;
60
+ completeWebAuthnRegistration(ticket: string, response: RegistrationResponseJSON, label?: string): Promise<void>;
61
+ /** Usernameless: no account is named up front, so any resident passkey the browser offers can answer. */
62
+ beginWebAuthnLogin(): Promise<WebAuthnAuthenticationChallenge>;
63
+ completeWebAuthnLogin(ticket: string, response: AuthenticationResponseJSON): Promise<LoginResult>;
64
+ }
65
+ export declare function createAuthService(options: AuthServiceOptions): AuthService;
66
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}