import { z } from 'zod'; export const codeChallengeMethods = z.enum(['plain', 'S256']); // 授权请求Schema export const authorizationRequestSchema = z.object({ response_type: z.string().min(1, '响应类型不能为空'), client_id: z.string().min(1, '客户端ID不能为空'), redirect_uri: z.string().url('重定向URI必须是有效的URL'), scope: z.string().min(1, '作用域不能为空'), state: z.string().optional(), nonce: z.string().optional(), code_challenge: z.string().optional(), code_challenge_method: codeChallengeMethods.optional(), prompt: z.string().optional(), max_age: z.number().int().positive().optional(), id_token_hint: z.string().optional(), login_hint: z.string().optional(), acr_values: z.string().optional(), }).strict(); // 令牌请求Schema export const tokenRequestSchema = z.object({ grant_type: z.string().min(1, '授权类型不能为空'), code: z.string().optional(), redirect_uri: z.string().url('重定向URI必须是有效的URL').optional().or(z.literal('')), client_id: z.string().min(1, '客户端ID不能为空'), client_secret: z.string().optional(), refresh_token: z.string().optional(), scope: z.string().optional(), code_verifier: z.string().optional(), }).strict(); // 令牌撤销请求Schema export const revokeTokenRequestSchema = z.object({ token: z.string().min(1, '令牌不能为空'), token_type_hint: z.enum(['access_token', 'refresh_token']).optional(), client_id: z.string().optional(), }).strict(); // 令牌内省请求Schema export const introspectTokenRequestSchema = z.object({ token: z.string().min(1, '令牌不能为空'), token_type_hint: z.enum(['access_token', 'refresh_token']).optional(), client_id: z.string().optional(), }).strict(); // 查询参数解析Schema(用于解析URL参数) export const authorizationQuerySchema = z.record(z.string(), z.union([z.string(), z.array(z.string())])).transform((data) => { // 将数组参数转换为单个字符串(取第一个值) const normalized: Record = {}; for (const [key, value] of Object.entries(data)) { if (Array.isArray(value)) { // 处理数组,取第一个值,如果为空则设为空字符串 normalized[key] = value[0] || ''; } else { // 处理字符串值 normalized[key] = value || ''; } } return normalized; }); // Form data解析Schema export const tokenFormDataSchema = z.instanceof(FormData).transform((formData) => { const result: Record = {}; for (const [key, value] of formData.entries()) { // 只处理字符串值,忽略File类型 if (typeof value === 'string') { result[key] = value; } else if (value instanceof File) { // 如果是文件,将文件名作为值(通常不应该在token请求中出现) result[key] = value.name || ''; } } return result; }); // HTTP Authorization header Schema export const bearerTokenSchema = z.string().regex(/^Bearer\s+(.+)$/, '无效的Bearer令牌格式').transform((auth) => { return auth.replace(/^Bearer\s+/, ''); }); export const basicAuthSchema = z.string().regex(/^Basic\s+(.+)$/, '无效的Basic认证格式').transform((auth) => { try { const base64Part = auth.replace(/^Basic\s+/, ''); if (!base64Part) { throw new Error('Basic认证缺少凭证部分'); } const decoded = atob(base64Part); const colonIndex = decoded.indexOf(':'); if (colonIndex === -1) { // 如果没有冒号,整个字符串作为用户名,密码为空 return { username: decoded, password: '' }; } const username = decoded.substring(0, colonIndex); const password = decoded.substring(colonIndex + 1); return { username, password }; } catch (error) { throw new Error(`无效的Basic认证编码: ${error instanceof Error ? error.message : '未知错误'}`); } });