初次提交
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
|
||||
const SECRET_KEY = process.env.OAUTH_STATE_SECRET
|
||||
|
||||
if (!SECRET_KEY) {
|
||||
throw new Error('OAUTH_STATE_SECRET environment variable is required')
|
||||
}
|
||||
|
||||
export interface OAuthState {
|
||||
target: string
|
||||
csrf: string
|
||||
timestamp: number
|
||||
provider?: string
|
||||
}
|
||||
|
||||
// 生成 OAuth 状态参数
|
||||
export const generateState = (
|
||||
targetOrigin: string,
|
||||
provider?: string
|
||||
): { state: string; csrf: string } => {
|
||||
const csrf = randomBytes(32).toString('hex')
|
||||
const payload: OAuthState = {
|
||||
target: targetOrigin,
|
||||
csrf,
|
||||
timestamp: Date.now(),
|
||||
provider
|
||||
}
|
||||
const json = JSON.stringify(payload)
|
||||
const state = CryptoJS.AES.encrypt(json, SECRET_KEY).toString()
|
||||
return { state, csrf }
|
||||
}
|
||||
|
||||
// 解析 OAuth 状态参数
|
||||
export const parseState = (
|
||||
stateStr: string,
|
||||
expectedOrigin?: string,
|
||||
expectedCsrf?: string
|
||||
): OAuthState | null => {
|
||||
try {
|
||||
const bytes = CryptoJS.AES.decrypt(stateStr, SECRET_KEY)
|
||||
const json = bytes.toString(CryptoJS.enc.Utf8)
|
||||
if (!json) return null
|
||||
|
||||
const payload = JSON.parse(json)
|
||||
|
||||
// 检查时间戳是否过期(例如:10分钟)
|
||||
if (Date.now() - payload.timestamp > 10 * 60 * 1000) {
|
||||
console.error('OAuth state expired')
|
||||
return null
|
||||
}
|
||||
|
||||
// 验证 target origin
|
||||
if (expectedOrigin && payload.target !== expectedOrigin) {
|
||||
console.error('OAuth state target mismatch')
|
||||
return null
|
||||
}
|
||||
|
||||
// 验证 CSRF
|
||||
if (!expectedCsrf || payload.csrf !== expectedCsrf) {
|
||||
console.error('OAuth state CSRF mismatch or missing cookie')
|
||||
return null
|
||||
}
|
||||
|
||||
return payload
|
||||
} catch (e) {
|
||||
console.error('Failed to parse OAuth state', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const getRedirectUri = (provider: string): string => {
|
||||
let redirectUri = process.env.OAUTH_REDIRECT_URI
|
||||
if (!redirectUri) {
|
||||
throw createError({ statusCode: 500, message: 'OAUTH_REDIRECT_URI not configured' })
|
||||
}
|
||||
|
||||
// 支持 [provider] 占位符
|
||||
redirectUri = redirectUri.replace('[provider]', provider)
|
||||
|
||||
// 兼容用户可能错误地将 "provider" 作为字面量填写的情况
|
||||
if (redirectUri.includes('/provider/callback')) {
|
||||
redirectUri = redirectUri.replace('/provider/callback', `/${provider}/callback`)
|
||||
}
|
||||
|
||||
return redirectUri
|
||||
}
|
||||
Reference in New Issue
Block a user