@byline/admin 2.3.3 → 2.4.1

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 (177) hide show
  1. package/dist/abilities.js +5 -24
  2. package/dist/index.js +8 -30
  3. package/dist/lib/assert-admin-actor.js +13 -74
  4. package/dist/lib/create-command.js +6 -16
  5. package/dist/modules/admin-account/commands.js +35 -24
  6. package/dist/modules/admin-account/components/change-password.d.ts +8 -0
  7. package/dist/modules/admin-account/components/change-password.js +192 -0
  8. package/dist/modules/admin-account/components/change-password.module.js +8 -0
  9. package/dist/modules/admin-account/components/change-password_module.css +27 -0
  10. package/dist/modules/admin-account/components/container.d.ts +29 -0
  11. package/dist/modules/admin-account/components/container.js +298 -0
  12. package/dist/modules/admin-account/components/container.module.js +28 -0
  13. package/dist/modules/admin-account/components/container_module.css +106 -0
  14. package/dist/modules/admin-account/components/update.d.ts +8 -0
  15. package/dist/modules/admin-account/components/update.js +207 -0
  16. package/dist/modules/admin-account/components/update.module.js +8 -0
  17. package/dist/modules/admin-account/components/update_module.css +27 -0
  18. package/dist/modules/admin-account/errors.js +14 -45
  19. package/dist/modules/admin-account/index.js +4 -34
  20. package/dist/modules/admin-account/schemas.js +25 -59
  21. package/dist/modules/admin-account/service.js +56 -61
  22. package/dist/modules/admin-permissions/abilities.js +6 -24
  23. package/dist/modules/admin-permissions/commands.js +42 -28
  24. package/dist/modules/admin-permissions/components/inspector.d.ts +4 -0
  25. package/dist/modules/admin-permissions/components/inspector.js +284 -0
  26. package/dist/modules/admin-permissions/components/inspector.module.js +56 -0
  27. package/dist/modules/admin-permissions/components/inspector_module.css +238 -0
  28. package/dist/modules/admin-permissions/dto.js +3 -16
  29. package/dist/modules/admin-permissions/errors.js +14 -27
  30. package/dist/modules/admin-permissions/index.js +6 -26
  31. package/dist/modules/admin-permissions/repository.js +1 -8
  32. package/dist/modules/admin-permissions/schemas.js +33 -70
  33. package/dist/modules/admin-permissions/service.js +88 -92
  34. package/dist/modules/admin-roles/abilities.js +8 -30
  35. package/dist/modules/admin-roles/commands.js +89 -55
  36. package/dist/modules/admin-roles/components/create.d.ts +7 -0
  37. package/dist/modules/admin-roles/components/create.js +177 -0
  38. package/dist/modules/admin-roles/components/create.module.js +8 -0
  39. package/dist/modules/admin-roles/components/create_module.css +27 -0
  40. package/dist/modules/admin-roles/components/permissions.d.ts +10 -0
  41. package/dist/modules/admin-roles/components/permissions.js +303 -0
  42. package/dist/modules/admin-roles/components/permissions.module.js +44 -0
  43. package/dist/modules/admin-roles/components/permissions_module.css +192 -0
  44. package/dist/modules/admin-roles/components/update.d.ts +8 -0
  45. package/dist/modules/admin-roles/components/update.js +166 -0
  46. package/dist/modules/admin-roles/components/update.module.js +8 -0
  47. package/dist/modules/admin-roles/components/update_module.css +27 -0
  48. package/dist/modules/admin-roles/dto.js +3 -16
  49. package/dist/modules/admin-roles/errors.js +16 -40
  50. package/dist/modules/admin-roles/index.js +6 -26
  51. package/dist/modules/admin-roles/repository.js +1 -8
  52. package/dist/modules/admin-roles/schemas.js +41 -71
  53. package/dist/modules/admin-roles/service.js +79 -82
  54. package/dist/modules/admin-users/abilities.js +9 -38
  55. package/dist/modules/admin-users/commands.js +92 -50
  56. package/dist/modules/admin-users/components/create.d.ts +8 -0
  57. package/dist/modules/admin-users/components/create.js +268 -0
  58. package/dist/modules/admin-users/components/create.module.js +10 -0
  59. package/dist/modules/admin-users/components/create_module.css +45 -0
  60. package/dist/modules/admin-users/components/roles.d.ts +11 -0
  61. package/dist/modules/admin-users/components/roles.js +148 -0
  62. package/dist/modules/admin-users/components/roles.module.js +18 -0
  63. package/dist/modules/admin-users/components/roles_module.css +75 -0
  64. package/dist/modules/admin-users/components/set-password.d.ts +8 -0
  65. package/dist/modules/admin-users/components/set-password.js +170 -0
  66. package/dist/modules/admin-users/components/set-password.module.js +9 -0
  67. package/dist/modules/admin-users/components/set-password_module.css +31 -0
  68. package/dist/modules/admin-users/components/update.d.ts +8 -0
  69. package/dist/modules/admin-users/components/update.js +254 -0
  70. package/dist/modules/admin-users/components/update.module.js +9 -0
  71. package/dist/modules/admin-users/components/update_module.css +34 -0
  72. package/dist/modules/admin-users/dto.js +3 -18
  73. package/dist/modules/admin-users/errors.js +17 -43
  74. package/dist/modules/admin-users/index.js +7 -27
  75. package/dist/modules/admin-users/repository.js +1 -8
  76. package/dist/modules/admin-users/schemas.js +44 -75
  77. package/dist/modules/admin-users/seed-super-admin.js +9 -34
  78. package/dist/modules/admin-users/service.js +76 -91
  79. package/dist/modules/auth/components/sign-in-form.d.ts +12 -0
  80. package/dist/modules/auth/components/sign-in-form.js +115 -0
  81. package/dist/modules/auth/components/sign-in-form.module.js +12 -0
  82. package/dist/modules/auth/components/sign-in-form_module.css +41 -0
  83. package/dist/modules/auth/index.js +3 -24
  84. package/dist/modules/auth/jwt-session-provider.js +179 -149
  85. package/dist/modules/auth/password.js +11 -53
  86. package/dist/modules/auth/phc.js +21 -54
  87. package/dist/modules/auth/refresh-tokens-repository.js +1 -8
  88. package/dist/modules/auth/resolve-actor.js +6 -28
  89. package/dist/services/admin-services-context.d.ts +16 -0
  90. package/dist/services/admin-services-context.js +13 -0
  91. package/dist/services/admin-services-types.d.ts +129 -0
  92. package/dist/services/admin-services-types.js +1 -0
  93. package/dist/store.js +1 -8
  94. package/dist/vendor/noble-argon2/_blake.js +277 -45
  95. package/dist/vendor/noble-argon2/_md.js +81 -136
  96. package/dist/vendor/noble-argon2/_u64.js +65 -67
  97. package/dist/vendor/noble-argon2/argon2.js +181 -342
  98. package/dist/vendor/noble-argon2/blake2.js +252 -327
  99. package/dist/vendor/noble-argon2/utils.js +110 -490
  100. package/dist/vendor/noble-argon2/utils.js.LICENSE.txt +1 -0
  101. package/package.json +89 -10
  102. package/src/abilities.ts +32 -0
  103. package/src/declarations.d.ts +4 -0
  104. package/src/index.ts +39 -0
  105. package/src/lib/assert-admin-actor.ts +90 -0
  106. package/src/lib/create-command.ts +109 -0
  107. package/src/modules/admin-account/commands.ts +76 -0
  108. package/src/modules/admin-account/components/change-password.module.css +40 -0
  109. package/src/modules/admin-account/components/change-password.tsx +232 -0
  110. package/src/modules/admin-account/components/container.module.css +158 -0
  111. package/src/modules/admin-account/components/container.tsx +229 -0
  112. package/src/modules/admin-account/components/update.module.css +40 -0
  113. package/src/modules/admin-account/components/update.tsx +263 -0
  114. package/src/modules/admin-account/errors.ts +75 -0
  115. package/src/modules/admin-account/index.ts +60 -0
  116. package/src/modules/admin-account/schemas.ts +84 -0
  117. package/src/modules/admin-account/service.ts +92 -0
  118. package/src/modules/admin-permissions/abilities.ts +46 -0
  119. package/src/modules/admin-permissions/commands.ts +103 -0
  120. package/src/modules/admin-permissions/components/inspector.module.css +326 -0
  121. package/src/modules/admin-permissions/components/inspector.tsx +298 -0
  122. package/src/modules/admin-permissions/dto.ts +28 -0
  123. package/src/modules/admin-permissions/errors.ts +57 -0
  124. package/src/modules/admin-permissions/index.ts +72 -0
  125. package/src/modules/admin-permissions/repository.ts +49 -0
  126. package/src/modules/admin-permissions/schemas.ts +128 -0
  127. package/src/modules/admin-permissions/service.ts +137 -0
  128. package/src/modules/admin-roles/abilities.ts +62 -0
  129. package/src/modules/admin-roles/commands.ts +161 -0
  130. package/src/modules/admin-roles/components/create.module.css +40 -0
  131. package/src/modules/admin-roles/components/create.tsx +218 -0
  132. package/src/modules/admin-roles/components/permissions.module.css +279 -0
  133. package/src/modules/admin-roles/components/permissions.tsx +396 -0
  134. package/src/modules/admin-roles/components/update.module.css +40 -0
  135. package/src/modules/admin-roles/components/update.tsx +218 -0
  136. package/src/modules/admin-roles/dto.ts +30 -0
  137. package/src/modules/admin-roles/errors.ts +76 -0
  138. package/src/modules/admin-roles/index.ts +81 -0
  139. package/src/modules/admin-roles/repository.ts +96 -0
  140. package/src/modules/admin-roles/schemas.ts +139 -0
  141. package/src/modules/admin-roles/service.ts +136 -0
  142. package/src/modules/admin-users/abilities.ts +76 -0
  143. package/src/modules/admin-users/commands.ts +157 -0
  144. package/src/modules/admin-users/components/create.module.css +63 -0
  145. package/src/modules/admin-users/components/create.tsx +323 -0
  146. package/src/modules/admin-users/components/roles.module.css +119 -0
  147. package/src/modules/admin-users/components/roles.tsx +172 -0
  148. package/src/modules/admin-users/components/set-password.module.css +46 -0
  149. package/src/modules/admin-users/components/set-password.tsx +199 -0
  150. package/src/modules/admin-users/components/update.module.css +49 -0
  151. package/src/modules/admin-users/components/update.tsx +328 -0
  152. package/src/modules/admin-users/dto.ts +39 -0
  153. package/src/modules/admin-users/errors.ts +84 -0
  154. package/src/modules/admin-users/index.ts +91 -0
  155. package/src/modules/admin-users/repository.ts +161 -0
  156. package/src/modules/admin-users/schemas.ts +168 -0
  157. package/src/modules/admin-users/seed-super-admin.ts +102 -0
  158. package/src/modules/admin-users/service.ts +166 -0
  159. package/src/modules/auth/components/sign-in-form.module.css +62 -0
  160. package/src/modules/auth/components/sign-in-form.tsx +132 -0
  161. package/src/modules/auth/index.ts +31 -0
  162. package/src/modules/auth/jwt-session-provider.ts +301 -0
  163. package/src/modules/auth/password.ts +94 -0
  164. package/src/modules/auth/phc.ts +121 -0
  165. package/src/modules/auth/refresh-tokens-repository.ts +74 -0
  166. package/src/modules/auth/resolve-actor.ts +42 -0
  167. package/src/services/admin-services-context.tsx +52 -0
  168. package/src/services/admin-services-types.ts +177 -0
  169. package/src/store.ts +32 -0
  170. package/src/vendor/noble-argon2/LICENSE +21 -0
  171. package/src/vendor/noble-argon2/README.md +87 -0
  172. package/src/vendor/noble-argon2/_blake.ts +58 -0
  173. package/src/vendor/noble-argon2/_md.ts +223 -0
  174. package/src/vendor/noble-argon2/_u64.ts +118 -0
  175. package/src/vendor/noble-argon2/argon2.ts +668 -0
  176. package/src/vendor/noble-argon2/blake2.ts +583 -0
  177. package/src/vendor/noble-argon2/utils.ts +849 -0
@@ -1,24 +1,3 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- /**
9
- * `@byline/admin/auth` — session handling for the built-in admin realm.
10
- *
11
- * Hosts the reference `JwtSessionProvider` and the sign-in / refresh /
12
- * revoke orchestration that consumes it, along with password hashing
13
- * (`hashPassword` / `verifyPassword`) and the `RefreshTokensRepository`
14
- * contract the provider drives.
15
- *
16
- * The `SessionProvider` **interface** lives in `@byline/auth` so the
17
- * pluggability contract stays narrow; this module supplies the
18
- * Byline-native implementation. Third-party providers (Lucia, WorkOS,
19
- * Clerk, institutional SSO) should be shipped as separate packages
20
- * against `@byline/auth` rather than added here.
21
- */
22
- export { JwtSessionProvider } from './jwt-session-provider.js';
23
- export { hashPassword, verifyPassword } from './password.js';
24
- export { resolveActor } from './resolve-actor.js';
1
+ export { JwtSessionProvider } from "./jwt-session-provider.js";
2
+ export { hashPassword, verifyPassword } from "./password.js";
3
+ export { resolveActor } from "./resolve-actor.js";
@@ -1,214 +1,244 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- import { createHash, randomBytes, randomUUID } from 'node:crypto';
9
- import { ERR_ACCOUNT_DISABLED, ERR_INVALID_CREDENTIALS, ERR_INVALID_TOKEN, ERR_REVOKED_TOKEN, } from '@byline/auth';
10
- import { jwtVerify, SignJWT } from 'jose';
11
- import { v7 as uuidv7 } from 'uuid';
12
- import { verifyPassword } from './password.js';
13
- import { resolveActor } from './resolve-actor.js';
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import { ERR_ACCOUNT_DISABLED, ERR_INVALID_CREDENTIALS, ERR_INVALID_TOKEN, ERR_REVOKED_TOKEN } from "@byline/auth";
3
+ import { SignJWT, jwtVerify } from "jose";
4
+ import { v7 } from "uuid";
5
+ import { verifyPassword } from "./password.js";
6
+ import { resolveActor } from "./resolve-actor.js";
7
+ function _check_private_redeclaration(obj, privateCollection) {
8
+ if (privateCollection.has(obj)) throw new TypeError("Cannot initialize the same private elements twice on an object");
9
+ }
10
+ function _class_apply_descriptor_get(receiver, descriptor) {
11
+ if (descriptor.get) return descriptor.get.call(receiver);
12
+ return descriptor.value;
13
+ }
14
+ function _class_apply_descriptor_set(receiver, descriptor, value) {
15
+ if (descriptor.set) descriptor.set.call(receiver, value);
16
+ else {
17
+ if (!descriptor.writable) throw new TypeError("attempted to set read only private field");
18
+ descriptor.value = value;
19
+ }
20
+ }
21
+ function _class_extract_field_descriptor(receiver, privateMap, action) {
22
+ if (!privateMap.has(receiver)) throw new TypeError("attempted to " + action + " private field on non-instance");
23
+ return privateMap.get(receiver);
24
+ }
25
+ function _class_private_field_get(receiver, privateMap) {
26
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "get");
27
+ return _class_apply_descriptor_get(receiver, descriptor);
28
+ }
29
+ function _class_private_field_init(obj, privateMap, value) {
30
+ _check_private_redeclaration(obj, privateMap);
31
+ privateMap.set(obj, value);
32
+ }
33
+ function _class_private_field_set(receiver, privateMap, value) {
34
+ var descriptor = _class_extract_field_descriptor(receiver, privateMap, "set");
35
+ _class_apply_descriptor_set(receiver, descriptor, value);
36
+ return value;
37
+ }
38
+ function _class_private_method_get(receiver, privateSet, fn) {
39
+ if (!privateSet.has(receiver)) throw new TypeError("attempted to get private field on non-instance");
40
+ return fn;
41
+ }
42
+ function _class_private_method_init(obj, privateSet) {
43
+ _check_private_redeclaration(obj, privateSet);
44
+ privateSet.add(obj);
45
+ }
14
46
  const DEFAULT_ISSUER = 'byline';
15
- const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 15 * 60; // 15 minutes
16
- const DEFAULT_REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 60 * 60; // 30 days
47
+ const DEFAULT_ACCESS_TOKEN_TTL_SECONDS = 900;
48
+ const DEFAULT_REFRESH_TOKEN_TTL_SECONDS = 2592000;
17
49
  const CAPABILITIES = {
18
50
  passwordChange: true,
19
51
  magicLink: false,
20
- sso: false,
52
+ sso: false
21
53
  };
22
- export class JwtSessionProvider {
23
- capabilities = CAPABILITIES;
24
- #store;
25
- #signingKey;
26
- #issuer;
27
- #accessTtl;
28
- #refreshTtl;
29
- #now;
30
- constructor(config) {
31
- this.#store = config.store;
32
- this.#signingKey =
33
- typeof config.signingSecret === 'string'
34
- ? new TextEncoder().encode(config.signingSecret)
35
- : config.signingSecret;
36
- if (this.#signingKey.byteLength < 32) {
37
- throw new Error('JwtSessionProvider: signingSecret must carry at least 32 bytes of entropy (256 bits)');
38
- }
39
- this.#issuer = config.issuer ?? DEFAULT_ISSUER;
40
- this.#accessTtl = config.accessTokenTtlSeconds ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS;
41
- this.#refreshTtl = config.refreshTokenTtlSeconds ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS;
42
- this.#now = config.now ?? (() => new Date());
43
- }
44
- // -----------------------------------------------------------------------
45
- // SessionProvider
46
- // -----------------------------------------------------------------------
54
+ var _store = /*#__PURE__*/ new WeakMap(), _signingKey = /*#__PURE__*/ new WeakMap(), _issuer = /*#__PURE__*/ new WeakMap(), _accessTtl = /*#__PURE__*/ new WeakMap(), _refreshTtl = /*#__PURE__*/ new WeakMap(), _now = /*#__PURE__*/ new WeakMap(), _issueTokens = /*#__PURE__*/ new WeakSet(), _signAccessToken = /*#__PURE__*/ new WeakSet();
55
+ class JwtSessionProvider {
47
56
  async signInWithPassword(args) {
48
- const users = this.#store.adminUsers;
57
+ const users = _class_private_field_get(this, _store).adminUsers;
49
58
  const row = await users.getByEmailForSignIn(args.email);
50
- // Uniform error response for unknown email vs. wrong password — don't
51
- // leak which one. Still do a real verify against a dummy hash so the
52
- // timing is comparable; the argon2 cost dominates regardless.
53
59
  if (!row) {
54
60
  await verifyPassword(args.password, DUMMY_HASH_FOR_TIMING);
55
- throw ERR_INVALID_CREDENTIALS({ message: 'invalid credentials' });
61
+ throw ERR_INVALID_CREDENTIALS({
62
+ message: 'invalid credentials'
63
+ });
56
64
  }
57
65
  const ok = await verifyPassword(args.password, row.password_hash);
58
66
  if (!ok) {
59
67
  await users.recordLoginFailure(row.id);
60
- throw ERR_INVALID_CREDENTIALS({ message: 'invalid credentials' });
61
- }
62
- if (!row.is_enabled) {
63
- throw ERR_ACCOUNT_DISABLED({ message: 'account disabled' });
68
+ throw ERR_INVALID_CREDENTIALS({
69
+ message: 'invalid credentials'
70
+ });
64
71
  }
72
+ if (!row.is_enabled) throw ERR_ACCOUNT_DISABLED({
73
+ message: 'account disabled'
74
+ });
65
75
  await users.recordLoginSuccess(row.id, args.ip ?? null);
66
- const actor = await resolveActor(this.#store, row.id);
67
- // resolveActor also checks is_enabled, but we just recorded success
68
- // above, so null here would indicate a race (the account was disabled
69
- // between the check and the resolve). Treat as disabled.
70
- if (!actor) {
71
- throw ERR_ACCOUNT_DISABLED({ message: 'account disabled' });
72
- }
73
- const tokens = await this.#issueTokens({
76
+ const actor = await resolveActor(_class_private_field_get(this, _store), row.id);
77
+ if (!actor) throw ERR_ACCOUNT_DISABLED({
78
+ message: 'account disabled'
79
+ });
80
+ const tokens = await _class_private_method_get(this, _issueTokens, issueTokens).call(this, {
74
81
  adminUserId: row.id,
75
82
  ip: args.ip ?? null,
76
- userAgent: args.userAgent ?? null,
83
+ userAgent: args.userAgent ?? null
77
84
  });
78
- return { ...tokens, actor };
85
+ return {
86
+ ...tokens,
87
+ actor
88
+ };
79
89
  }
80
90
  async verifyAccessToken(token) {
81
91
  let payload;
82
92
  try {
83
- const result = await jwtVerify(token, this.#signingKey, {
84
- issuer: this.#issuer,
93
+ const result = await jwtVerify(token, _class_private_field_get(this, _signingKey), {
94
+ issuer: _class_private_field_get(this, _issuer)
85
95
  });
86
96
  payload = result.payload;
97
+ } catch (err) {
98
+ throw ERR_INVALID_TOKEN({
99
+ message: 'access token verification failed',
100
+ cause: err
101
+ });
87
102
  }
88
- catch (err) {
89
- throw ERR_INVALID_TOKEN({ message: 'access token verification failed', cause: err });
90
- }
91
- if (payload.typ !== 'access') {
92
- throw ERR_INVALID_TOKEN({ message: 'unexpected token type' });
93
- }
94
- const actor = await resolveActor(this.#store, payload.sub);
95
- if (!actor) {
96
- // The token was valid but the user is now disabled or deleted.
97
- throw ERR_ACCOUNT_DISABLED({ message: 'account disabled or deleted' });
98
- }
99
- return { actor };
103
+ if ('access' !== payload.typ) throw ERR_INVALID_TOKEN({
104
+ message: 'unexpected token type'
105
+ });
106
+ const actor = await resolveActor(_class_private_field_get(this, _store), payload.sub);
107
+ if (!actor) throw ERR_ACCOUNT_DISABLED({
108
+ message: 'account disabled or deleted'
109
+ });
110
+ return {
111
+ actor
112
+ };
100
113
  }
101
114
  async refreshSession(args) {
102
- const refreshTokens = this.#store.refreshTokens;
115
+ const refreshTokens = _class_private_field_get(this, _store).refreshTokens;
103
116
  const hash = hashToken(args.refreshToken);
104
117
  const row = await refreshTokens.findByHash(hash);
105
- if (!row) {
106
- throw ERR_INVALID_TOKEN({ message: 'refresh token not recognised' });
107
- }
108
- const now = this.#now();
109
- // Already revoked?
110
- if (row.revoked_at != null) {
111
- if (row.rotated_to_id != null) {
112
- // Rotated token replayed — the chain is compromised. Revoke every
113
- // descendant so the attacker and the legitimate holder are both
114
- // signed out.
118
+ if (!row) throw ERR_INVALID_TOKEN({
119
+ message: 'refresh token not recognised'
120
+ });
121
+ const now = _class_private_field_get(this, _now).call(this);
122
+ if (null != row.revoked_at) {
123
+ if (null != row.rotated_to_id) {
115
124
  await refreshTokens.revokeChain(row.id, now);
116
125
  throw ERR_REVOKED_TOKEN({
117
- message: 'refresh token was already rotated — chain revoked',
126
+ message: 'refresh token was already rotated — chain revoked'
118
127
  });
119
128
  }
120
- throw ERR_REVOKED_TOKEN({ message: 'refresh token has been revoked' });
121
- }
122
- if (row.expires_at.getTime() <= now.getTime()) {
123
- throw ERR_INVALID_TOKEN({ message: 'refresh token expired' });
129
+ throw ERR_REVOKED_TOKEN({
130
+ message: 'refresh token has been revoked'
131
+ });
124
132
  }
125
- // Rotate: mint a new token, mark the old one rotated_to the new id.
126
- const newId = uuidv7();
133
+ if (row.expires_at.getTime() <= now.getTime()) throw ERR_INVALID_TOKEN({
134
+ message: 'refresh token expired'
135
+ });
136
+ const newId = v7();
127
137
  const newRefreshPlain = generateOpaqueToken();
128
138
  const newRefreshHash = hashToken(newRefreshPlain);
129
- const refreshExpiresAt = new Date(now.getTime() + this.#refreshTtl * 1000);
139
+ const refreshExpiresAt = new Date(now.getTime() + 1000 * _class_private_field_get(this, _refreshTtl));
130
140
  await refreshTokens.issue({
131
141
  id: newId,
132
142
  admin_user_id: row.admin_user_id,
133
143
  token_hash: newRefreshHash,
134
144
  expires_at: refreshExpiresAt,
135
145
  user_agent: args.userAgent ?? null,
136
- ip: args.ip ?? null,
146
+ ip: args.ip ?? null
137
147
  });
138
148
  await refreshTokens.markRotated(row.id, newId, now);
139
- const accessToken = await this.#signAccessToken(row.admin_user_id, now);
140
- const accessExpiresAt = new Date(now.getTime() + this.#accessTtl * 1000);
149
+ const accessToken = await _class_private_method_get(this, _signAccessToken, signAccessToken).call(this, row.admin_user_id, now);
150
+ const accessExpiresAt = new Date(now.getTime() + 1000 * _class_private_field_get(this, _accessTtl));
141
151
  return {
142
152
  accessToken,
143
153
  refreshToken: newRefreshPlain,
144
154
  accessTokenExpiresAt: accessExpiresAt,
145
- refreshTokenExpiresAt: refreshExpiresAt,
155
+ refreshTokenExpiresAt: refreshExpiresAt
146
156
  };
147
157
  }
148
158
  async revokeSession(refreshToken) {
149
- const refreshTokens = this.#store.refreshTokens;
159
+ const refreshTokens = _class_private_field_get(this, _store).refreshTokens;
150
160
  const row = await refreshTokens.findByHash(hashToken(refreshToken));
151
- if (!row)
152
- return; // Idempotent — unknown tokens are a no-op.
153
- await refreshTokens.revoke(row.id, this.#now());
161
+ if (!row) return;
162
+ await refreshTokens.revoke(row.id, _class_private_field_get(this, _now).call(this));
154
163
  }
155
164
  async resolveActor(adminUserId) {
156
- return resolveActor(this.#store, adminUserId);
165
+ return resolveActor(_class_private_field_get(this, _store), adminUserId);
157
166
  }
158
- // -----------------------------------------------------------------------
159
- // Internals
160
- // -----------------------------------------------------------------------
161
- async #issueTokens(input) {
162
- const now = this.#now();
163
- const refreshTokens = this.#store.refreshTokens;
164
- const accessToken = await this.#signAccessToken(input.adminUserId, now);
165
- const accessExpiresAt = new Date(now.getTime() + this.#accessTtl * 1000);
166
- const refreshPlain = generateOpaqueToken();
167
- const refreshHash = hashToken(refreshPlain);
168
- const refreshExpiresAt = new Date(now.getTime() + this.#refreshTtl * 1000);
169
- await refreshTokens.issue({
170
- id: uuidv7(),
171
- admin_user_id: input.adminUserId,
172
- token_hash: refreshHash,
173
- expires_at: refreshExpiresAt,
174
- user_agent: input.userAgent,
175
- ip: input.ip,
167
+ constructor(config){
168
+ _class_private_method_init(this, _issueTokens);
169
+ _class_private_method_init(this, _signAccessToken);
170
+ _class_private_field_init(this, _store, {
171
+ writable: true,
172
+ value: void 0
176
173
  });
177
- return {
178
- accessToken,
179
- refreshToken: refreshPlain,
180
- accessTokenExpiresAt: accessExpiresAt,
181
- refreshTokenExpiresAt: refreshExpiresAt,
182
- };
183
- }
184
- async #signAccessToken(adminUserId, now) {
185
- const iat = Math.floor(now.getTime() / 1000);
186
- const exp = iat + this.#accessTtl;
187
- return new SignJWT({ typ: 'access' })
188
- .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
189
- .setSubject(adminUserId)
190
- .setIssuer(this.#issuer)
191
- .setIssuedAt(iat)
192
- .setExpirationTime(exp)
193
- .setJti(randomUUID())
194
- .sign(this.#signingKey);
174
+ _class_private_field_init(this, _signingKey, {
175
+ writable: true,
176
+ value: void 0
177
+ });
178
+ _class_private_field_init(this, _issuer, {
179
+ writable: true,
180
+ value: void 0
181
+ });
182
+ _class_private_field_init(this, _accessTtl, {
183
+ writable: true,
184
+ value: void 0
185
+ });
186
+ _class_private_field_init(this, _refreshTtl, {
187
+ writable: true,
188
+ value: void 0
189
+ });
190
+ _class_private_field_init(this, _now, {
191
+ writable: true,
192
+ value: void 0
193
+ });
194
+ this.capabilities = CAPABILITIES;
195
+ _class_private_field_set(this, _store, config.store);
196
+ _class_private_field_set(this, _signingKey, 'string' == typeof config.signingSecret ? new TextEncoder().encode(config.signingSecret) : config.signingSecret);
197
+ if (_class_private_field_get(this, _signingKey).byteLength < 32) throw new Error('JwtSessionProvider: signingSecret must carry at least 32 bytes of entropy (256 bits)');
198
+ _class_private_field_set(this, _issuer, config.issuer ?? DEFAULT_ISSUER);
199
+ _class_private_field_set(this, _accessTtl, config.accessTokenTtlSeconds ?? DEFAULT_ACCESS_TOKEN_TTL_SECONDS);
200
+ _class_private_field_set(this, _refreshTtl, config.refreshTokenTtlSeconds ?? DEFAULT_REFRESH_TOKEN_TTL_SECONDS);
201
+ _class_private_field_set(this, _now, config.now ?? (()=>new Date()));
195
202
  }
196
203
  }
197
- // ---------------------------------------------------------------------------
198
- // Utilities
199
- // ---------------------------------------------------------------------------
200
- /** 32 bytes of randomness, base64url-encoded. ~43 chars on the wire. */
204
+ async function issueTokens(input) {
205
+ const now = _class_private_field_get(this, _now).call(this);
206
+ const refreshTokens = _class_private_field_get(this, _store).refreshTokens;
207
+ const accessToken = await _class_private_method_get(this, _signAccessToken, signAccessToken).call(this, input.adminUserId, now);
208
+ const accessExpiresAt = new Date(now.getTime() + 1000 * _class_private_field_get(this, _accessTtl));
209
+ const refreshPlain = generateOpaqueToken();
210
+ const refreshHash = hashToken(refreshPlain);
211
+ const refreshExpiresAt = new Date(now.getTime() + 1000 * _class_private_field_get(this, _refreshTtl));
212
+ await refreshTokens.issue({
213
+ id: v7(),
214
+ admin_user_id: input.adminUserId,
215
+ token_hash: refreshHash,
216
+ expires_at: refreshExpiresAt,
217
+ user_agent: input.userAgent,
218
+ ip: input.ip
219
+ });
220
+ return {
221
+ accessToken,
222
+ refreshToken: refreshPlain,
223
+ accessTokenExpiresAt: accessExpiresAt,
224
+ refreshTokenExpiresAt: refreshExpiresAt
225
+ };
226
+ }
227
+ async function signAccessToken(adminUserId, now) {
228
+ const iat = Math.floor(now.getTime() / 1000);
229
+ const exp = iat + _class_private_field_get(this, _accessTtl);
230
+ return new SignJWT({
231
+ typ: 'access'
232
+ }).setProtectedHeader({
233
+ alg: 'HS256',
234
+ typ: 'JWT'
235
+ }).setSubject(adminUserId).setIssuer(_class_private_field_get(this, _issuer)).setIssuedAt(iat).setExpirationTime(exp).setJti(randomUUID()).sign(_class_private_field_get(this, _signingKey));
236
+ }
201
237
  function generateOpaqueToken() {
202
238
  return randomBytes(32).toString('base64url');
203
239
  }
204
- /** SHA-256 hex digest of the raw refresh-token string. */
205
240
  function hashToken(token) {
206
241
  return createHash('sha256').update(token).digest('hex');
207
242
  }
208
- /**
209
- * A stable argon2id hash used only to equalise sign-in timing on the
210
- * unknown-email code path. The plaintext here is arbitrary — we never
211
- * succeed against it. This is pre-generated at module-load time so the
212
- * first sign-in call doesn't pay the generation cost.
213
- */
214
243
  const DUMMY_HASH_FOR_TIMING = '$argon2id$v=19$m=19456,t=2,p=1$c2lkZS1jaGFubmVsLW1pdGlnYXRpb24$0Hqf2vQKZqSfZZ4nJRr7K5IOjn9ngjzaQjV+yTG6iNY';
244
+ export { JwtSessionProvider };
@@ -1,62 +1,25 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- /**
9
- * Password hashing — argon2id via the vendored `@noble/hashes` copy at
10
- * `../../vendor/noble-argon2/`. Pure-JS, runs anywhere with a modern JS
11
- * runtime (Node, Workers, Deno, Bun, browsers).
12
- *
13
- * Stores the full PHC string (`$argon2id$v=19$m=…$…$…`) in the
14
- * `byline_admin_users.password` column. That makes the algorithm and
15
- * parameters self-describing, so upgrading params later (or migrating off
16
- * argon2id entirely) is a straightforward re-hash on next successful sign-in.
17
- *
18
- * Defaults follow OWASP 2023 guidance for argon2id: memory 19 MiB,
19
- * iterations 2, parallelism 1. These are reasonable for typical server
20
- * hardware; tune if sign-in latency becomes a concern under load.
21
- *
22
- * Note: pure-JS argon2id is meaningfully slower than the previous
23
- * `@node-rs/argon2` Rust binding (~50–150 ms vs ~10 ms at these params on
24
- * modern server hardware). For interactive sign-in this is fine; for
25
- * high-throughput auth services consider tuning `HASH_OPTIONS` or
26
- * reintroducing a native binding behind a runtime-feature check.
27
- */
28
- import { argon2idAsync } from '../../vendor/noble-argon2/argon2.js';
29
- import { decodeArgon2idPhc, encodeArgon2idPhc, timingSafeEqual } from './phc.js';
30
- /** Argon2id cost parameters. Matches the prior `@node-rs/argon2` defaults. */
1
+ import { argon2idAsync } from "../../vendor/noble-argon2/argon2.js";
2
+ import { decodeArgon2idPhc, encodeArgon2idPhc, timingSafeEqual } from "./phc.js";
31
3
  const HASH_OPTIONS = {
32
- /** Memory cost in KiB (19 MiB). */
33
4
  memoryCost: 19456,
34
- /** Iterations. */
35
5
  timeCost: 2,
36
- /** Parallelism (lanes). */
37
6
  parallelism: 1,
38
- /** Derived-key length in bytes — 32 matches the prior stored hashes. */
39
7
  hashLength: 32,
40
- /** Salt length in bytes — 16 matches the prior stored hashes. */
41
- saltLength: 16,
8
+ saltLength: 16
42
9
  };
43
- /** Argon2 v1.3 (RFC 9106). */
44
10
  const ARGON2_VERSION = 0x13;
45
11
  function randomSalt(length) {
46
12
  return crypto.getRandomValues(new Uint8Array(length));
47
13
  }
48
- /** Hash a plaintext password. Returns a full PHC string. */
49
- export async function hashPassword(plaintext) {
50
- if (plaintext.length === 0) {
51
- throw new Error('hashPassword: plaintext must be non-empty');
52
- }
14
+ async function hashPassword(plaintext) {
15
+ if (0 === plaintext.length) throw new Error('hashPassword: plaintext must be non-empty');
53
16
  const salt = randomSalt(HASH_OPTIONS.saltLength);
54
17
  const hash = await argon2idAsync(plaintext, salt, {
55
18
  m: HASH_OPTIONS.memoryCost,
56
19
  t: HASH_OPTIONS.timeCost,
57
20
  p: HASH_OPTIONS.parallelism,
58
21
  dkLen: HASH_OPTIONS.hashLength,
59
- version: ARGON2_VERSION,
22
+ version: ARGON2_VERSION
60
23
  });
61
24
  return encodeArgon2idPhc({
62
25
  algorithm: 'argon2id',
@@ -65,24 +28,19 @@ export async function hashPassword(plaintext) {
65
28
  timeCost: HASH_OPTIONS.timeCost,
66
29
  parallelism: HASH_OPTIONS.parallelism,
67
30
  salt,
68
- hash,
31
+ hash
69
32
  });
70
33
  }
71
- /**
72
- * Verify a plaintext password against a stored PHC string. Returns `false`
73
- * on mismatch — never throws for a normal mismatch. Re-throws on malformed
74
- * hash strings or underlying library errors so those get surfaced.
75
- */
76
- export async function verifyPassword(plaintext, phc) {
77
- if (plaintext.length === 0 || phc.length === 0)
78
- return false;
34
+ async function verifyPassword(plaintext, phc) {
35
+ if (0 === plaintext.length || 0 === phc.length) return false;
79
36
  const decoded = decodeArgon2idPhc(phc);
80
37
  const candidate = await argon2idAsync(plaintext, decoded.salt, {
81
38
  m: decoded.memoryCost,
82
39
  t: decoded.timeCost,
83
40
  p: decoded.parallelism,
84
41
  dkLen: decoded.hash.length,
85
- version: decoded.version,
42
+ version: decoded.version
86
43
  });
87
44
  return timingSafeEqual(candidate, decoded.hash);
88
45
  }
46
+ export { hashPassword, verifyPassword };
@@ -1,68 +1,40 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
1
  function bytesToB64NoPad(bytes) {
9
2
  let bin = '';
10
- for (let i = 0; i < bytes.length; i++)
11
- bin += String.fromCharCode(bytes[i]);
3
+ for(let i = 0; i < bytes.length; i++)bin += String.fromCharCode(bytes[i]);
12
4
  return btoa(bin).replace(/=+$/, '');
13
5
  }
14
6
  function b64NoPadToBytes(b64) {
15
- const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
7
+ const padded = b64 + '='.repeat((4 - b64.length % 4) % 4);
16
8
  const bin = atob(padded);
17
9
  const out = new Uint8Array(bin.length);
18
- for (let i = 0; i < bin.length; i++)
19
- out[i] = bin.charCodeAt(i);
10
+ for(let i = 0; i < bin.length; i++)out[i] = bin.charCodeAt(i);
20
11
  return out;
21
12
  }
22
- export function encodeArgon2idPhc(phc) {
23
- return (`$${phc.algorithm}` +
24
- `$v=${phc.version}` +
25
- `$m=${phc.memoryCost},t=${phc.timeCost},p=${phc.parallelism}` +
26
- `$${bytesToB64NoPad(phc.salt)}` +
27
- `$${bytesToB64NoPad(phc.hash)}`);
13
+ function encodeArgon2idPhc(phc) {
14
+ return `$${phc.algorithm}$v=${phc.version}$m=${phc.memoryCost},t=${phc.timeCost},p=${phc.parallelism}$${bytesToB64NoPad(phc.salt)}$${bytesToB64NoPad(phc.hash)}`;
28
15
  }
29
- export function decodeArgon2idPhc(s) {
30
- // Leading `$` produces an empty first segment, so a valid argon2id PHC string
31
- // splits into exactly 6 parts: ['', algo, 'v=…', 'm=…,t=…,p=…', salt, hash].
16
+ function decodeArgon2idPhc(s) {
32
17
  const parts = s.split('$');
33
- if (parts.length !== 6 || parts[0] !== '') {
34
- throw new Error('decodeArgon2idPhc: malformed PHC string');
35
- }
18
+ if (6 !== parts.length || '' !== parts[0]) throw new Error('decodeArgon2idPhc: malformed PHC string');
36
19
  const algorithm = parts[1] ?? '';
37
20
  const versionField = parts[2] ?? '';
38
21
  const paramsField = parts[3] ?? '';
39
22
  const saltB64 = parts[4] ?? '';
40
23
  const hashB64 = parts[5] ?? '';
41
- if (algorithm !== 'argon2id') {
42
- throw new Error(`decodeArgon2idPhc: unsupported algorithm "${algorithm}"`);
43
- }
44
- if (!versionField.startsWith('v=')) {
45
- throw new Error('decodeArgon2idPhc: missing version field');
46
- }
24
+ if ('argon2id' !== algorithm) throw new Error(`decodeArgon2idPhc: unsupported algorithm "${algorithm}"`);
25
+ if (!versionField.startsWith('v=')) throw new Error('decodeArgon2idPhc: missing version field');
47
26
  const version = Number.parseInt(versionField.slice(2), 10);
48
- if (!Number.isInteger(version)) {
49
- throw new Error(`decodeArgon2idPhc: invalid version "${versionField}"`);
50
- }
27
+ if (!Number.isInteger(version)) throw new Error(`decodeArgon2idPhc: invalid version "${versionField}"`);
51
28
  const params = {};
52
- for (const kv of paramsField.split(',')) {
29
+ for (const kv of paramsField.split(',')){
53
30
  const eq = kv.indexOf('=');
54
- if (eq <= 0)
55
- throw new Error(`decodeArgon2idPhc: malformed param "${kv}"`);
31
+ if (eq <= 0) throw new Error(`decodeArgon2idPhc: malformed param "${kv}"`);
56
32
  const k = kv.slice(0, eq);
57
33
  const v = Number.parseInt(kv.slice(eq + 1), 10);
58
- if (!Number.isInteger(v))
59
- throw new Error(`decodeArgon2idPhc: malformed param value "${kv}"`);
60
- if (k === 'm' || k === 't' || k === 'p')
61
- params[k] = v;
62
- }
63
- if (params.m === undefined || params.t === undefined || params.p === undefined) {
64
- throw new Error('decodeArgon2idPhc: missing required m/t/p params');
34
+ if (!Number.isInteger(v)) throw new Error(`decodeArgon2idPhc: malformed param value "${kv}"`);
35
+ if ('m' === k || 't' === k || 'p' === k) params[k] = v;
65
36
  }
37
+ if (void 0 === params.m || void 0 === params.t || void 0 === params.p) throw new Error('decodeArgon2idPhc: missing required m/t/p params');
66
38
  return {
67
39
  algorithm: 'argon2id',
68
40
  version,
@@ -70,18 +42,13 @@ export function decodeArgon2idPhc(s) {
70
42
  timeCost: params.t,
71
43
  parallelism: params.p,
72
44
  salt: b64NoPadToBytes(saltB64),
73
- hash: b64NoPadToBytes(hashB64),
45
+ hash: b64NoPadToBytes(hashB64)
74
46
  };
75
47
  }
76
- /**
77
- * Constant-time byte comparison. Returns `true` only if both arrays have the
78
- * same length and every byte matches. Intended for hash verification.
79
- */
80
- export function timingSafeEqual(a, b) {
81
- if (a.length !== b.length)
82
- return false;
48
+ function timingSafeEqual(a, b) {
49
+ if (a.length !== b.length) return false;
83
50
  let diff = 0;
84
- for (let i = 0; i < a.length; i++)
85
- diff |= a[i] ^ b[i];
86
- return diff === 0;
51
+ for(let i = 0; i < a.length; i++)diff |= a[i] ^ b[i];
52
+ return 0 === diff;
87
53
  }
54
+ export { decodeArgon2idPhc, encodeArgon2idPhc, timingSafeEqual };
@@ -1,8 +1 @@
1
- /**
2
- * This Source Code is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
- *
6
- * Copyright (c) Infonomic Company Limited
7
- */
8
- export {};
1
+ export { };