63 lines
2.0 KiB
TypeScript
63 lines
2.0 KiB
TypeScript
// shared/lib/auth/jwt-edge.ts — V2 Edge Runtime JWT 验证(步骤 1a 新增)
|
||
// 使用 Web Crypto API(Edge 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('')
|
||
}
|
||
|
||
/** 验证 JWT(Edge Runtime),3 参数。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
|
||
}
|
||
}
|