feat: implement auth system, passkeys, and user management
Add PostgreSQL and Redis integration for users and sessions Implement password and WebAuthn passkey login flows Add Docker stack, super-admin seeding, and protected routes
This commit is contained in:
103
server/utils/session.ts
Normal file
103
server/utils/session.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import type { H3Event } from 'h3'
|
||||
|
||||
import { deleteCookie, getCookie, getRequestURL, setCookie } from 'h3'
|
||||
|
||||
import { randomToken } from './base64url'
|
||||
import { getRedisClient } from './redis'
|
||||
|
||||
const DEFAULT_SESSION_TTL_SECONDS = 60 * 60 * 24 * 7
|
||||
const SHORT_SESSION_TTL_SECONDS = 60 * 60 * 24
|
||||
|
||||
interface StoredSession {
|
||||
id: string
|
||||
userId: string
|
||||
remember: boolean
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
function getSessionCookieName() {
|
||||
const config = useRuntimeConfig()
|
||||
return config.sessionCookieName || 'dinner_ticket_session'
|
||||
}
|
||||
|
||||
function getSessionStorageKey(token: string) {
|
||||
return `auth:session:${token}`
|
||||
}
|
||||
|
||||
function shouldUseSecureCookies(event: H3Event) {
|
||||
const url = getRequestURL(event)
|
||||
return url.protocol === 'https:'
|
||||
}
|
||||
|
||||
function getSessionTtl(remember: boolean) {
|
||||
return remember ? DEFAULT_SESSION_TTL_SECONDS : SHORT_SESSION_TTL_SECONDS
|
||||
}
|
||||
|
||||
export async function createUserSession(event: H3Event, input: {
|
||||
userId: string
|
||||
remember: boolean
|
||||
}) {
|
||||
const token = randomToken(32)
|
||||
const session: StoredSession = {
|
||||
id: randomUUID(),
|
||||
userId: input.userId,
|
||||
remember: input.remember,
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
const redis = await getRedisClient()
|
||||
const ttl = getSessionTtl(input.remember)
|
||||
|
||||
await redis.set(getSessionStorageKey(token), JSON.stringify(session), {
|
||||
expiration: {
|
||||
type: 'EX',
|
||||
value: ttl
|
||||
}
|
||||
})
|
||||
|
||||
setCookie(event, getSessionCookieName(), token, {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
secure: shouldUseSecureCookies(event),
|
||||
path: '/',
|
||||
maxAge: ttl
|
||||
})
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
export async function getUserSession(event: H3Event): Promise<StoredSession | null> {
|
||||
const token = getCookie(event, getSessionCookieName())
|
||||
|
||||
if (!token) {
|
||||
return null
|
||||
}
|
||||
|
||||
const redis = await getRedisClient()
|
||||
const raw = await redis.get(getSessionStorageKey(token))
|
||||
|
||||
if (!raw) {
|
||||
deleteCookie(event, getSessionCookieName(), { path: '/' })
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(raw) as StoredSession
|
||||
} catch {
|
||||
await redis.del(getSessionStorageKey(token))
|
||||
deleteCookie(event, getSessionCookieName(), { path: '/' })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function destroyUserSession(event: H3Event) {
|
||||
const token = getCookie(event, getSessionCookieName())
|
||||
|
||||
if (token) {
|
||||
const redis = await getRedisClient()
|
||||
await redis.del(getSessionStorageKey(token))
|
||||
}
|
||||
|
||||
deleteCookie(event, getSessionCookieName(), { path: '/' })
|
||||
}
|
||||
Reference in New Issue
Block a user