import { type AuthRequest } from '@cloudflare/workers-oauth-provider'
import { type KVNamespace } from '@cloudflare/workers-types'
export interface AuthSession {
sessionId: string
clientId: string
timestamp: number
expiresAt: number
}
interface AuthState {
sessionId: string
authRequest: AuthRequest
}
/**
* Encode auth state with HMAC signature for tamper protection
*/
export async function encodeAuthState(
state: AuthState,
secret: string,
): Promise<string> {
const stateJson = JSON.stringify(state)
const encoder = new TextEncoder()
// Create HMAC key
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign'],
)
// Sign the state
const signature = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(stateJson),
)
// Combine state and signature
const combined = {
data: btoa(stateJson),
sig: btoa(String.fromCharCode(...new Uint8Array(signature))),
}
return btoa(JSON.stringify(combined))
}
/**
* Decode and verify auth state
*/
export async function decodeAuthState(
encodedState: string,
secret: string,
): Promise<AuthState | null> {
try {
const combined = JSON.parse(atob(encodedState))
const stateJson = atob(combined.data)
const signature = new Uint8Array(
atob(combined.sig)
.split('')
.map((c) => c.charCodeAt(0)),
)
const encoder = new TextEncoder()
// Create HMAC key
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify'],
)
// Verify signature
const valid = await crypto.subtle.verify(
'HMAC',
key,
signature,
encoder.encode(stateJson),
)
if (!valid) {
return null
}
return JSON.parse(stateJson) as AuthState
} catch {
log.error('Error decoding auth state')
return null
}
}
/**
* Clean up expired auth sessions from KV
*/
export async function cleanupExpiredRequests(kv: KVNamespace): Promise<void> {
// This is a simple implementation - in production you might want
// to use a more efficient approach like a scheduled worker
const { keys } = await kv.list({ prefix: 'auth_session:' })
for (const key of keys) {
const data = await kv.get(key.name)
if (data) {
try {
const session: AuthSession = JSON.parse(data)
if (Date.now() > session.expiresAt) {
await kv.delete(key.name)
}
} catch {
// Invalid data, delete it
await kv.delete(key.name)
}
}
}
}
export const log = {
info: (msg: string, data?: any) =>
console.log('INFO:', msg, data ? JSON.stringify(data) : ''),
error: (msg: string, data?: any) =>
console.error('ERROR:', msg, data ? JSON.stringify(data) : ''),
warn: (msg: string, data?: any) =>
console.warn('WARN:', msg, data ? JSON.stringify(data) : ''),
}