diff --git a/src/auth/repository.ts b/src/auth/repository.ts index fbbe8c9..b6c9f8d 100644 --- a/src/auth/repository.ts +++ b/src/auth/repository.ts @@ -453,22 +453,58 @@ export async function verifyPasswordResetCode(email: string, code: string): Prom export async function createRefreshToken(userId: string): Promise { const token = createOpaqueToken() - await redis.set(refreshTokenKey(token), JSON.stringify({ userId }), 'EX', config.refreshTokenTtlSeconds) + const expiresAt = new Date(Date.now() + config.refreshTokenTtlSeconds * 1000) + + await Promise.all([ + redis.set(refreshTokenKey(token), JSON.stringify({ userId }), 'EX', config.refreshTokenTtlSeconds), + prisma.refreshToken.create({ + data: { + userId, + tokenHash: hashToken(token), + expiresAt + } + }) + ]) + return token } export async function rotateRefreshToken(refreshToken: string): Promise { + const tokenHash = hashToken(refreshToken) const tokenJson = await redis.send('GETDEL', [refreshTokenKey(refreshToken)]) if (typeof tokenJson !== 'string') return null const token = parseStoredRefreshToken(tokenJson) if (!token) return null + const storedToken = await prisma.refreshToken.findUnique({ + where: { tokenHash }, + select: { id: true, userId: true, revokedAt: true, expiresAt: true } + }) + + if (!storedToken || storedToken.userId !== token.userId || storedToken.revokedAt || storedToken.expiresAt <= new Date()) { + return null + } + + await prisma.refreshToken.update({ + where: { id: storedToken.id }, + data: { revokedAt: new Date() } + }) + return findUserById(token.userId) } export async function revokeRefreshToken(refreshToken: string): Promise { - await redis.del(refreshTokenKey(refreshToken)) + await Promise.all([ + redis.del(refreshTokenKey(refreshToken)), + prisma.refreshToken.updateMany({ + where: { + tokenHash: hashToken(refreshToken), + revokedAt: null + }, + data: { revokedAt: new Date() } + }) + ]) } export async function createHandoffToken(userId: string, audienceApp: string): Promise {