rlz-engine 1.0.53 → 1.0.55

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 (134) hide show
  1. package/README.md +225 -0
  2. package/dist/back/auth/admin-secret.d.ts +4 -0
  3. package/dist/back/auth/admin-secret.js +86 -0
  4. package/dist/back/auth/admin-secret.js.map +1 -0
  5. package/dist/back/auth/cookies.d.ts +12 -0
  6. package/dist/back/auth/cookies.js +20 -0
  7. package/dist/back/auth/cookies.js.map +1 -0
  8. package/dist/back/auth/csrf.d.ts +8 -0
  9. package/dist/back/auth/csrf.js +47 -0
  10. package/dist/back/auth/csrf.js.map +1 -0
  11. package/dist/back/auth/model.d.ts +7 -3
  12. package/dist/back/auth/module.d.ts +14 -0
  13. package/dist/back/auth/module.js +121 -0
  14. package/dist/back/auth/module.js.map +1 -0
  15. package/dist/back/auth/rate-limit.d.ts +11 -0
  16. package/dist/back/auth/rate-limit.js +59 -0
  17. package/dist/back/auth/rate-limit.js.map +1 -0
  18. package/dist/back/auth/session.d.ts +22 -0
  19. package/dist/back/auth/session.js +55 -0
  20. package/dist/back/auth/session.js.map +1 -0
  21. package/dist/back/auth/storage.d.ts +9 -9
  22. package/dist/back/auth/storage.js +39 -36
  23. package/dist/back/auth/storage.js.map +1 -1
  24. package/dist/back/auth/utils.d.ts +10 -8
  25. package/dist/back/auth/utils.js +43 -56
  26. package/dist/back/auth/utils.js.map +1 -1
  27. package/dist/back/certificate/adm-rpc.d.ts +5 -0
  28. package/dist/back/certificate/adm-rpc.js +52 -0
  29. package/dist/back/certificate/adm-rpc.js.map +1 -0
  30. package/dist/back/certificate/module.d.ts +13 -0
  31. package/dist/back/certificate/module.js +19 -0
  32. package/dist/back/certificate/module.js.map +1 -0
  33. package/dist/back/cli/cli-engine.d.ts +25 -0
  34. package/dist/back/cli/cli-engine.js +68 -0
  35. package/dist/back/cli/cli-engine.js.map +1 -0
  36. package/dist/back/http/module.d.ts +7 -0
  37. package/dist/back/http/module.js +34 -0
  38. package/dist/back/http/module.js.map +1 -0
  39. package/dist/back/http/server.d.ts +22 -0
  40. package/dist/back/http/server.js +126 -0
  41. package/dist/back/http/server.js.map +1 -0
  42. package/dist/back/logger/adm-rpc.d.ts +2 -0
  43. package/dist/back/logger/adm-rpc.js +50 -0
  44. package/dist/back/logger/adm-rpc.js.map +1 -0
  45. package/dist/back/logger/audit-logger.d.ts +97 -0
  46. package/dist/back/logger/audit-logger.js +155 -0
  47. package/dist/back/logger/audit-logger.js.map +1 -0
  48. package/dist/back/logger/cli.d.ts +2 -0
  49. package/dist/back/logger/cli.js +43 -0
  50. package/dist/back/logger/cli.js.map +1 -0
  51. package/dist/back/logger/format.d.ts +3 -0
  52. package/dist/back/logger/format.js +27 -0
  53. package/dist/back/logger/format.js.map +1 -0
  54. package/dist/back/logger/module.d.ts +14 -0
  55. package/dist/back/logger/module.js +24 -0
  56. package/dist/back/logger/module.js.map +1 -0
  57. package/dist/back/logger/sqlite-storage.d.ts +27 -0
  58. package/dist/back/logger/sqlite-storage.js +169 -0
  59. package/dist/back/logger/sqlite-storage.js.map +1 -0
  60. package/dist/back/logger/system-logger.d.ts +27 -0
  61. package/dist/back/logger/system-logger.js +82 -0
  62. package/dist/back/logger/system-logger.js.map +1 -0
  63. package/dist/back/module/composer.d.ts +34 -0
  64. package/dist/back/module/composer.js +57 -0
  65. package/dist/back/module/composer.js.map +1 -0
  66. package/dist/back/rpc/gen.d.ts +2 -1
  67. package/dist/back/rpc/gen.js +38 -17
  68. package/dist/back/rpc/gen.js.map +1 -1
  69. package/dist/back/rpc/module.d.ts +14 -0
  70. package/dist/back/rpc/module.js +43 -0
  71. package/dist/back/rpc/module.js.map +1 -0
  72. package/dist/back/rpc/rpc.d.ts +53 -6
  73. package/dist/back/rpc/rpc.js +72 -29
  74. package/dist/back/rpc/rpc.js.map +1 -1
  75. package/dist/back/storage/db.js +5 -3
  76. package/dist/back/storage/db.js.map +1 -1
  77. package/dist/back/storage/model.d.ts +2 -2
  78. package/dist/back/storage/model.js +2 -2
  79. package/dist/back/storage/model.js.map +1 -1
  80. package/dist/back/storage/sync.d.ts +5 -2
  81. package/dist/back/storage/sync.js +1 -3
  82. package/dist/back/storage/sync.js.map +1 -1
  83. package/dist/client/adm/admin.d.ts +17 -0
  84. package/dist/client/adm/admin.js +62 -0
  85. package/dist/client/adm/admin.js.map +1 -0
  86. package/dist/client/adm/audit-logs.d.ts +5 -0
  87. package/dist/client/adm/audit-logs.js +68 -0
  88. package/dist/client/adm/audit-logs.js.map +1 -0
  89. package/dist/client/adm/certificate.d.ts +5 -0
  90. package/dist/client/adm/certificate.js +48 -0
  91. package/dist/client/adm/certificate.js.map +1 -0
  92. package/dist/client/config.d.ts +2 -0
  93. package/dist/client/config.js +7 -0
  94. package/dist/client/config.js.map +1 -1
  95. package/dist/client/rpc.d.ts +4 -4
  96. package/dist/client/rpc.js +19 -7
  97. package/dist/client/rpc.js.map +1 -1
  98. package/dist/client/screens/404.js.map +1 -1
  99. package/dist/client/screens/SignupSigninScreen.d.ts +13 -1
  100. package/dist/client/screens/SignupSigninScreen.js +41 -37
  101. package/dist/client/screens/SignupSigninScreen.js.map +1 -1
  102. package/dist/client/state/auth.d.ts +13 -7
  103. package/dist/client/state/auth.js +24 -19
  104. package/dist/client/state/auth.js.map +1 -1
  105. package/dist/client/sync.d.ts +7 -2
  106. package/dist/client/sync.js +5 -3
  107. package/dist/client/sync.js.map +1 -1
  108. package/dist/client/widgets/ItemsSelect.js +15 -8
  109. package/dist/client/widgets/ItemsSelect.js.map +1 -1
  110. package/package.json +87 -77
  111. package/dist/back/auth/controllers.d.ts +0 -7
  112. package/dist/back/auth/controllers.js +0 -39
  113. package/dist/back/auth/controllers.js.map +0 -1
  114. package/dist/back/esbuild-plugin-pino.d.ts +0 -6
  115. package/dist/back/esbuild-plugin-pino.js +0 -87
  116. package/dist/back/esbuild-plugin-pino.js.map +0 -1
  117. package/dist/back/logger.d.ts +0 -7
  118. package/dist/back/logger.js +0 -43
  119. package/dist/back/logger.js.map +0 -1
  120. package/dist/back/server.d.ts +0 -17
  121. package/dist/back/server.js +0 -94
  122. package/dist/back/server.js.map +0 -1
  123. package/dist/client/api/api.d.ts +0 -22
  124. package/dist/client/api/api.js +0 -87
  125. package/dist/client/api/api.js.map +0 -1
  126. package/dist/client/api/auth.d.ts +0 -5
  127. package/dist/client/api/auth.js +0 -22
  128. package/dist/client/api/auth.js.map +0 -1
  129. package/dist/shared/api/auth.d.ts +0 -19
  130. package/dist/shared/api/auth.js +0 -17
  131. package/dist/shared/api/auth.js.map +0 -1
  132. package/dist/shared/api/sync.d.ts +0 -21
  133. package/dist/shared/api/sync.js +0 -22
  134. package/dist/shared/api/sync.js.map +0 -1
@@ -0,0 +1,59 @@
1
+ import { httpErrors } from '@fastify/sensible';
2
+ const SIGNIN_WINDOW_MS = 15 * 60 * 1000;
3
+ const SIGNIN_ATTEMPTS_PER_WINDOW = 10;
4
+ const SIGNUP_WINDOW_MS = 60 * 60 * 1000;
5
+ const SIGNUP_ATTEMPTS_PER_WINDOW = 5;
6
+ const MAX_CONCURRENT_OPERATIONS = 4;
7
+ const MAX_TRACKED_BUCKETS = 10_000;
8
+ /** Bounds anonymous password-hash work per peer and across this server process. */
9
+ export class AuthRateLimiter {
10
+ buckets = new Map();
11
+ activeOperations = 0;
12
+ bucketInsertionsSinceCleanup = 0;
13
+ async run(operation, ip, action) {
14
+ if (this.activeOperations >= MAX_CONCURRENT_OPERATIONS)
15
+ throw httpErrors.tooManyRequests();
16
+ this.checkRate(operation, ip);
17
+ this.activeOperations += 1;
18
+ try {
19
+ return await action();
20
+ }
21
+ finally {
22
+ this.activeOperations -= 1;
23
+ }
24
+ }
25
+ checkRate(operation, ip) {
26
+ const now = Date.now();
27
+ const windowMs = operation === 'signin' ? SIGNIN_WINDOW_MS : SIGNUP_WINDOW_MS;
28
+ const limit = operation === 'signin' ? SIGNIN_ATTEMPTS_PER_WINDOW : SIGNUP_ATTEMPTS_PER_WINDOW;
29
+ const key = `${operation}:${ip}`;
30
+ const bucket = this.buckets.get(key);
31
+ if (bucket === undefined || now - bucket.startedAt >= windowMs) {
32
+ if (bucket === undefined && this.buckets.size >= MAX_TRACKED_BUCKETS) {
33
+ this.bucketInsertionsSinceCleanup += 1;
34
+ if (this.bucketInsertionsSinceCleanup >= 256) {
35
+ this.removeExpiredBuckets(now);
36
+ this.bucketInsertionsSinceCleanup = 0;
37
+ }
38
+ if (this.buckets.size >= MAX_TRACKED_BUCKETS) {
39
+ // Keep attacker-controlled peer diversity from growing this map without bound.
40
+ const oldestKey = this.buckets.keys().next().value;
41
+ if (oldestKey !== undefined)
42
+ this.buckets.delete(oldestKey);
43
+ }
44
+ }
45
+ this.buckets.set(key, { startedAt: now, attempts: 1, expiresAt: now + windowMs });
46
+ return;
47
+ }
48
+ if (bucket.attempts >= limit)
49
+ throw httpErrors.tooManyRequests();
50
+ bucket.attempts += 1;
51
+ }
52
+ removeExpiredBuckets(now) {
53
+ for (const [key, bucket] of this.buckets) {
54
+ if (bucket.expiresAt <= now)
55
+ this.buckets.delete(key);
56
+ }
57
+ }
58
+ }
59
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/back/auth/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACvC,MAAM,0BAA0B,GAAG,EAAE,CAAA;AACrC,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AACvC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AACpC,MAAM,yBAAyB,GAAG,CAAC,CAAA;AACnC,MAAM,mBAAmB,GAAG,MAAM,CAAA;AAUlC,mFAAmF;AACnF,MAAM,OAAO,eAAe;IACP,OAAO,GAAG,IAAI,GAAG,EAAsB,CAAA;IAChD,gBAAgB,GAAG,CAAC,CAAA;IACpB,4BAA4B,GAAG,CAAC,CAAA;IAExC,KAAK,CAAC,GAAG,CAAI,SAAwB,EAAE,EAAU,EAAE,MAAwB;QACvE,IAAI,IAAI,CAAC,gBAAgB,IAAI,yBAAyB;YAAE,MAAM,UAAU,CAAC,eAAe,EAAE,CAAA;QAC1F,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QAE7B,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAA;QAC1B,IAAI,CAAC;YACD,OAAO,MAAM,MAAM,EAAE,CAAA;QACzB,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,gBAAgB,IAAI,CAAC,CAAA;QAC9B,CAAC;IACL,CAAC;IAEO,SAAS,CAAC,SAAwB,EAAE,EAAU;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,QAAQ,GAAG,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,gBAAgB,CAAA;QAC7E,MAAM,KAAK,GAAG,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,0BAA0B,CAAA;QAC9F,MAAM,GAAG,GAAG,GAAG,SAAS,IAAI,EAAE,EAAE,CAAA;QAChC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEpC,IAAI,MAAM,KAAK,SAAS,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,IAAI,QAAQ,EAAE,CAAC;YAC7D,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,mBAAmB,EAAE,CAAC;gBACnE,IAAI,CAAC,4BAA4B,IAAI,CAAC,CAAA;gBACtC,IAAI,IAAI,CAAC,4BAA4B,IAAI,GAAG,EAAE,CAAC;oBAC3C,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAA;oBAC9B,IAAI,CAAC,4BAA4B,GAAG,CAAC,CAAA;gBACzC,CAAC;gBACD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,mBAAmB,EAAE,CAAC;oBAC3C,+EAA+E;oBAC/E,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAA;oBAClD,IAAI,SAAS,KAAK,SAAS;wBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;gBAC/D,CAAC;YACL,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC,CAAA;YACjF,OAAM;QACV,CAAC;QAED,IAAI,MAAM,CAAC,QAAQ,IAAI,KAAK;YAAE,MAAM,UAAU,CAAC,eAAe,EAAE,CAAA;QAChE,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAA;IACxB,CAAC;IAEO,oBAAoB,CAAC,GAAW;QACpC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,MAAM,CAAC,SAAS,IAAI,GAAG;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACzD,CAAC;IACL,CAAC;CACJ"}
@@ -0,0 +1,22 @@
1
+ import { Binary } from 'mongodb';
2
+ import type { AuthStorage } from './storage.js';
3
+ import { toAuthUser } from './utils.js';
4
+ import type { RpcRequestContext } from '../rpc/rpc.js';
5
+ export declare const SESSION_IDLE_TIMEOUT_MS: number;
6
+ export declare const SESSION_ABSOLUTE_TIMEOUT_MS: number;
7
+ export interface AuthenticatedSession {
8
+ user: ReturnType<typeof toAuthUser>;
9
+ sessionHash: Binary;
10
+ csrfToken: string;
11
+ }
12
+ /** Create a new opaque session; only its one-way hash is persisted. */
13
+ export declare function createSession(storage: AuthStorage, userId: string): Promise<{
14
+ sessionId: string;
15
+ csrfToken: string;
16
+ }>;
17
+ /** Revoke the session currently held by this browser before issuing a replacement. */
18
+ export declare function revokeCurrentSession(storage: AuthStorage, context: RpcRequestContext): Promise<void>;
19
+ /** Authenticate only previously issued session IDs and reject expired or idle sessions. */
20
+ export declare function authenticateSession(storage: AuthStorage, context: RpcRequestContext, idleTimeoutMs?: number): Promise<AuthenticatedSession>;
21
+ /** Require both same-origin browser requests and a session-bound synchronizer token. */
22
+ export declare function verifySessionCsrf(storage: AuthStorage, context: RpcRequestContext, session: AuthenticatedSession): Promise<void>;
@@ -0,0 +1,55 @@
1
+ import { httpErrors } from '@fastify/sensible';
2
+ import { createHash, randomBytes } from 'node:crypto';
3
+ import { Binary } from 'mongodb';
4
+ import { SESSION_COOKIE_NAME } from './cookies.js';
5
+ import { assertSameOrigin, verifyCsrfToken } from './csrf.js';
6
+ import { toAuthUser } from './utils.js';
7
+ export const SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
8
+ export const SESSION_ABSOLUTE_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1000;
9
+ /** Create a new opaque session; only its one-way hash is persisted. */
10
+ export async function createSession(storage, userId) {
11
+ const sessionId = randomBytes(32).toString('base64url');
12
+ const csrfToken = randomBytes(32).toString('base64url');
13
+ const now = new Date();
14
+ const expiresAt = new Date(now.getTime() + SESSION_ABSOLUTE_TIMEOUT_MS);
15
+ const sessionHash = hashSessionId(sessionId);
16
+ await storage.createSession(sessionHash, userId, csrfToken, now, expiresAt);
17
+ return { sessionId, csrfToken };
18
+ }
19
+ /** Revoke the session currently held by this browser before issuing a replacement. */
20
+ export async function revokeCurrentSession(storage, context) {
21
+ const sessionId = context.request.cookies[SESSION_COOKIE_NAME];
22
+ if (typeof sessionId === 'string' && /^[A-Za-z0-9_-]{43}$/.test(sessionId)) {
23
+ await storage.deleteSession(hashSessionId(sessionId));
24
+ }
25
+ }
26
+ /** Authenticate only previously issued session IDs and reject expired or idle sessions. */
27
+ export async function authenticateSession(storage, context, idleTimeoutMs = SESSION_IDLE_TIMEOUT_MS) {
28
+ const sessionId = context.request.cookies[SESSION_COOKIE_NAME];
29
+ if (typeof sessionId !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(sessionId)) {
30
+ throw httpErrors.forbidden();
31
+ }
32
+ const sessionHash = hashSessionId(sessionId);
33
+ const session = await storage.getSession(sessionHash, new Date(), idleTimeoutMs);
34
+ if (session === null)
35
+ throw httpErrors.forbidden();
36
+ const user = await storage.getUserById(session.userId);
37
+ if (user === null) {
38
+ await storage.deleteSession(sessionHash);
39
+ throw httpErrors.forbidden();
40
+ }
41
+ return { user: toAuthUser(user), sessionHash, csrfToken: session.csrfToken };
42
+ }
43
+ /** Require both same-origin browser requests and a session-bound synchronizer token. */
44
+ export async function verifySessionCsrf(storage, context, session) {
45
+ assertSameOrigin(context.request);
46
+ const provided = context.request.headers['x-csrf-token'];
47
+ if (!verifyCsrfToken(session.csrfToken, provided))
48
+ throw httpErrors.forbidden();
49
+ await storage.touchSession(session.sessionHash, new Date());
50
+ }
51
+ /** Require same-origin requests for anonymous auth actions and session bootstrap. */
52
+ function hashSessionId(sessionId) {
53
+ return new Binary(createHash('sha256').update(sessionId).digest());
54
+ }
55
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/back/auth/session.ts"],"names":[],"mappings":"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"}
@@ -1,22 +1,22 @@
1
- import { FastifyRequest } from 'fastify';
2
1
  import { Binary, Collection } from 'mongodb';
3
2
  import { MongoStorage } from '../storage/db.js';
4
- import { StorageTempPassword, StorageUser } from './model.js';
3
+ import { StorageSession, StorageUser } from './model.js';
5
4
  export declare class AuthStorage {
6
5
  private readonly logger;
7
6
  private readonly mongo;
8
7
  private constructor();
9
8
  static create(mongo: MongoStorage): Promise<AuthStorage>;
10
9
  private init;
11
- createUser(id: string, name: string, email: string, passwordSalt: Binary, passwordHash: Binary): Promise<void>;
10
+ createUser(id: string, name: string, email: string, passwordSalt: Binary, passwordHash: Binary, passwordHashVersion: number): Promise<void>;
12
11
  getUser(name: string): Promise<StorageUser | null>;
13
12
  markUserActive(userId: string): Promise<void>;
14
- pushTempPassword(userId: string, passwordHash: Binary, validUntil: Date): Promise<void>;
15
- deleteTempPassword(userId: string, passwordHash: Binary): Promise<void>;
16
- getUserByTempPassword(userId: string, passwordHash: Binary): Promise<StorageUser | null>;
17
- private createCollections;
13
+ getUserById(userId: string): Promise<StorageUser | null>;
14
+ updateLegacyPasswordHash(userId: string, oldSalt: Binary, oldHash: Binary, newSalt: Binary, newHash: Binary): Promise<boolean>;
15
+ createSession(sessionHash: Binary, userId: string, csrfToken: string, createdAt: Date, expiresAt: Date): Promise<void>;
16
+ getSession(sessionHash: Binary, now: Date, idleTimeoutMs: number): Promise<StorageSession | null>;
17
+ touchSession(sessionHash: Binary, lastActivityAt: Date): Promise<void>;
18
+ deleteSession(sessionHash: Binary): Promise<void>;
18
19
  private createIndexes;
19
20
  get users(): Collection<StorageUser>;
20
- get tempPasswords(): Collection<StorageTempPassword>;
21
- get auth(): (headers: FastifyRequest["headers"]) => Promise<string>;
21
+ get sessions(): Collection<StorageSession>;
22
22
  }
@@ -1,8 +1,7 @@
1
1
  import { DateTime } from 'luxon';
2
- import { logger } from '../logger.js';
3
- import { auth } from './utils.js';
2
+ import { getSystemLogger } from '../logger/system-logger.js';
4
3
  export class AuthStorage {
5
- logger = logger('AuthStorage');
4
+ logger = getSystemLogger('AuthStorage');
6
5
  mongo;
7
6
  constructor(mongo) {
8
7
  this.mongo = mongo;
@@ -13,18 +12,17 @@ export class AuthStorage {
13
12
  return s;
14
13
  }
15
14
  async init() {
16
- await Promise.all([
17
- 'users', 'temp-passwords'
18
- ].map(i => this.mongo.createCollection(i)));
15
+ await Promise.all(['users', 'sessions'].map((i) => this.mongo.createCollection(i)));
19
16
  await this.createIndexes();
20
17
  }
21
- async createUser(id, name, email, passwordSalt, passwordHash) {
18
+ async createUser(id, name, email, passwordSalt, passwordHash, passwordHashVersion) {
22
19
  await this.users.insertOne({
23
20
  _id: id,
24
21
  name,
25
22
  email,
26
23
  passwordSalt,
27
24
  passwordHash,
25
+ passwordHashVersion,
28
26
  lastActivityDate: DateTime.utc().toJSDate()
29
27
  });
30
28
  }
@@ -34,28 +32,38 @@ export class AuthStorage {
34
32
  async markUserActive(userId) {
35
33
  await this.users.updateOne({ _id: userId }, { $set: { lastActivityDate: DateTime.utc().toJSDate() } });
36
34
  }
37
- async pushTempPassword(userId, passwordHash, validUntil) {
38
- await this.tempPasswords.insertOne({
35
+ async getUserById(userId) {
36
+ return await this.users.findOne({ _id: userId });
37
+ }
38
+ async updateLegacyPasswordHash(userId, oldSalt, oldHash, newSalt, newHash) {
39
+ const result = await this.users.updateOne({ _id: userId, passwordSalt: oldSalt, passwordHash: oldHash, passwordHashVersion: { $exists: false } }, { $set: { passwordSalt: newSalt, passwordHash: newHash, passwordHashVersion: 1 } });
40
+ return result.modifiedCount === 1;
41
+ }
42
+ async createSession(sessionHash, userId, csrfToken, createdAt, expiresAt) {
43
+ await this.sessions.insertOne({
39
44
  userId,
40
- passwordHash,
41
- validUntil
45
+ sessionHash,
46
+ csrfToken,
47
+ createdAt,
48
+ lastActivityAt: createdAt,
49
+ expiresAt
42
50
  });
43
51
  }
44
- async deleteTempPassword(userId, passwordHash) {
45
- await this.tempPasswords.deleteOne({ userId, passwordHash });
46
- }
47
- async getUserByTempPassword(userId, passwordHash) {
48
- const t = await this.tempPasswords.findOne({ userId, passwordHash });
49
- if (t === null) {
52
+ async getSession(sessionHash, now, idleTimeoutMs) {
53
+ const session = await this.sessions.findOne({ sessionHash, expiresAt: { $gt: now } });
54
+ if (session === null)
50
55
  return null;
51
- }
52
- if (DateTime.utc() > DateTime.fromJSDate(t.validUntil)) {
53
- await this.tempPasswords.deleteOne({ _id: t._id });
56
+ if (session.lastActivityAt.getTime() <= now.getTime() - idleTimeoutMs) {
57
+ await this.sessions.deleteOne({ sessionHash });
54
58
  return null;
55
59
  }
56
- return await this.users.findOne({ _id: t.userId });
60
+ return session;
57
61
  }
58
- async createCollections() {
62
+ async touchSession(sessionHash, lastActivityAt) {
63
+ await this.sessions.updateOne({ sessionHash }, { $set: { lastActivityAt } });
64
+ }
65
+ async deleteSession(sessionHash) {
66
+ await this.sessions.deleteOne({ sessionHash });
59
67
  }
60
68
  async createIndexes() {
61
69
  await this.mongo.createIndexes(this.users, [
@@ -67,31 +75,26 @@ export class AuthStorage {
67
75
  unique: true
68
76
  }
69
77
  ]);
70
- await this.mongo.createIndexes(this.tempPasswords, [
78
+ await this.mongo.createIndexes(this.sessions, [
71
79
  {
72
- name: 'userId_v0',
73
- key: {
74
- userId: 1
75
- }
80
+ name: 'sessionHash_v1',
81
+ key: { sessionHash: 1 },
82
+ unique: true
76
83
  },
77
84
  {
78
- name: 'ttl_v0',
85
+ name: 'expiresAt_v1',
79
86
  key: {
80
- passwordHash: 1
87
+ expiresAt: 1
81
88
  },
82
- // Temp passwords also have expirity dates, make sure they are syncronized with this expirity index
83
- expireAfterSeconds: 60 * 60 * 24 * 7 // 7 days
89
+ expireAfterSeconds: 0
84
90
  }
85
91
  ]);
86
92
  }
87
93
  get users() {
88
94
  return this.mongo.db.collection('users');
89
95
  }
90
- get tempPasswords() {
91
- return this.mongo.db.collection('temp-passwords');
92
- }
93
- get auth() {
94
- return (headers) => auth(headers, this);
96
+ get sessions() {
97
+ return this.mongo.db.collection('sessions');
95
98
  }
96
99
  }
97
100
  //# sourceMappingURL=storage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/back/auth/storage.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/back/auth/storage.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,10 @@
1
- import { FastifyRequest } from 'fastify';
2
- import { ApiAuthResponseV0 } from '../../shared/api/auth.js';
3
- import { AuthStorage } from './storage.js';
4
- export declare function signup(storage: AuthStorage, name: string, email: string, password: string): Promise<ApiAuthResponseV0>;
5
- export declare function signin(storage: AuthStorage, name: string, password: string): Promise<ApiAuthResponseV0 | null>;
6
- export declare function logout(storage: AuthStorage, userId: string, tempPassword: string): Promise<void>;
7
- export declare function verifyTempPassword(storage: AuthStorage, userId: string, tempPassword: string): Promise<string>;
8
- export declare function auth(headers: FastifyRequest['headers'], storage: AuthStorage): Promise<string>;
1
+ import type { AuthStorage } from './storage.js';
2
+ import type { StorageUser } from './model.js';
3
+ export interface AuthUser {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ }
8
+ export declare function signup(storage: AuthStorage, name: string, email: string, password: string): Promise<AuthUser>;
9
+ export declare function signin(storage: AuthStorage, name: string, password: string): Promise<AuthUser | null>;
10
+ export declare function toAuthUser(user: Pick<StorageUser, '_id' | 'name' | 'email'>): AuthUser;
@@ -1,81 +1,68 @@
1
1
  import { httpErrors } from '@fastify/sensible';
2
- import { randomBytes, scryptSync } from 'crypto';
3
- import { DateTime } from 'luxon';
2
+ import { randomBytes, scrypt as scryptCallback, timingSafeEqual } from 'node:crypto';
4
3
  import { Binary, MongoServerError } from 'mongodb';
5
4
  import { uuidv7 } from 'uuidv7';
6
- const TEMP_PASSWORD_SALT = Buffer.from('cashmony-temp-password-salt', 'utf8');
5
+ const PASSWORD_HASH_VERSION = 1;
6
+ const PASSWORD_HASH_BYTES = 64;
7
+ const LEGACY_SCRYPT_OPTIONS = { N: 1024, r: 8, p: 1, maxmem: 32 * 1024 * 1024 };
8
+ const CURRENT_SCRYPT_OPTIONS = { N: 32_768, r: 8, p: 3, maxmem: 64 * 1024 * 1024 };
9
+ const DUMMY_SALT = Buffer.alloc(64, 0x5a);
7
10
  export async function signup(storage, name, email, password) {
8
11
  const salt = randomBytes(64);
9
- const hash = calcHash(password, salt);
12
+ const hash = await derivePasswordHash(password, salt, CURRENT_SCRYPT_OPTIONS);
10
13
  const id = uuidv7();
11
14
  try {
12
- await storage.createUser(id, name, email, new Binary(salt), new Binary(hash));
15
+ await storage.createUser(id, name, email, new Binary(salt), new Binary(hash), PASSWORD_HASH_VERSION);
13
16
  }
14
17
  catch (e) {
15
18
  if (e instanceof MongoServerError && e.code === 11000) {
16
- // duplicate key error
17
19
  throw httpErrors.conflict();
18
20
  }
19
21
  throw e;
20
22
  }
21
- const tempPassword = await makeTempPassword(storage, id);
22
- return {
23
- id,
24
- name,
25
- email,
26
- tempPassword
27
- };
23
+ return { id, name, email };
28
24
  }
29
25
  export async function signin(storage, name, password) {
30
- const u = await storage.getUser(name);
31
- if (u === null) {
26
+ const user = await storage.getUser(name);
27
+ if (user === null) {
28
+ await derivePasswordHash(password, DUMMY_SALT, CURRENT_SCRYPT_OPTIONS);
32
29
  return null;
33
30
  }
34
- const hash = calcHash(password, u.passwordSalt.value());
35
- if (!hash.equals(u.passwordHash.value())) {
36
- return null;
31
+ if (user.passwordHashVersion === undefined) {
32
+ const legacyHash = await derivePasswordHash(password, user.passwordSalt.value(), LEGACY_SCRYPT_OPTIONS, 512);
33
+ if (!hashMatches(legacyHash, user.passwordHash.value())) {
34
+ await derivePasswordHash(password, DUMMY_SALT, CURRENT_SCRYPT_OPTIONS);
35
+ return null;
36
+ }
37
+ const salt = randomBytes(64);
38
+ const upgradedHash = await derivePasswordHash(password, salt, CURRENT_SCRYPT_OPTIONS);
39
+ await storage.updateLegacyPasswordHash(user._id, user.passwordSalt, user.passwordHash, new Binary(salt), new Binary(upgradedHash));
37
40
  }
38
- const tempPassword = await makeTempPassword(storage, u._id);
39
- await storage.markUserActive(u._id);
40
- return {
41
- id: u._id,
42
- name: u.name,
43
- email: u.email,
44
- tempPassword
45
- };
46
- }
47
- export async function logout(storage, userId, tempPassword) {
48
- const hash = calcHash(tempPassword, TEMP_PASSWORD_SALT);
49
- await storage.deleteTempPassword(userId, new Binary(hash));
50
- }
51
- export async function verifyTempPassword(storage, userId, tempPassword) {
52
- const hash = calcTempPasswordHash(Buffer.from(tempPassword, 'base64'));
53
- const u = await storage.getUserByTempPassword(userId, new Binary(hash));
54
- if (u === null) {
55
- throw httpErrors.forbidden();
41
+ else if (user.passwordHashVersion === PASSWORD_HASH_VERSION) {
42
+ const hash = await derivePasswordHash(password, user.passwordSalt.value(), CURRENT_SCRYPT_OPTIONS);
43
+ if (!hashMatches(hash, user.passwordHash.value()))
44
+ return null;
56
45
  }
57
- await storage.markUserActive(u._id);
58
- return u._id;
59
- }
60
- async function makeTempPassword(storage, userId) {
61
- const password = randomBytes(128);
62
- const passwordHash = calcTempPasswordHash(password);
63
- // Temp passwords also are removed from Mongo by TTL index, make sure expirity dates are syncronised
64
- await storage.pushTempPassword(userId, new Binary(passwordHash), DateTime.utc().plus({ days: 7 }).toJSDate());
65
- return password.toString('base64');
66
- }
67
- export async function auth(headers, storage) {
68
- const authHeader = headers.authorization;
69
- if (authHeader === undefined) {
70
- throw httpErrors.forbidden();
46
+ else {
47
+ return null;
71
48
  }
72
- const [userId, tempPassword] = authHeader.split(':');
73
- return await verifyTempPassword(storage, userId, tempPassword);
49
+ await storage.markUserActive(user._id);
50
+ return toAuthUser(user);
51
+ }
52
+ export function toAuthUser(user) {
53
+ return { id: user._id, name: user.name, email: user.email };
74
54
  }
75
- function calcHash(password, salt) {
76
- return scryptSync(password, salt, 512, { N: 1024 });
55
+ async function derivePasswordHash(password, salt, options, keyLength = PASSWORD_HASH_BYTES) {
56
+ return await new Promise((resolve, reject) => {
57
+ scryptCallback(password, salt, keyLength, options, (error, derivedKey) => {
58
+ if (error !== null)
59
+ reject(error);
60
+ else
61
+ resolve(derivedKey);
62
+ });
63
+ });
77
64
  }
78
- function calcTempPasswordHash(tempPassword) {
79
- return scryptSync(tempPassword, TEMP_PASSWORD_SALT, 512, { N: 1024 });
65
+ function hashMatches(actual, expected) {
66
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
80
67
  }
81
68
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/back/auth/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/back/auth/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Creates a protected endpoint that reports dates from the certificate on disk. */
2
+ export declare function makeCertificateAdmRpc({ certDir, domain }: {
3
+ certDir: string;
4
+ domain: string;
5
+ }): import("../rpc/rpc.js").AdmRpcPluginBuilder<import("../rpc/rpc.js").AdmRpcPluginOptions>;
@@ -0,0 +1,52 @@
1
+ import { X509Certificate } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import z from 'zod';
5
+ import { certFilename } from 'fastify-acme';
6
+ import { admRpc } from '../rpc/rpc.js';
7
+ const CERTIFICATE_RENEWAL_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
8
+ const certificateStatusSchema = z.object({
9
+ domain: z.string(),
10
+ certificateFound: z.boolean(),
11
+ issuedAt: z.string().optional(),
12
+ expiresAt: z.string().optional(),
13
+ renewalAt: z.string().optional(),
14
+ renewalOverdue: z.boolean(),
15
+ expired: z.boolean()
16
+ });
17
+ /** Creates a protected endpoint that reports dates from the certificate on disk. */
18
+ export function makeCertificateAdmRpc({ certDir, domain }) {
19
+ const certificatePath = certFilename(path.resolve(certDir));
20
+ return admRpc('certificate').add({
21
+ name: 'status',
22
+ v: 1,
23
+ bodySchema: z.object({}),
24
+ respSchema: certificateStatusSchema,
25
+ action: async () => {
26
+ let pem;
27
+ try {
28
+ pem = await readFile(certificatePath);
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
32
+ return { domain, certificateFound: false, renewalOverdue: true, expired: false };
33
+ }
34
+ throw error;
35
+ }
36
+ const certificate = new X509Certificate(pem);
37
+ const issuedAt = new Date(certificate.validFrom);
38
+ const expiresAt = new Date(certificate.validTo);
39
+ const renewalAt = new Date(expiresAt.getTime() - CERTIFICATE_RENEWAL_WINDOW_MS);
40
+ return {
41
+ domain,
42
+ certificateFound: true,
43
+ issuedAt: issuedAt.toISOString(),
44
+ expiresAt: expiresAt.toISOString(),
45
+ renewalAt: renewalAt.toISOString(),
46
+ renewalOverdue: Date.now() >= renewalAt.getTime(),
47
+ expired: Date.now() >= expiresAt.getTime()
48
+ };
49
+ }
50
+ });
51
+ }
52
+ //# sourceMappingURL=adm-rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adm-rpc.js","sourceRoot":"","sources":["../../../src/back/certificate/adm-rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,CAAC,MAAM,KAAK,CAAA;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,MAAM,6BAA6B,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAE9D,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACrC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,gBAAgB,EAAE,CAAC,CAAC,OAAO,EAAE;IAC7B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACvB,CAAC,CAAA;AAEF,oFAAoF;AACpF,MAAM,UAAU,qBAAqB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAuC;IAC1F,MAAM,eAAe,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;IAE3D,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC;QAC7B,IAAI,EAAE,QAAQ;QACd,CAAC,EAAE,CAAC;QACJ,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,UAAU,EAAE,uBAAuB;QACnC,MAAM,EAAE,KAAK,IAAI,EAAE;YACf,IAAI,GAAW,CAAA;YACf,IAAI,CAAC;gBACD,GAAG,GAAG,MAAM,QAAQ,CAAC,eAAe,CAAC,CAAA;YACzC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBACvE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;gBACpF,CAAC;gBACD,MAAM,KAAK,CAAA;YACf,CAAC;YAED,MAAM,WAAW,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,CAAA;YAC5C,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;YAChD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;YAC/C,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,6BAA6B,CAAC,CAAA;YAE/E,OAAO;gBACH,MAAM;gBACN,gBAAgB,EAAE,IAAI;gBACtB,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;gBAChC,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE;gBAClC,SAAS,EAAE,SAAS,CAAC,WAAW,EAAE;gBAClC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC,OAAO,EAAE;gBACjD,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC,OAAO,EAAE;aAC7C,CAAA;QACL,CAAC;KACJ,CAAC,CAAA;AACN,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { AdmModule } from '../../client/adm/admin.js';
2
+ import { makeCertificateAdmRpc } from './adm-rpc.js';
3
+ export interface CertificateModuleOptions {
4
+ certDir: string;
5
+ domain: string;
6
+ }
7
+ declare function makeAdm(): AdmModule;
8
+ /** TLS certificate status integrations. */
9
+ export declare const CERTIFICATE_MODULE: {
10
+ makeAdmRpc: typeof makeCertificateAdmRpc;
11
+ makeAdm: typeof makeAdm;
12
+ };
13
+ export {};
@@ -0,0 +1,19 @@
1
+ import { admRpcCall } from '../../client/rpc.js';
2
+ import { CertificateStatus } from '../../client/adm/certificate.js';
3
+ import { makeCertificateAdmRpc } from './adm-rpc.js';
4
+ function makeAdm() {
5
+ return {
6
+ name: 'certificate',
7
+ title: 'Certificate',
8
+ component: CertificateStatus,
9
+ verify: async (auth) => {
10
+ await admRpcCall(auth, 'certificate', 'status', 1, {});
11
+ }
12
+ };
13
+ }
14
+ /** TLS certificate status integrations. */
15
+ export const CERTIFICATE_MODULE = {
16
+ makeAdmRpc: makeCertificateAdmRpc,
17
+ makeAdm
18
+ };
19
+ //# sourceMappingURL=module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.js","sourceRoot":"","sources":["../../../src/back/certificate/module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAA;AAEnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAOpD,SAAS,OAAO;IACZ,OAAO;QACH,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,aAAa;QACpB,SAAS,EAAE,iBAAiB;QAC5B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YACnB,MAAM,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;QAC1D,CAAC;KACJ,CAAA;AACL,CAAC;AAED,2CAA2C;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,UAAU,EAAE,qBAAqB;IACjC,OAAO;CACV,CAAA"}