"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const uuid_1 = require("oak-domain/lib/utils/uuid"); const crypto_1 = require("crypto"); const index_backend_1 = require("../utils/cos/index.backend"); const assert_1 = tslib_1.__importDefault(require("assert")); const oauth_1 = require("../utils/oauth"); const crypto_2 = require("crypto"); // PKCE 验证函数 async function verifyPKCE(verifier, challenge, method) { if (method === 'plain') { return verifier === challenge; } else if (method === 'S256') { const hash = (0, crypto_2.createHash)('sha256').update(verifier).digest(); const computed = hash.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); return computed === challenge; } return false; } const oauthTokenEndpoint = { name: "获取OAuth Token", params: [], method: 'post', type: "free", fn: async (contextBuilder, params, header, req, body) => { const context = await contextBuilder(); const { client_id, client_secret, grant_type, code, redirect_uri, code_verifier // PKCE支持 } = body; const [app] = await context.select("oauthApplication", { data: { id: 1, clientSecret: 1, }, filter: { id: client_id, } }, {}); if (!app) { await context.commit(); return { statusCode: 400, data: { error: "invalid_client", error_description: "Client not found", success: false } }; } if (app.clientSecret !== client_secret) { await context.commit(); return { statusCode: 400, data: { error: "invalid_client", error_description: "Client secret mismatch", success: false } }; } // grant_type几种类型, 目前只支持authorization_code if (grant_type !== "authorization_code") { await context.commit(); return { statusCode: 400, data: { error: "unsupported_grant_type", error_description: "Only authorization_code grant type is supported", success: false } }; } // 找code的记录 const [authCodeRecord] = await context.select("oauthAuthorizationCode", { data: { id: 1, code: 1, redirectUri: 1, userId: 1, expiresAt: 1, usedAt: 1, codeChallenge: 1, codeChallengeMethod: 1, }, filter: { code, } }, {}); // 找不到记录 if (!authCodeRecord) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Invalid authorization code", success: false } }; } // PKCE 验证 if (authCodeRecord.codeChallenge) { if (!code_verifier) { await context.commit(); return { statusCode: 400, data: { error: "invalid_request", error_description: "code_verifier is required", success: false } }; } // 验证 code_verifier const isValid = await verifyPKCE(code_verifier, authCodeRecord.codeChallenge, authCodeRecord.codeChallengeMethod); if (!isValid) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Invalid code_verifier", success: false } }; } } // 验证redirect_uri if (authCodeRecord.redirectUri !== redirect_uri) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Redirect URI mismatch", success: false } }; } // 验证过期 if (authCodeRecord.expiresAt < Date.now()) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Authorization code expired", success: false } }; } // 验证是否已使用 if (authCodeRecord.usedAt) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Authorization code already used", success: false } }; } // 如果userId,code里面的appId都一样,则说明是这个用户在这个应用下第二次授权的,直接返回 const [existingToken] = await context.select("oauthToken", { data: { id: 1, accessToken: 1, refreshToken: 1, accessExpiresAt: 1, refreshExpiresAt: 1, }, filter: { userId: authCodeRecord.userId, code: { oauthAppId: app.id, }, revokedAt: { $exists: false, } } }, {}); // 如果存在且未过期,直接返回 if (existingToken && (existingToken.accessExpiresAt > Date.now())) { console.log("Existing valid token found, returning it directly"); // 刷新最后一次使用 await context.operate("oauthToken", { id: await (0, uuid_1.generateNewIdAsync)(), action: "update", data: { lastUsedAt: Date.now(), }, filter: { id: existingToken.id, } }, {}); // // 创建记录 // await context.operate("oauthUserAuthorization", { // id: await generateNewIdAsync(), // action: "update", // data: { // tokenId: existingToken.id, // usageState: 'granted', // }, // filter: { // codeId: authCodeRecord.id, // } // }, {}) await context.commit(); return { statusCode: 200, data: { access_token: existingToken.accessToken, token_type: "Bearer", expires_in: existingToken.accessExpiresAt - Date.now(), refresh_token: existingToken.refreshToken, refresh_expires_in: existingToken.refreshExpiresAt - Date.now(), success: true, } }; } const expiresIn = 3600; // 1 hour const refreshTokenExpiresIn = 86400 * 30; // 30 days // 如果过期就顺带刷新了(refresh token没过期, accessToken过期了) if (existingToken && (existingToken.refreshExpiresAt > Date.now())) { console.log("Existing token expired, refreshing it"); const newAccessToken = (0, crypto_1.randomUUID)(); const newRefreshToken = (0, crypto_1.randomUUID)(); await context.operate("oauthToken", { id: await (0, uuid_1.generateNewIdAsync)(), action: "update", data: { lastUsedAt: Date.now(), accessToken: newAccessToken, refreshToken: newRefreshToken, accessExpiresAt: Date.now() + expiresIn * 1000, refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, } }, {}); // // 创建记录 // await context.operate("oauthUserAuthorization", { // id: await generateNewIdAsync(), // action: "update", // data: { // tokenId: existingToken.id, // usageState: 'granted', // }, // filter: { // codeId: authCodeRecord.id, // } // }, {}) await context.commit(); return { statusCode: 200, data: { access_token: newAccessToken, token_type: "Bearer", expires_in: expiresIn, refresh_token: newRefreshToken, refresh_expires_in: refreshTokenExpiresIn, success: true, } }; } // 有一种情况是access和refresh都过期了,但是用户又重新用code来换token // 这种情况下不能刷新老的,也要当作新的处理 // 创建accessToken const genaccessToken = (0, crypto_1.randomUUID)(); const refreshToken = (0, crypto_1.randomUUID)(); console.log("Creating new access token and refresh token"); // create const tokenId = await (0, uuid_1.generateNewIdAsync)(); await context.operate("oauthToken", { id: await (0, uuid_1.generateNewIdAsync)(), action: "create", data: { id: tokenId, accessToken: genaccessToken, refreshToken: refreshToken, userId: authCodeRecord.userId, accessExpiresAt: Date.now() + expiresIn * 1000, refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, codeId: authCodeRecord.id, } }, {}); // 创建记录 await context.operate("oauthUserAuthorization", { id: await (0, uuid_1.generateNewIdAsync)(), action: "update", data: { tokenId: tokenId, usageState: 'granted', }, filter: { codeId: authCodeRecord.id, } }, {}); // 标记code为已使用 await context.operate("oauthAuthorizationCode", { id: await (0, uuid_1.generateNewIdAsync)(), action: "update", data: { usedAt: Date.now(), }, filter: { id: authCodeRecord.id, } }, {}); await context.commit(); return { statusCode: 200, data: { access_token: genaccessToken, token_type: "Bearer", expires_in: expiresIn, refresh_token: refreshToken, refresh_expires_in: refreshTokenExpiresIn, success: true, } }; } }; const oauthUserInfoEndpoint = { name: "获取OAuth用户信息", params: [], method: 'get', type: "free", fn: async (contextBuilder, params, header, req, body) => { const context = await contextBuilder(); const token = header.authorization; // Bearer token const checkResult = await (0, oauth_1.checkOauthTokenAvaliable)(context, token); if (checkResult.error) { await context.commit(); return { statusCode: checkResult.statusCode || 401, data: { error: checkResult.error, success: false } }; } const tokenRecord = checkResult.tokenRecord; (0, assert_1.default)(tokenRecord?.user, "User must be present in token record"); (0, assert_1.default)(tokenRecord?.code?.application, "Application must be present in token record"); const extrafile = tokenRecord.user.extraFile$entity?.[0]; const application = tokenRecord.code.application; let avatarUrl = ''; if (extrafile) { avatarUrl = (0, index_backend_1.composeFileUrl)(application, extrafile); } await context.commit(); return { statusCode: 200, data: { userInfo: { id: tokenRecord.user.id, name: tokenRecord.user.name, nickname: tokenRecord.user.nickname, birth: tokenRecord.user.birth, gender: tokenRecord.user.gender, avatarUrl: avatarUrl, }, error: null } }; } }; const refreshTokenEndpoint = { name: "刷新OAuth令牌", params: [], method: 'post', type: "free", fn: async (contextBuilder, params, header, req, body) => { const { refresh_token, grant_type } = body; const { authorization } = header; // 暂时只支持 refresh_token 模式 if (grant_type !== "refresh_token") { return { statusCode: 400, data: { error: "unsupported_grant_type", error_description: "Only refresh_token grant type is supported", success: false } }; } // 根据 RFC 6749 规范,请求参数在 Body 中,以表单格式(application/x-www-form-urlencoded)提交。 // authorization header 中包含 client_id 和 client_secret 的 Base64 编码 const decodedAuth = Buffer.from((authorization || '').split(' ')[1] || '', 'base64').toString('utf-8'); const [client_id, client_secret] = decodedAuth.split(':'); if (!client_id || !client_secret) { return { statusCode: 401, data: { error: "invalid_client", error_description: "Missing client credentials", success: false } }; } if (!refresh_token) { return { statusCode: 400, data: { error: "invalid_request", error_description: "Missing refresh_token", success: false } }; } const context = await contextBuilder(); const [oauthApp] = await context.select("oauthApplication", { data: { id: 1, }, filter: { clientSecret: client_secret, id: client_id, } }, {}); if (!oauthApp) { await context.commit(); return { statusCode: 401, data: { error: "invalid_client", error_description: "Client authentication failed", success: false } }; } const [tokenRecord] = await context.select("oauthToken", { data: { id: 1, userId: 1, accessToken: 1, accessExpiresAt: 1, refreshToken: 1, refreshExpiresAt: 1, code: { applicationId: 1, oauthApp: { id: 1, } } }, filter: { refreshToken: refresh_token, code: { oauthAppId: oauthApp.id, } } }, {}); if (!tokenRecord) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Invalid refresh token", success: false } }; } if (tokenRecord.refreshExpiresAt < Date.now()) { await context.commit(); return { statusCode: 400, data: { error: "invalid_grant", error_description: "Refresh token expired", success: false } }; } // 生成新的令牌 const newAccessToken = (0, crypto_1.randomUUID)(); const newRefreshToken = (0, crypto_1.randomUUID)(); const expiresIn = 3600; // 1 hour const refreshTokenExpiresIn = 86400 * 30; // 30 days await context.operate("oauthToken", { id: await (0, uuid_1.generateNewIdAsync)(), action: "update", data: { accessToken: newAccessToken, refreshToken: newRefreshToken, accessExpiresAt: Date.now() + expiresIn * 1000, refreshExpiresAt: Date.now() + refreshTokenExpiresIn * 1000, }, filter: { id: tokenRecord.id, } }, {}); await context.commit(); return { statusCode: 200, data: { access_token: newAccessToken, token_type: "Bearer", expires_in: expiresIn, refresh_token: newRefreshToken, refresh_expires_in: refreshTokenExpiresIn, success: true, } }; } }; const oauthRevocationEndpoint = { name: "撤销OAuth令牌", params: [], method: 'post', type: "free", fn: async (contextBuilder, params, header, req, body) => { const { token, token_type_hint } = body; const { authorization } = header; // 1. 验证请求参数 if (!token) { return { statusCode: 400, data: { error: "invalid_request", error_description: "Missing token parameter", success: false } }; } // 2. 客户端认证(使用 Basic Auth) const decodedAuth = Buffer.from((authorization || '').split(' ')[1] || '', 'base64').toString('utf-8'); const [client_id, client_secret] = decodedAuth.split(':'); if (!client_id || !client_secret) { return { statusCode: 401, data: { error: "invalid_client", error_description: "Missing client credentials", success: false } }; } const context = await contextBuilder(); const [oauthApp] = await context.select("oauthApplication", { data: { id: 1 }, filter: { clientSecret: client_secret, id: client_id } }, {}); if (!oauthApp) { await context.commit(); return { statusCode: 401, data: { error: "invalid_client", error_description: "Client authentication failed", success: false } }; } // 3. 查找令牌记录 let tokenRecord = null; const tokenProjection = { data: { id: 1, code: { oauthAppId: 1 } }, filter: {} }; // 尝试查找 Refresh Token if (!token_type_hint || token_type_hint === 'refresh_token') { tokenProjection.filter = { refreshToken: token, code: { oauthAppId: oauthApp.id } }; [tokenRecord] = await context.select("oauthToken", tokenProjection, {}); } // 如果没找到,且 hint 不是 'refresh_token',则尝试查找 Access Token if (!tokenRecord && (!token_type_hint || token_type_hint === 'access_token')) { tokenProjection.filter = { accessToken: token, code: { oauthAppId: oauthApp.id } }; [tokenRecord] = await context.select("oauthToken", tokenProjection, {}); } // 4. 撤销操作(无论找到与否,都返回 200,但如果找到则执行失效操作) if (tokenRecord) { // const pastTime = Date.now() - 1000; // // 将 Access Token 和 Refresh Token 的过期时间都设为过去,使其立即失效 // await context.operate("oauthToken", { // id: await generateNewIdAsync(), // action: "update", // data: { // accessExpiresAt: pastTime, // refreshExpiresAt: pastTime, // }, // filter: { // id: tokenRecord.id, // } // }, {}); // 使用这个token的认证记录都撤销掉,在trigger里会自动设置 revokedAt await context.operate("oauthUserAuthorization", { id: await (0, uuid_1.generateNewIdAsync)(), action: "revoke", data: {}, filter: { tokenId: tokenRecord.id, } }, {}); } await context.commit(); // 5. RFC 7009 规定:令牌撤销成功或令牌无效时,返回 HTTP 200 return { statusCode: 200, data: {} }; } }; const endpoints = { 'oauth/access_token': oauthTokenEndpoint, 'oauth/userinfo': oauthUserInfoEndpoint, 'oauth/token': refreshTokenEndpoint, 'oauth/revoke': oauthRevocationEndpoint, }; exports.default = endpoints;