Skip to content

Commit b2e887e

Browse files
committed
redis cache and other updates
1 parent 5857d99 commit b2e887e

File tree

6 files changed

+42
-12
lines changed

6 files changed

+42
-12
lines changed

server/api/auth/session.get.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { PublicUserSessionSchema } from '#shared/schemas/publicUserSession'
22
import { safeParse } from 'valibot'
33

4-
export default defineEventHandler(async event => {
5-
const serverSession = await useServerSession(event)
4+
export default eventHandlerWithOAuthSession(async (event, _, serverSession) => {
65
const result = safeParse(PublicUserSessionSchema, serverSession.data.public)
76
if (!result.success) {
87
return null

server/utils/atproto/oauth-session-store.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@ import type { UserServerSession } from '#shared/types/userSession'
33
import type { SessionManager } from 'h3'
44
import { OAUTH_CACHE_STORAGE_BASE } from '#server/utils/atproto/storage'
55

6+
// Refresh tokens from a confidential client should last for 180 days, each new refresh of access token resets
7+
// the expiration with the new refresh token. Shorting to 179 days to keep it a bit simpler since we rely on redis to clear sessions
8+
// Note: This expiration only lasts this long in production. Local dev is 2 weeks
9+
const SESSION_EXPIRATION = CACHE_MAX_AGE_ONE_DAY * 179
10+
611
export class OAuthSessionStore implements NodeSavedSessionStore {
712
private readonly serverSession: SessionManager<UserServerSession>
8-
private readonly storage = useStorage(OAUTH_CACHE_STORAGE_BASE)
13+
private readonly cache: CacheAdapter
914

1015
constructor(session: SessionManager<UserServerSession>) {
1116
this.serverSession = session
17+
this.cache = getCacheAdapter(OAUTH_CACHE_STORAGE_BASE)
1218
}
1319

1420
private createStorageKey(did: string, sessionId: string) {
@@ -23,7 +29,7 @@ export class OAuthSessionStore implements NodeSavedSessionStore {
2329
return undefined
2430
}
2531

26-
let session = await this.storage.getItem<NodeSavedSession>(
32+
let session = await this.cache.get<NodeSavedSession>(
2733
this.createStorageKey(key, serverSessionData.oauthSessionId),
2834
)
2935
return session ?? undefined
@@ -41,7 +47,14 @@ export class OAuthSessionStore implements NodeSavedSessionStore {
4147
sessionId = serverSessionData.oauthSessionId
4248
}
4349
try {
44-
await this.storage.setItem<NodeSavedSession>(this.createStorageKey(key, sessionId), val)
50+
await this.cache.set<NodeSavedSession>(
51+
this.createStorageKey(key, sessionId),
52+
val,
53+
SESSION_EXPIRATION,
54+
)
55+
await this.serverSession.update({
56+
lastUpdatedAt: new Date(),
57+
})
4558
} catch (error) {
4659
// Not sure if this has been happening. But helps with debugging
4760
console.error(
@@ -59,7 +72,7 @@ export class OAuthSessionStore implements NodeSavedSessionStore {
5972
console.warn('[oauth session store] No oauthSessionId found in session data')
6073
return undefined
6174
}
62-
await this.storage.removeItem(this.createStorageKey(key, serverSessionData.oauthSessionId))
75+
await this.cache.delete(this.createStorageKey(key, serverSessionData.oauthSessionId))
6376
await this.serverSession.update({
6477
oauthSessionId: undefined,
6578
})

server/utils/atproto/oauth-state-store.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
import type { NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node'
22
import type { UserServerSession } from '#shared/types/userSession'
33
import type { SessionManager } from 'h3'
4-
import { OAUTH_CACHE_STORAGE_BASE } from '#server/utils/atproto/storage'
4+
5+
// It is recommended that oauth state is only saved for 30 minutes
6+
const STATE_EXPIRATION = CACHE_MAX_AGE_ONE_MINUTE * 30
57

68
export class OAuthStateStore implements NodeSavedStateStore {
79
private readonly serverSession: SessionManager<UserServerSession>
8-
private readonly storage = useStorage(OAUTH_CACHE_STORAGE_BASE)
10+
private readonly cache: CacheAdapter
911

1012
constructor(session: SessionManager<UserServerSession>) {
1113
this.serverSession = session
14+
this.cache = getCacheAdapter(OAUTH_CACHE_STORAGE_BASE)
1215
}
1316

1417
private createStorageKey(did: string, sessionId: string) {
@@ -19,7 +22,7 @@ export class OAuthStateStore implements NodeSavedStateStore {
1922
const serverSessionData = this.serverSession.data
2023
if (!serverSessionData) return undefined
2124
if (!serverSessionData.oauthStateId) return undefined
22-
const state = await this.storage.getItem<NodeSavedState>(
25+
const state = await this.cache.get<NodeSavedState>(
2326
this.createStorageKey(key, serverSessionData.oauthStateId),
2427
)
2528
return state ?? undefined
@@ -30,14 +33,14 @@ export class OAuthStateStore implements NodeSavedStateStore {
3033
await this.serverSession.update({
3134
oauthStateId: stateId,
3235
})
33-
await this.storage.setItem<NodeSavedState>(this.createStorageKey(key, stateId), val)
36+
await this.cache.set<NodeSavedState>(this.createStorageKey(key, stateId), val, STATE_EXPIRATION)
3437
}
3538

3639
async del(key: string) {
3740
const serverSessionData = this.serverSession.data
3841
if (!serverSessionData) return undefined
3942
if (!serverSessionData.oauthStateId) return undefined
40-
await this.storage.removeItem(this.createStorageKey(key, serverSessionData.oauthStateId))
43+
await this.cache.delete(this.createStorageKey(key, serverSessionData.oauthStateId))
4144
await this.serverSession.update({
4245
oauthStateId: undefined,
4346
})

server/utils/atproto/oauth.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,18 @@ export function eventHandlerWithOAuthSession<T extends EventHandlerRequest, D>(
156156
) {
157157
return defineEventHandler(async event => {
158158
const { oauthSession, serverSession } = await getOAuthSession(event)
159+
let oauthSessionId = serverSession.data.oauthSessionId
160+
161+
// User was authenticated at one point, but was not able to restore
162+
// the session to the PDS
163+
if (!oauthSession && oauthSessionId) {
164+
// cleans up our server side session store
165+
await serverSession.clear()
166+
throw createError({
167+
status: 401,
168+
message: 'User needs to re authenticate',
169+
})
170+
}
159171

160172
return await handler(event, oauthSession, serverSession)
161173
})

server/utils/server-session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ export const useServerSession = async (event: H3Event) => {
1414
throw new Error('Session password is not configured')
1515
}
1616

17-
const serverSession = useSession<UserServerSession>(event, {
17+
const serverSession = await useSession<UserServerSession>(event, {
1818
password: config.sessionPassword,
19+
maxAge: CACHE_MAX_AGE_ONE_DAY * 179,
1920
})
2021

2122
return serverSession

shared/types/userSession.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export interface UserServerSession {
1313
// These values are tied to the users browser session and used by atproto OAuth
1414
oauthSessionId?: string | undefined
1515
oauthStateId?: string | undefined
16+
// Last time the oauth session was updated
17+
lastUpdatedAt?: Date | undefined
1618

1719
// DO NOT USE
1820
// Here for historic reasons to redirect users logged in with the previous oauth to login again

0 commit comments

Comments
 (0)