@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.
|
|
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": "
|
|
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
|
|
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,
|