@pablozaiden/webapp 0.5.5 → 0.5.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablozaiden/webapp",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Opinionated Bun + React webapp framework for single-server apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -61,6 +61,6 @@
61
61
  "playwright": "1.61.0",
62
62
  "react": "19.2.7",
63
63
  "react-dom": "19.2.7",
64
- "typescript": "6.0.3"
64
+ "typescript": "7.0.2"
65
65
  }
66
66
  }
@@ -208,6 +208,35 @@ function createRefreshRecord(userId: string, clientId: string, scope: string, fa
208
208
  };
209
209
  }
210
210
 
211
+ function revokeExistingClientSessions(store: WebAppStore, userId: string, clientId: string, revokedAt: string): void {
212
+ for (const session of store.listRefreshSessions(userId)) {
213
+ if (session.clientId === clientId && !session.revokedAt && !isExpired(session.expiresAt)) {
214
+ store.revokeRefreshSession(session.id, revokedAt, userId);
215
+ }
216
+ }
217
+ }
218
+
219
+ function activeRefreshSessions(store: WebAppStore, userId: string): RefreshSessionRecord[] {
220
+ return store.listRefreshSessions(userId)
221
+ .filter((session) => !session.revokedAt && !isExpired(session.expiresAt))
222
+ .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt));
223
+ }
224
+
225
+ function uniqueActiveClientSessions(store: WebAppStore, userId: string): RefreshSessionRecord[] {
226
+ const seenClientIds = new Set<string>();
227
+ const sessions: RefreshSessionRecord[] = [];
228
+ const duplicateRevokedAt = nowIso();
229
+ for (const session of activeRefreshSessions(store, userId)) {
230
+ if (seenClientIds.has(session.clientId)) {
231
+ store.revokeRefreshSession(session.id, duplicateRevokedAt, userId);
232
+ } else {
233
+ seenClientIds.add(session.clientId);
234
+ sessions.push(session);
235
+ }
236
+ }
237
+ return sessions;
238
+ }
239
+
211
240
  export async function exchangeDeviceCode(store: WebAppStore, config: RuntimeConfig, deviceCode: string, clientId?: string): Promise<TokenResponse> {
212
241
  store.deleteExpiredDeviceAuthRequests(nowIso());
213
242
  const record = store.getDeviceAuthByDeviceCodeHash(sha256(deviceCode));
@@ -237,6 +266,7 @@ export async function exchangeDeviceCode(store: WebAppStore, config: RuntimeConf
237
266
  throw new AuthError("invalid_grant", "Approving user no longer exists", 400);
238
267
  }
239
268
  const user = toCurrentUser(userRecord);
269
+ revokeExistingClientSessions(store, user.id, record.clientId, nowIso());
240
270
  const refresh = createRefreshRecord(user.id, record.clientId, record.scope);
241
271
  store.saveRefreshSession(refresh.record);
242
272
  const access = await issueAccessToken(store, config, {
@@ -305,8 +335,7 @@ export async function verifyAccessToken(store: WebAppStore, config: RuntimeConfi
305
335
 
306
336
  export function listAuthSessions(store: WebAppStore, userId: string): AuthSessionSummary[] {
307
337
  store.deleteExpiredRefreshSessions?.(nowIso());
308
- return store.listRefreshSessions(userId)
309
- .filter((session) => !session.revokedAt && !isExpired(session.expiresAt))
338
+ return uniqueActiveClientSessions(store, userId)
310
339
  .map((session) => ({
311
340
  id: session.id,
312
341
  clientId: session.clientId,