@isahaq/kosh-server 0.0.0-stage → 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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +21 -2
  3. package/dist/api/client.d.ts +18 -0
  4. package/dist/api/client.d.ts.map +1 -0
  5. package/dist/api/client.js +380 -0
  6. package/dist/api/client.js.map +1 -0
  7. package/dist/api/idempotency.d.ts +23 -0
  8. package/dist/api/idempotency.d.ts.map +1 -0
  9. package/dist/api/idempotency.js +52 -0
  10. package/dist/api/idempotency.js.map +1 -0
  11. package/dist/api/metadata.d.ts +65 -0
  12. package/dist/api/metadata.d.ts.map +1 -0
  13. package/dist/api/metadata.js +88 -0
  14. package/dist/api/metadata.js.map +1 -0
  15. package/dist/api/navigation.d.ts +46 -0
  16. package/dist/api/navigation.d.ts.map +1 -0
  17. package/dist/api/navigation.js +131 -0
  18. package/dist/api/navigation.js.map +1 -0
  19. package/dist/api/openapi.d.ts +22 -0
  20. package/dist/api/openapi.d.ts.map +1 -0
  21. package/dist/api/openapi.js +349 -0
  22. package/dist/api/openapi.js.map +1 -0
  23. package/dist/api/rest.d.ts +28 -0
  24. package/dist/api/rest.d.ts.map +1 -0
  25. package/dist/api/rest.js +435 -0
  26. package/dist/api/rest.js.map +1 -0
  27. package/dist/api/views.d.ts +67 -0
  28. package/dist/api/views.d.ts.map +1 -0
  29. package/dist/api/views.js +307 -0
  30. package/dist/api/views.js.map +1 -0
  31. package/dist/audit.d.ts +21 -0
  32. package/dist/audit.d.ts.map +1 -0
  33. package/dist/audit.js +26 -0
  34. package/dist/audit.js.map +1 -0
  35. package/dist/auth/admins.d.ts +26 -0
  36. package/dist/auth/admins.d.ts.map +1 -0
  37. package/dist/auth/admins.js +70 -0
  38. package/dist/auth/admins.js.map +1 -0
  39. package/dist/auth/base.d.ts +12 -0
  40. package/dist/auth/base.d.ts.map +1 -0
  41. package/dist/auth/base.js +192 -0
  42. package/dist/auth/base.js.map +1 -0
  43. package/dist/auth/crypto.d.ts +20 -0
  44. package/dist/auth/crypto.d.ts.map +1 -0
  45. package/dist/auth/crypto.js +100 -0
  46. package/dist/auth/crypto.js.map +1 -0
  47. package/dist/auth/install.d.ts +24 -0
  48. package/dist/auth/install.d.ts.map +1 -0
  49. package/dist/auth/install.js +18 -0
  50. package/dist/auth/install.js.map +1 -0
  51. package/dist/auth/routes.d.ts +20 -0
  52. package/dist/auth/routes.d.ts.map +1 -0
  53. package/dist/auth/routes.js +56 -0
  54. package/dist/auth/routes.js.map +1 -0
  55. package/dist/auth/service.d.ts +108 -0
  56. package/dist/auth/service.d.ts.map +1 -0
  57. package/dist/auth/service.js +477 -0
  58. package/dist/auth/service.js.map +1 -0
  59. package/dist/errors.d.ts +21 -0
  60. package/dist/errors.d.ts.map +1 -0
  61. package/dist/errors.js +78 -0
  62. package/dist/errors.js.map +1 -0
  63. package/dist/identity.d.ts +49 -0
  64. package/dist/identity.d.ts.map +1 -0
  65. package/dist/identity.js +58 -0
  66. package/dist/identity.js.map +1 -0
  67. package/dist/index.d.ts +26 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +26 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/orm.d.ts +14 -0
  72. package/dist/orm.d.ts.map +1 -0
  73. package/dist/orm.js +12 -0
  74. package/dist/orm.js.map +1 -0
  75. package/dist/registry.d.ts +19 -0
  76. package/dist/registry.d.ts.map +1 -0
  77. package/dist/registry.js +26 -0
  78. package/dist/registry.js.map +1 -0
  79. package/dist/server.d.ts +93 -0
  80. package/dist/server.d.ts.map +1 -0
  81. package/dist/server.js +223 -0
  82. package/dist/server.js.map +1 -0
  83. package/dist/telemetry.d.ts +23 -0
  84. package/dist/telemetry.d.ts.map +1 -0
  85. package/dist/telemetry.js +38 -0
  86. package/dist/telemetry.js.map +1 -0
  87. package/package.json +63 -3
@@ -0,0 +1,477 @@
1
+ import { audit } from '../audit.js';
2
+ import { HttpProblem } from '../errors.js';
3
+ import { keepingAdministrator } from './admins.js';
4
+ import { digest, hashPassword, newTotpSecret, otpauthUri, randomToken, verifyPassword, verifyTotp, } from './crypto.js';
5
+ export const SESSION_COOKIE = 'kosh_session';
6
+ const MIN_PASSWORD = 12;
7
+ const model = (env, name) => env.model(name);
8
+ /** Milliseconds of a datetime value as Shutro returns it (`Date`, or an ISO text), or `null`. */
9
+ export function timeOf(value) {
10
+ if (value === null || value === undefined)
11
+ return null;
12
+ const ms = value instanceof Date ? value.getTime() : Date.parse(String(value));
13
+ return Number.isNaN(ms) ? null : ms;
14
+ }
15
+ const one = (rows) => rows[0];
16
+ /** Loads `keys` (scalar fields) and returns plain rows. */
17
+ async function fetch(records, keys) {
18
+ const set = await records;
19
+ return set.length === 0 ? [] : (await set.load(keys)).read(keys);
20
+ }
21
+ function unauthenticated(type, detail, hint = 'Sign in again.') {
22
+ return new HttpProblem(401, type, 'Not authenticated', detail, hint);
23
+ }
24
+ /** Reads `name` from the Cookie header. */
25
+ export function cookieOf(request, name) {
26
+ for (const part of String(request.headers.cookie ?? '').split(';')) {
27
+ const [k, ...v] = part.trim().split('=');
28
+ if (k === name)
29
+ return decodeURIComponent(v.join('='));
30
+ }
31
+ return undefined;
32
+ }
33
+ const UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
34
+ /** The authentication service of one server. */
35
+ export class AuthService {
36
+ #shutro;
37
+ #options;
38
+ #cache = new Map();
39
+ #dummy = hashPassword(randomToken());
40
+ constructor(shutro, options = {}) {
41
+ this.#shutro = shutro;
42
+ this.#options = {
43
+ secureCookies: options.secureCookies ?? true,
44
+ idleMs: options.idleMs ?? 12 * 3_600_000,
45
+ lifetimeMs: options.lifetimeMs ?? 7 * 86_400_000,
46
+ cacheMs: options.cacheMs ?? 5_000,
47
+ };
48
+ }
49
+ #system(reason, fn) {
50
+ return this.#shutro().bootstrap({ reason: `auth: ${reason}` }, fn);
51
+ }
52
+ /** The `Set-Cookie` value for a session token (empty token: clears it). */
53
+ cookie(token) {
54
+ const parts = [
55
+ `${SESSION_COOKIE}=${token ?? ''}`,
56
+ 'Path=/',
57
+ 'HttpOnly',
58
+ 'SameSite=Lax',
59
+ `Max-Age=${token === null ? 0 : Math.floor(this.#options.lifetimeMs / 1000)}`,
60
+ ];
61
+ if (this.#options.secureCookies)
62
+ parts.push('Secure');
63
+ return parts.join('; ');
64
+ }
65
+ /** Hashes and sets a user's password. @throws {HttpProblem} 422 for a too-short password */
66
+ async setPassword(env, user, password) {
67
+ if (password.length < MIN_PASSWORD)
68
+ throw new HttpProblem(422, 'weak-password', 'Weak password', `Passwords have at least ${MIN_PASSWORD} characters.`);
69
+ const hash = await hashPassword(password);
70
+ const existing = await model(env, 'auth.credential').search({ where: { user } });
71
+ if (existing.length === 0)
72
+ await model(env, 'auth.credential').create([{ user, passwordHash: hash }]);
73
+ else
74
+ await existing.write({ passwordHash: hash });
75
+ }
76
+ /** Creates a user with a password and group assignments (installation, tests, admin tools). */
77
+ async createUser(input) {
78
+ return this.#system(`create user ${input.login}`, async (env) => {
79
+ const created = await model(env, 'res.users').create([
80
+ {
81
+ login: input.login,
82
+ name: input.name,
83
+ ...(input.company === undefined ? {} : { company: input.company }),
84
+ ...(input.companies === undefined ? {} : { companies: { set: [...input.companies] } }),
85
+ },
86
+ ]);
87
+ const user = created.ids[0];
88
+ await this.setPassword(env, user, input.password);
89
+ const assignments = (input.groups ?? []).map((g) => typeof g === 'string' ? { user, group: g } : { user, group: g.group, company: g.company });
90
+ if (assignments.length > 0)
91
+ await model(env, 'auth.user_group').create(assignments);
92
+ return user;
93
+ });
94
+ }
95
+ /** Signs in: a new session (pending second factor when 2FA is on). @throws {HttpProblem} 401 */
96
+ async login(request, login, password) {
97
+ const outcome = await this.#system(`login ${login}`, async (env) => {
98
+ const user = one(await fetch(model(env, 'res.users').search({ where: { login } }), ['login']));
99
+ const credential = user === undefined
100
+ ? undefined
101
+ : one(await fetch(model(env, 'auth.credential').search({ where: { user: user['id'] } }), [
102
+ 'passwordHash',
103
+ 'totpEnabled',
104
+ ]));
105
+ const hash = typeof credential?.['passwordHash'] === 'string'
106
+ ? credential['passwordHash']
107
+ : await this.#dummy;
108
+ const ok = (await verifyPassword(password, hash)) && user !== undefined && credential !== undefined;
109
+ if (!ok)
110
+ return null;
111
+ const token = randomToken();
112
+ const csrfToken = randomToken(24);
113
+ const now = Date.now();
114
+ const twoFactorRequired = credential?.['totpEnabled'] === true;
115
+ await model(env, 'auth.session').create([
116
+ {
117
+ tokenHash: digest(token),
118
+ csrfHash: digest(csrfToken),
119
+ user: user['id'],
120
+ twoFactorVerified: !twoFactorRequired,
121
+ expiresAt: new Date(now + this.#options.lifetimeMs),
122
+ lastSeenAt: new Date(now),
123
+ ip: request.ip,
124
+ userAgent: String(request.headers['user-agent'] ?? '').slice(0, 300),
125
+ },
126
+ ]);
127
+ await audit(env, {
128
+ kind: 'auth.login',
129
+ summary: `${login} signed in${twoFactorRequired ? ' (second factor pending)' : ''}.`,
130
+ actor: user['id'],
131
+ company: null,
132
+ ip: request.ip,
133
+ });
134
+ return { token, csrfToken, twoFactorRequired };
135
+ });
136
+ if (outcome === null) {
137
+ // The failed attempt is audited in its own scope (the login scope stored nothing).
138
+ await this.#system(`failed login ${login}`, (env) => audit(env, {
139
+ kind: 'auth.login_failed',
140
+ summary: `Failed sign-in for ${login.slice(0, 100)}.`,
141
+ details: { login: login.slice(0, 100) },
142
+ ip: request.ip,
143
+ }));
144
+ throw unauthenticated('invalid-credentials', 'The login or password is wrong.', 'Check them and try again.');
145
+ }
146
+ return outcome;
147
+ }
148
+ /** The request's session, or `null`. Cookie sessions need the CSRF header on unsafe methods. */
149
+ async session(request) {
150
+ const header = String(request.headers.authorization ?? '');
151
+ const bearer = /^Bearer\s+(\S+)$/i.exec(header)?.[1];
152
+ const token = bearer ?? cookieOf(request, SESSION_COOKIE);
153
+ if (token === undefined || token === '')
154
+ return null;
155
+ const via = bearer === undefined ? 'cookie' : 'bearer';
156
+ const tokenHash = digest(token);
157
+ const cached = this.#cache.get(tokenHash);
158
+ let session = cached !== undefined && Date.now() - cached.at < this.#options.cacheMs
159
+ ? cached.session
160
+ : undefined;
161
+ if (session === undefined) {
162
+ const found = await this.#system('session', async (env) => {
163
+ const rows = await fetch(model(env, 'auth.session').search({ where: { tokenHash, revoked: false } }), ['user', 'twoFactorVerified', 'secondFactor', 'expiresAt', 'lastSeenAt', 'csrfHash']);
164
+ const row = one(rows);
165
+ if (row === undefined)
166
+ return null;
167
+ const now = Date.now();
168
+ const lastSeen = new Date(String(row['lastSeenAt'])).getTime();
169
+ if (new Date(String(row['expiresAt'])).getTime() <= now ||
170
+ lastSeen + this.#options.idleMs <= now)
171
+ return null;
172
+ if (now - lastSeen > 5 * 60_000)
173
+ await model(env, 'auth.session')
174
+ .browse([row['id']])
175
+ .write({ lastSeenAt: new Date(now) });
176
+ const active = (await model(env, 'res.users').search({ where: { id: row['user'] } })).length > 0; // archived users cannot sign in
177
+ return active ? row : null;
178
+ });
179
+ if (found === null) {
180
+ this.#cache.delete(tokenHash);
181
+ return null;
182
+ }
183
+ session = {
184
+ id: found['id'],
185
+ user: found['user'],
186
+ twoFactorRequired: found['twoFactorVerified'] !== true,
187
+ secondFactor: found['secondFactor'] === true,
188
+ via,
189
+ csrfHash: String(found['csrfHash']),
190
+ };
191
+ this.#cache.set(tokenHash, { at: Date.now(), session });
192
+ }
193
+ if (via === 'cookie' && UNSAFE.has(request.method)) {
194
+ const csrf = request.headers['x-csrf-token'];
195
+ if (typeof csrf !== 'string' || digest(csrf) !== session.csrfHash)
196
+ throw new HttpProblem(403, 'csrf', 'CSRF check failed', 'Cookie-authenticated requests that change data need the X-CSRF-Token header.', 'Send the csrfToken returned at sign-in.');
197
+ }
198
+ return {
199
+ id: session.id,
200
+ user: session.user,
201
+ twoFactorRequired: session.twoFactorRequired,
202
+ secondFactor: session.secondFactor,
203
+ via: session.via,
204
+ };
205
+ }
206
+ /** The verified session of a request. @throws {HttpProblem} 401 without one, or before the second factor */
207
+ async requireSession(request, options = {}) {
208
+ const session = await this.session(request);
209
+ if (session === null)
210
+ throw unauthenticated('unauthenticated', 'This route needs a signed-in user.', 'Sign in first.');
211
+ if (session.twoFactorRequired && options.allowPending !== true)
212
+ throw unauthenticated('two-factor-required', 'Enter the code from your authenticator app first.', 'POST /api/v1/auth/2fa/verify.');
213
+ return session;
214
+ }
215
+ /** Authenticator for the server (RFC 0002 §6.2): the session's user, company from `X-Company`, groups for it. */
216
+ async authenticate(request) {
217
+ const session = await this.session(request);
218
+ if (session === null)
219
+ return null;
220
+ if (session.twoFactorRequired)
221
+ throw unauthenticated('two-factor-required', 'Enter the code from your authenticator app first.', 'POST /api/v1/auth/2fa/verify.');
222
+ const info = await this.#identityOf(session.user);
223
+ if (info === null)
224
+ return null;
225
+ const asked = Number(request.headers['x-company']);
226
+ const company = Number.isSafeInteger(asked) && info.companies.includes(asked)
227
+ ? asked
228
+ : (info.company ?? info.companies[0]);
229
+ if (company === undefined)
230
+ throw new HttpProblem(403, 'no-company', 'No company', 'This user is not allowed in any company.', 'An administrator assigns the companies of the user.');
231
+ // Mandatory 2FA for administrators (RFC 0003 §5.2): their administrator groups count only in sessions that
232
+ // entered a second factor, while 2FA is on. The user keeps every other group.
233
+ const elevated = session.secondFactor && info.twoFactorEnabled;
234
+ const groups = info.assignments
235
+ .filter((a) => a.company === null || a.company === company)
236
+ .map((a) => a.group)
237
+ .filter((g) => elevated || !info.administratorGroups.has(g));
238
+ const now = Date.now();
239
+ const grants = info.grants
240
+ .filter((g) => (g.company === null || g.company === company) &&
241
+ (g.expiresAt === null || g.expiresAt > now))
242
+ .map((g) => g.grant);
243
+ return {
244
+ user: session.user,
245
+ company,
246
+ companies: info.companies,
247
+ groups: [...new Set(groups)],
248
+ grants: [...new Set(grants)],
249
+ lang: info.lang,
250
+ tz: info.tz,
251
+ };
252
+ }
253
+ /** Identity loader for Shutro (`createShutro({ identity })`): the user is active; companies and groups from the database. */
254
+ async verify(uid) {
255
+ const info = await this.#identityOf(uid);
256
+ if (info === null)
257
+ return null;
258
+ const now = Date.now();
259
+ return {
260
+ companies: info.companies,
261
+ groups: [...new Set(info.assignments.map((a) => a.group))],
262
+ grants: [
263
+ ...new Set(info.grants.filter((g) => g.expiresAt === null || g.expiresAt > now).map((g) => g.grant)),
264
+ ],
265
+ };
266
+ }
267
+ async #identityOf(uid) {
268
+ return this.#system('identity', async (env) => {
269
+ const users = await model(env, 'res.users').search({ where: { id: uid } });
270
+ if (users.length === 0)
271
+ return null; // unknown or archived
272
+ const loaded = await users.load(['company', 'lang', 'tz']);
273
+ const withCompanies = await loaded.load({ companies: true });
274
+ const [row] = withCompanies.read(['company', 'companies', 'lang', 'tz']);
275
+ const company = typeof row?.['company'] === 'number' ? row['company'] : null;
276
+ const companies = [
277
+ ...new Set([
278
+ ...(row?.['companies'] ?? []),
279
+ ...(company === null ? [] : [company]),
280
+ ]),
281
+ ];
282
+ const assignments = (await fetch(model(env, 'auth.user_group').search({ where: { user: uid } }), [
283
+ 'group',
284
+ 'company',
285
+ ])).map((a) => ({
286
+ group: String(a['group']),
287
+ company: typeof a['company'] === 'number' ? a['company'] : null,
288
+ }));
289
+ const grants = (await fetch(model(env, 'auth.user_grant').search({ where: { user: uid } }), [
290
+ 'grant',
291
+ 'company',
292
+ 'expiresAt',
293
+ ])).map((g) => ({
294
+ grant: String(g['grant']),
295
+ company: typeof g['company'] === 'number' ? g['company'] : null,
296
+ expiresAt: timeOf(g['expiresAt']),
297
+ }));
298
+ const [credential] = await fetch(model(env, 'auth.credential').search({ where: { user: uid } }), ['totpEnabled']);
299
+ return {
300
+ company,
301
+ companies,
302
+ lang: String(row?.['lang'] ?? 'en_US'),
303
+ tz: String(row?.['tz'] ?? 'UTC'),
304
+ assignments,
305
+ grants,
306
+ administratorGroups: env.access.administratorGroups(),
307
+ twoFactorEnabled: credential?.['totpEnabled'] === true,
308
+ };
309
+ });
310
+ }
311
+ /** The signed-in user's state. */
312
+ async me(session) {
313
+ return this.#system('me', async (env) => {
314
+ const [user] = (await (await model(env, 'res.users').browse([session.user]).load(['login', 'name', 'company'])).load({ companies: true })).read(['login', 'name', 'company', 'companies']);
315
+ const [credential] = await fetch(model(env, 'auth.credential').search({ where: { user: session.user } }), ['totpEnabled']);
316
+ const administrator = await this.#holdsAdministrator(env, session.user);
317
+ const twoFactorEnabled = credential?.['totpEnabled'] === true;
318
+ return {
319
+ id: session.user,
320
+ login: String(user?.['login']),
321
+ name: String(user?.['name']),
322
+ company: typeof user?.['company'] === 'number' ? user['company'] : null,
323
+ companies: user?.['companies'] ?? [],
324
+ twoFactorEnabled,
325
+ administrator,
326
+ administratorNeedsSecondFactor: administrator && !(session.secondFactor && twoFactorEnabled),
327
+ };
328
+ });
329
+ }
330
+ /** Revokes a session. */
331
+ async logout(session) {
332
+ await this.#system('logout', async (env) => {
333
+ await model(env, 'auth.session').browse([session.id]).write({ revoked: true });
334
+ await audit(env, { kind: 'auth.logout', summary: 'Signed out.', actor: session.user });
335
+ });
336
+ this.#forgetUser(session.user, session.id);
337
+ }
338
+ #forgetUser(user, onlySession) {
339
+ for (const [hash, entry] of this.#cache)
340
+ if (entry.session.user === user &&
341
+ (onlySession === undefined || entry.session.id === onlySession))
342
+ this.#cache.delete(hash);
343
+ }
344
+ /** Starts 2FA enrolment: a new secret, not active until confirmed with a code. */
345
+ async setupTwoFactor(session) {
346
+ return this.#system('2fa setup', async (env) => {
347
+ const secret = newTotpSecret();
348
+ const [user] = (await model(env, 'res.users').browse([session.user]).load(['login'])).read([
349
+ 'login',
350
+ ]);
351
+ const credentials = await model(env, 'auth.credential').search({
352
+ where: { user: session.user },
353
+ });
354
+ const [current] = await fetch(credentials, ['totpEnabled']);
355
+ if (current?.['totpEnabled'] === true)
356
+ throw new HttpProblem(409, 'two-factor-enabled', 'Already enabled', 'Two-factor authentication is already on.', 'Disable it first to enrol a new device.');
357
+ if (credentials.length === 0)
358
+ await model(env, 'auth.credential').create([{ user: session.user, totpSecret: secret }]);
359
+ else
360
+ await credentials.write({ totpSecret: secret });
361
+ return { secret, otpauthUri: otpauthUri(secret, String(user?.['login'])) };
362
+ });
363
+ }
364
+ /** Confirms enrolment with a code; returns ten recovery codes (shown once, stored as digests). */
365
+ async enableTwoFactor(session, code) {
366
+ return this.#system('2fa enable', async (env) => {
367
+ const credentials = await model(env, 'auth.credential').search({
368
+ where: { user: session.user },
369
+ });
370
+ const [row] = await fetch(credentials, ['totpSecret', 'totpEnabled']);
371
+ if (typeof row?.['totpSecret'] !== 'string' || row['totpEnabled'] === true)
372
+ throw new HttpProblem(409, 'two-factor-not-pending', 'Nothing to confirm', 'Start with POST /api/v1/auth/2fa/setup.');
373
+ if (!verifyTotp(row['totpSecret'], code))
374
+ throw new HttpProblem(422, 'invalid-code', 'Invalid code', 'The code does not match.', 'Use the current code of the authenticator app.');
375
+ const recoveryCodes = Array.from({ length: 10 }, () => randomToken(9));
376
+ await credentials.write({ totpEnabled: true, recoveryCodes: recoveryCodes.map(digest) });
377
+ // The code just entered is this session's second factor.
378
+ await model(env, 'auth.session').browse([session.id]).write({ secondFactor: true });
379
+ await audit(env, {
380
+ kind: 'auth.2fa_enabled',
381
+ summary: 'Two-factor authentication enabled.',
382
+ actor: session.user,
383
+ });
384
+ await this.#auditAdministratorSession(env, session.user);
385
+ return { recoveryCodes };
386
+ });
387
+ }
388
+ /** Completes a pending sign-in with a TOTP or recovery code (a recovery code works once). */
389
+ async verifyTwoFactor(session, code) {
390
+ await this.#system('2fa verify', async (env) => {
391
+ const credentials = await model(env, 'auth.credential').search({
392
+ where: { user: session.user },
393
+ });
394
+ const [row] = await fetch(credentials, ['totpSecret', 'totpEnabled', 'recoveryCodes']);
395
+ const secret = row?.['totpSecret'];
396
+ if (row?.['totpEnabled'] !== true || typeof secret !== 'string')
397
+ throw new HttpProblem(409, 'two-factor-off', 'Two-factor authentication is off', 'This account does not use a second factor.');
398
+ const recovery = row['recoveryCodes'] ?? [];
399
+ const used = recovery.indexOf(digest(code.trim()));
400
+ if (!verifyTotp(secret, code.trim()) && used < 0)
401
+ throw new HttpProblem(422, 'invalid-code', 'Invalid code', 'The code does not match.', 'Use the current code of the authenticator app, or a recovery code.');
402
+ if (used >= 0)
403
+ await credentials.write({ recoveryCodes: recovery.filter((_, i) => i !== used) });
404
+ await model(env, 'auth.session')
405
+ .browse([session.id])
406
+ .write({ twoFactorVerified: true, secondFactor: true });
407
+ await audit(env, {
408
+ kind: 'auth.2fa_verified',
409
+ summary: used >= 0
410
+ ? 'Second factor: a recovery code (now used up).'
411
+ : 'Second factor: authenticator code.',
412
+ actor: session.user,
413
+ });
414
+ await this.#auditAdministratorSession(env, session.user);
415
+ });
416
+ this.#forgetUser(session.user, session.id);
417
+ }
418
+ async #holdsAdministrator(env, user) {
419
+ const codes = [...env.access.administratorGroups()];
420
+ return (codes.length > 0 &&
421
+ (await model(env, 'auth.user_group').search({ where: { user, group: { in: codes } } }))
422
+ .length > 0);
423
+ }
424
+ /** Administrator sessions are audited when they start (when the second factor makes the user an administrator). */
425
+ async #auditAdministratorSession(env, user) {
426
+ if (await this.#holdsAdministrator(env, user))
427
+ await audit(env, {
428
+ kind: 'admin.session',
429
+ summary: 'Administrator session started.',
430
+ actor: user,
431
+ });
432
+ }
433
+ /** Turns 2FA off (password and a current code required). */
434
+ async disableTwoFactor(session, password, code) {
435
+ await this.#system('2fa disable', async (env) => {
436
+ const credentials = await model(env, 'auth.credential').search({
437
+ where: { user: session.user },
438
+ });
439
+ const [row] = await fetch(credentials, ['passwordHash', 'totpSecret', 'totpEnabled']);
440
+ const ok = typeof row?.['passwordHash'] === 'string' &&
441
+ (await verifyPassword(password, row['passwordHash'])) &&
442
+ typeof row['totpSecret'] === 'string' &&
443
+ verifyTotp(row['totpSecret'], code);
444
+ if (!ok)
445
+ throw new HttpProblem(422, 'invalid-credentials', 'Invalid credentials', 'The password or code is wrong.');
446
+ // Administrators need 2FA: turning it off for the last one is refused (credentials change only here).
447
+ await keepingAdministrator(env, () => credentials.write({ totpEnabled: false, totpSecret: null, recoveryCodes: null }));
448
+ await audit(env, {
449
+ kind: 'auth.2fa_disabled',
450
+ summary: 'Two-factor authentication disabled.',
451
+ actor: session.user,
452
+ });
453
+ });
454
+ this.#forgetUser(session.user);
455
+ }
456
+ /** Changes the password; every other session of the user is revoked. */
457
+ async changePassword(session, current, next) {
458
+ await this.#system('password change', async (env) => {
459
+ const [row] = await fetch(model(env, 'auth.credential').search({ where: { user: session.user } }), ['passwordHash']);
460
+ if (typeof row?.['passwordHash'] !== 'string' ||
461
+ !(await verifyPassword(current, row['passwordHash'])))
462
+ throw new HttpProblem(422, 'invalid-credentials', 'Invalid credentials', 'The current password is wrong.');
463
+ await this.setPassword(env, session.user, next);
464
+ const others = (await model(env, 'auth.session').search({ where: { user: session.user, revoked: false } })).ids.filter((id) => id !== session.id);
465
+ if (others.length > 0)
466
+ await model(env, 'auth.session').browse(others).write({ revoked: true });
467
+ await audit(env, {
468
+ kind: 'auth.password_changed',
469
+ summary: `Password changed; ${others.length} other session(s) revoked.`,
470
+ actor: session.user,
471
+ });
472
+ });
473
+ this.#forgetUser(session.user);
474
+ this.#cache.clear(); // other sessions of this user, cached by token
475
+ }
476
+ }
477
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/auth/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /** A server set up wrongly (modules, options). The message says what and how to fix it. */
2
+ export declare class ServerConfigError extends Error {
3
+ readonly hint: string;
4
+ constructor(problem: string, hint: string);
5
+ }
6
+ /** An RFC 9457 problem document. */
7
+ export interface Problem {
8
+ readonly type: string;
9
+ readonly title: string;
10
+ readonly status: number;
11
+ readonly detail: string;
12
+ readonly hint?: string;
13
+ }
14
+ /** A request the server refuses before it reaches the ORM (authentication, headers). */
15
+ export declare class HttpProblem extends Error {
16
+ readonly problem: Problem;
17
+ constructor(status: number, type: string, title: string, detail: string, hint?: string);
18
+ }
19
+ /** The problem document for any error; unknown errors are 500 without internals (they are logged instead). */
20
+ export declare function problemOf(error: unknown): Problem;
21
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAMA,2FAA2F;AAC3F,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAIxC;CACF;AAED,oCAAoC;AACpC,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wFAAwF;AACxF,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,YAAY,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAUrF;CACF;AAiBD,8GAA8G;AAC9G,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAiCjD"}
package/dist/errors.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Server errors and their HTTP form: RFC 9457 problem details with stable `type` codes (RFC 0002 §6.4). Shutro's typed
3
+ * errors map to statuses here; the full mapping grows with the generated endpoints (step 3).
4
+ */
5
+ import { orm } from './orm.js';
6
+ /** A server set up wrongly (modules, options). The message says what and how to fix it. */
7
+ export class ServerConfigError extends Error {
8
+ hint;
9
+ constructor(problem, hint) {
10
+ super(`${problem} ${hint}`);
11
+ this.name = 'ServerConfigError';
12
+ this.hint = hint;
13
+ }
14
+ }
15
+ /** A request the server refuses before it reaches the ORM (authentication, headers). */
16
+ export class HttpProblem extends Error {
17
+ problem;
18
+ constructor(status, type, title, detail, hint) {
19
+ super(detail);
20
+ this.name = 'HttpProblem';
21
+ this.problem = {
22
+ type: `https://kosh.dev/problems/${type}`,
23
+ title,
24
+ status,
25
+ detail,
26
+ ...(hint === undefined ? {} : { hint }),
27
+ };
28
+ }
29
+ }
30
+ const SHUTRO_STATUS = {
31
+ ACCESS: 403,
32
+ MISSING_RECORD: 404,
33
+ STALE_RECORD: 412,
34
+ VALIDATION: 422,
35
+ CONSTRAINT: 422,
36
+ CONSTRAINT_VIOLATION: 422,
37
+ INVALID_DOMAIN: 400,
38
+ INVALID_IDENTITY: 400,
39
+ COMPANY_NOT_ALLOWED: 403,
40
+ CONCURRENT_UPDATE: 409,
41
+ INVALID_TRANSITION: 409,
42
+ TRANSITION_GUARD: 409,
43
+ };
44
+ /** The problem document for any error; unknown errors are 500 without internals (they are logged instead). */
45
+ export function problemOf(error) {
46
+ if (error instanceof HttpProblem)
47
+ return error.problem;
48
+ if (error instanceof orm.ShutroError) {
49
+ const status = SHUTRO_STATUS[error.code] ?? 500;
50
+ if (status !== 500)
51
+ return {
52
+ type: `https://kosh.dev/problems/${error.code.toLowerCase().replaceAll('_', '-')}`,
53
+ title: error.name,
54
+ status,
55
+ detail: error.message,
56
+ hint: error.hint,
57
+ };
58
+ }
59
+ const fastify = error;
60
+ if (typeof fastify.statusCode === 'number' &&
61
+ fastify.statusCode >= 400 &&
62
+ fastify.statusCode < 500)
63
+ return {
64
+ type: `https://kosh.dev/problems/${String(fastify.code ?? 'bad-request')
65
+ .toLowerCase()
66
+ .replaceAll('_', '-')}`,
67
+ title: 'Bad request',
68
+ status: fastify.statusCode,
69
+ detail: String(fastify.message ?? 'The request is invalid.'),
70
+ };
71
+ return {
72
+ type: 'https://kosh.dev/problems/internal',
73
+ title: 'Internal error',
74
+ status: 500,
75
+ detail: 'The server failed to handle the request; the error is logged with the request id.',
76
+ };
77
+ }
78
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,2FAA2F;AAC3F,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAS;IACtB,YAAY,OAAe,EAAE,IAAY;QACvC,KAAK,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAWD,wFAAwF;AACxF,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,OAAO,CAAU;IAC1B,YAAY,MAAc,EAAE,IAAY,EAAE,KAAa,EAAE,MAAc,EAAE,IAAa;QACpF,KAAK,CAAC,MAAM,CAAC,CAAC;QACd,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG;YACb,IAAI,EAAE,6BAA6B,IAAI,EAAE;YACzC,KAAK;YACL,MAAM;YACN,MAAM;YACN,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;SACxC,CAAC;IACJ,CAAC;CACF;AAED,MAAM,aAAa,GAAqC;IACtD,MAAM,EAAE,GAAG;IACX,cAAc,EAAE,GAAG;IACnB,YAAY,EAAE,GAAG;IACjB,UAAU,EAAE,GAAG;IACf,UAAU,EAAE,GAAG;IACf,oBAAoB,EAAE,GAAG;IACzB,cAAc,EAAE,GAAG;IACnB,gBAAgB,EAAE,GAAG;IACrB,mBAAmB,EAAE,GAAG;IACxB,iBAAiB,EAAE,GAAG;IACtB,kBAAkB,EAAE,GAAG;IACvB,gBAAgB,EAAE,GAAG;CACtB,CAAC;AAEF,8GAA8G;AAC9G,MAAM,UAAU,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,YAAY,WAAW;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACvD,IAAI,KAAK,YAAY,GAAG,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;QAChD,IAAI,MAAM,KAAK,GAAG;YAChB,OAAO;gBACL,IAAI,EAAE,6BAA6B,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;gBAClF,KAAK,EAAE,KAAK,CAAC,IAAI;gBACjB,MAAM;gBACN,MAAM,EAAE,KAAK,CAAC,OAAO;gBACrB,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB,CAAC;IACN,CAAC;IACD,MAAM,OAAO,GAAG,KAAoE,CAAC;IACrF,IACE,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;QACtC,OAAO,CAAC,UAAU,IAAI,GAAG;QACzB,OAAO,CAAC,UAAU,GAAG,GAAG;QAExB,OAAO;YACL,IAAI,EAAE,6BAA6B,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,aAAa,CAAC;iBACrE,WAAW,EAAE;iBACb,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE;YACzB,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,OAAO,CAAC,UAAU;YAC1B,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,yBAAyB,CAAC;SAC7D,CAAC;IACJ,OAAO;QACL,IAAI,EAAE,oCAAoC;QAC1C,KAAK,EAAE,gBAAgB;QACvB,MAAM,EAAE,GAAG;QACX,MAAM,EAAE,mFAAmF;KAC5F,CAAC;AACJ,CAAC"}