oak-general-business/lib/endpoints/oauth.js

313 lines
11 KiB
JavaScript
Raw 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.

"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 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 } = 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,
},
filter: {
code,
}
}, {});
// 找不到记录
if (!authCodeRecord) {
await context.commit();
return {
statusCode: 400,
data: { error: "invalid_grant", error_description: "Invalid authorization code", 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 }
};
}
// 创建accessToken
const genaccessToken = (0, crypto_1.randomUUID)();
const expiresIn = 3600; // 1 hour
const refreshToken = (0, crypto_1.randomUUID)();
const refreshTokenExpiresIn = 86400 * 30; // 30 days
// 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,
},
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 endpoints = {
'oauth/access_token': oauthTokenEndpoint,
'oauth/userinfo': oauthUserInfoEndpoint,
'oauth/token': refreshTokenEndpoint,
};
exports.default = endpoints;