shared/lib/auth/jwt-edge.ts

63 lines
2.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// shared/lib/auth/jwt-edge.ts — V2 Edge Runtime JWT 验证(步骤 1a 新增)
// 使用 Web Crypto APIEdge Runtime 兼容3 参数强制校验 iss 匹配
import type { SessionPayloadV2 } from './types-v2'
/** Base64url 解码为 Uint8Array */
function base64urlToBytes(str: string): Uint8Array {
const base64 = str.replace(/-/g, '+').replace(/_/g, '/')
const padding = '='.repeat((4 - (base64.length % 4)) % 4)
const binary = atob(base64 + padding)
const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i)
}
return bytes
}
/** 将 ArrayBuffer 转为 hex 字符串 */
function bufferToHex(buffer: ArrayBuffer): string {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}
/** 验证 JWTEdge Runtime3 参数。expectedIss = '*' 时跳过 iss 校验(迁移模式) */
export async function verifyJwtEdge(
token: string,
secret: string,
expectedIss: string
): Promise<SessionPayloadV2 | null> {
try {
const parts = token.split('.')
if (parts.length !== 3) return null
// 解码 payload
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))) as SessionPayloadV2
// 过期检查
if (payload.exp && payload.exp * 1000 < Date.now()) return null
// iss 校验(* 为迁移模式,跳过校验,兼容旧 token 不含 iss
if (expectedIss !== '*' && payload.iss !== expectedIss) return null
// 验签HMAC-SHA256
const encoder = new TextEncoder()
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['verify']
)
const data = encoder.encode(`${parts[0]}.${parts[1]}`)
const signature = base64urlToBytes(parts[2])
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const valid = await crypto.subtle.verify('HMAC', key, signature as any, data)
return valid ? payload : null
} catch {
return null
}
}