@maestro-js/auth 1.0.0-alpha.24 → 1.0.0-alpha.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -14,6 +14,12 @@ interface RememberMeTokenRecordType<Id, DeviceContext> {
14
14
  verifierHash: string;
15
15
  expirationDate: Iso.Instant | null;
16
16
  rotatedDate: Iso.Instant | null;
17
+ /**
18
+ * When the token chain was first issued (at login). Successor rows produced by `rotateBySelector`
19
+ * must inherit this value rather than resetting it, so it always reflects the original grant time.
20
+ * Used to reject cookies whose chain predates a credential invalidation.
21
+ */
22
+ createdDate: Iso.Instant;
17
23
  }
18
24
  interface RememberMeOptionsType<Id, DeviceContext> {
19
25
  maxRememberMeSeconds: number;
@@ -23,7 +29,7 @@ interface RememberMeOptionsType<Id, DeviceContext> {
23
29
  rotateBySelector(selector: string, token: {
24
30
  verifierHash: string;
25
31
  selector: string;
26
- }): Promise<void>;
32
+ }): Promise<boolean>;
27
33
  revokeBySelector(selector: string): Promise<void>;
28
34
  revokeByAuthenticatableId(id: Id): Promise<void>;
29
35
  validateDeviceContext?(creatingDeviceContext: DeviceContext, currentDeviceContext: DeviceContext): Promise<boolean> | boolean;
@@ -129,6 +135,13 @@ declare namespace Auth {
129
135
  id: Id | null;
130
136
  cookie: string | null;
131
137
  }>;
138
+ /**
139
+ * Delete all stored remember-me token rows for an authenticatable. `authenticate` already
140
+ * rejects any chain issued before `credentialsInvalidBefore`, so credential-invalidating
141
+ * flows (e.g. password reset) are secure without this; call it to immediately clean up the
142
+ * rows instead of leaving them to expire. No-op when remember-me is not configured.
143
+ */
144
+ revokeRememberMeTokens(id: Id): Promise<void>;
132
145
  };
133
146
  interface AuthServiceConfig<Id, Credentials, DeviceContext> {
134
147
  name: string;
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ function createRememberMeProvider({
20
20
  deviceContext: options.deviceContext,
21
21
  expirationDate: options.expirationDate,
22
22
  rotatedDate: null,
23
+ createdDate: instantFns.now(),
23
24
  selector,
24
25
  verifierHash
25
26
  });
@@ -49,16 +50,19 @@ function createRememberMeProvider({
49
50
  if (validateDeviceContext && !await validateDeviceContext(record.deviceContext, deviceContext)) {
50
51
  return null;
51
52
  }
52
- return record.authenticatableId;
53
+ return { id: record.authenticatableId, tokenCreatedDate: record.createdDate };
53
54
  }
54
55
  async function rotate(previousSelector) {
55
56
  const selector = crypto.randomBytes(16).toString("base64url");
56
57
  const verifier = crypto.randomBytes(32).toString("base64url");
57
58
  const verifierHash = await hash.hash(verifier);
58
- await db.rotateBySelector(previousSelector, {
59
+ const claimed = await db.rotateBySelector(previousSelector, {
59
60
  selector,
60
61
  verifierHash
61
62
  });
63
+ if (!claimed) {
64
+ return null;
65
+ }
62
66
  return { selector, verifier };
63
67
  }
64
68
  async function revoke(token) {
@@ -180,19 +184,25 @@ function create(config) {
180
184
  if (!rememberMeToken) {
181
185
  return NO_AUTH;
182
186
  }
183
- const id = await rememberMeProvider.validate({ token: rememberMeToken, deviceContext });
184
- if (!id) {
187
+ const validated = await rememberMeProvider.validate({ token: rememberMeToken, deviceContext });
188
+ if (!validated) {
185
189
  return NO_AUTH;
186
190
  }
187
- const authenticatable = await config.getAuthenticatableById(id);
191
+ const authenticatable = await config.getAuthenticatableById(validated.id);
188
192
  if (!authenticatable) {
189
193
  return NO_AUTH;
190
194
  }
195
+ if (authenticatable.credentialsInvalidBefore && validated.tokenCreatedDate < authenticatable.credentialsInvalidBefore) {
196
+ return NO_AUTH;
197
+ }
191
198
  const newRememberMeToken = await rememberMeProvider.rotate(rememberMeToken.selector);
192
199
  session.set(ID_SESSION_KEY, authenticatable.id);
193
200
  session.set(AUTH_PROVIDER_NAME_SESSION_KEY, config.name);
194
201
  session.set(SESSION_CREATED_DATE_KEY, instantFns2.now());
195
202
  session.regenerate();
203
+ if (!newRememberMeToken) {
204
+ return { id: authenticatable.id, cookie: null };
205
+ }
196
206
  return {
197
207
  id: authenticatable.id,
198
208
  cookie: getCookie({
@@ -246,6 +256,11 @@ function create(config) {
246
256
  session
247
257
  });
248
258
  }
259
+ async function revokeRememberMeTokens(id) {
260
+ if (config.rememberMeOptions) {
261
+ await rememberMeProvider.revokeAll(id);
262
+ }
263
+ }
249
264
  function getCookie({ rememberMeCookieValue }) {
250
265
  if (rememberMeCookieValue) {
251
266
  return buildSetCookieHeader({
@@ -268,7 +283,8 @@ function create(config) {
268
283
  loginById,
269
284
  authenticate,
270
285
  logout,
271
- logoutOtherDevices
286
+ logoutOtherDevices,
287
+ revokeRememberMeTokens
272
288
  };
273
289
  }
274
290
  var registry = ServiceRegistry.createRegistry(
@@ -285,7 +301,8 @@ function provider(key) {
285
301
  loginById: service.loginById,
286
302
  authenticate: service.authenticate,
287
303
  logout: service.logout,
288
- logoutOtherDevices: service.logoutOtherDevices
304
+ logoutOtherDevices: service.logoutOtherDevices,
305
+ revokeRememberMeTokens: service.revokeRememberMeTokens
289
306
  };
290
307
  }
291
308
  var AuthBase = {
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "default": "./dist/index.js"
8
8
  }
9
9
  },
10
- "version": "1.0.0-alpha.24",
10
+ "version": "1.0.0-alpha.26",
11
11
  "publishConfig": {
12
12
  "access": "public"
13
13
  },
@@ -16,18 +16,18 @@
16
16
  ],
17
17
  "dependencies": {
18
18
  "iso-fns2": "npm:iso-fns@2.0.0-alpha.26",
19
- "@maestro-js/service-registry": "1.0.0-alpha.24"
19
+ "@maestro-js/service-registry": "1.0.0-alpha.26"
20
20
  },
21
21
  "peerDependencies": {
22
- "@maestro-js/session": "1.0.0-alpha.24",
23
- "@maestro-js/hash": "1.0.0-alpha.24",
24
- "@maestro-js/crypt": "1.0.0-alpha.24"
22
+ "@maestro-js/session": "1.0.0-alpha.26",
23
+ "@maestro-js/crypt": "1.0.0-alpha.26",
24
+ "@maestro-js/hash": "1.0.0-alpha.26"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.19.11",
28
- "@maestro-js/session": "1.0.0-alpha.24",
29
- "@maestro-js/crypt": "1.0.0-alpha.24",
30
- "@maestro-js/hash": "1.0.0-alpha.24"
28
+ "@maestro-js/session": "1.0.0-alpha.26",
29
+ "@maestro-js/crypt": "1.0.0-alpha.26",
30
+ "@maestro-js/hash": "1.0.0-alpha.26"
31
31
  },
32
32
  "license": "UNLICENSED",
33
33
  "engines": {