58 lines
1.7 KiB
TypeScript
58 lines
1.7 KiB
TypeScript
// shared/lib/auth/jwt.ts — 共享 JWT 签发/验证(HS256,跨站点 cookie)
|
||
import crypto from 'crypto'
|
||
|
||
export interface SignJwtOptions {
|
||
secret: string
|
||
payload: Record<string, unknown>
|
||
expiresInSeconds?: number
|
||
}
|
||
|
||
export function signJwt({ secret, payload, expiresInSeconds = 604800 }: SignJwtOptions): string {
|
||
const header = { alg: 'HS256', typ: 'JWT' }
|
||
const now = Math.floor(Date.now() / 1000)
|
||
const body = { ...payload, iat: now, exp: now + expiresInSeconds }
|
||
|
||
const encoded = (obj: object) => Buffer.from(JSON.stringify(obj)).toString('base64url')
|
||
const signature = crypto
|
||
.createHmac('sha256', secret)
|
||
.update(`${encoded(header)}.${encoded(body)}`)
|
||
.digest('base64url')
|
||
|
||
return `${encoded(header)}.${encoded(body)}.${signature}`
|
||
}
|
||
|
||
export function verifyJwt(token: string, secret: string): Record<string, unknown> | null {
|
||
try {
|
||
const parts = token.split('.')
|
||
if (parts.length !== 3) return null
|
||
|
||
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString())
|
||
if (payload.exp && payload.exp * 1000 < Date.now()) return null
|
||
|
||
const signature = crypto
|
||
.createHmac('sha256', secret)
|
||
.update(`${parts[0]}.${parts[1]}`)
|
||
.digest('base64url')
|
||
|
||
return signature === parts[2] ? payload : null
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
// 签发共享 tlyq_session cookie 配置
|
||
export function sharedCookieConfig(domain: string, maxAge = 604800): Record<string, unknown> {
|
||
return {
|
||
name: 'tlyq_session',
|
||
value: '',
|
||
options: {
|
||
httpOnly: true,
|
||
secure: process.env.NODE_ENV === 'production',
|
||
sameSite: 'lax' as const,
|
||
domain,
|
||
path: '/',
|
||
maxAge,
|
||
},
|
||
}
|
||
}
|