oak-general-business/lib/triggers/oauthUser.js

195 lines
8.5 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const assert_1 = tslib_1.__importDefault(require("assert"));
const Projection_1 = require("../types/Projection");
const uuid_1 = require("oak-domain/lib/utils/uuid");
const index_backend_1 = require("../utils/cos/index.backend");
const oauth_1 = require("../utils/oauth");
const triggers = [
{
name: "加载用户信息",
action: "loadUserInfo",
when: "before",
entity: "oauthUser",
fn: async ({ operation }, context) => {
const { filter } = operation;
(0, assert_1.default)(filter && filter.id, "loadUserInfo 操作必须指定 filter.id ");
const [findOauthUser] = await context.select("oauthUser", {
data: {
id: 1,
userId: 1,
providerUserId: 1,
rawUserInfo: 1,
user: {
id: 1,
name: 1,
nickname: 1,
extraFile$entity: {
$entity: 'extraFile',
data: Projection_1.extraFileProjection,
filter: {
tag1: 'avatar',
}
},
},
providerConfig: {
type: 1,
},
},
filter: {
id: filter.id,
},
}, {});
(0, assert_1.default)(findOauthUser, `oauthUser ${filter.id} 不存在`);
(0, assert_1.default)(findOauthUser.user, `oauthUser ${filter.id} 关联的 user 不存在`);
(0, assert_1.default)(findOauthUser.providerConfig, `oauthUser ${filter.id} 关联的 providerConfig 不存在`);
const providerType = findOauthUser.providerConfig.type;
let updateUserInfo = {};
let newAvatarUrl = null;
try {
const { id, name, nickname, birth, gender, avatarUrl } = await (0, oauth_1.processUserInfo)(providerType, findOauthUser.rawUserInfo || {});
updateUserInfo = {
name: name || findOauthUser.user.name,
nickname: nickname || findOauthUser.user.nickname,
birth: birth || findOauthUser.user.birth,
gender: gender || findOauthUser.user.gender,
};
newAvatarUrl = avatarUrl;
}
catch (err) {
console.error(`处理 oauthUser ${filter.id} 的 rawUserInfo 失败: 将使用默认生成数据`, err);
throw err;
}
const { extraFile$entity, id } = findOauthUser.user;
const application = context.getApplication();
if (newAvatarUrl &&
(extraFile$entity?.length === 0 ||
await (0, index_backend_1.composeFileUrlBackend)(application, extraFile$entity[0], context) !== newAvatarUrl)) {
console.log("需要更新用户头像为:", newAvatarUrl);
// 需要更新新的avatar extra file
const extraFileOperations = [
{
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'create',
data: Object.assign({
id: await (0, uuid_1.generateNewIdAsync)(),
tag1: 'avatar',
entity: 'user',
entityId: findOauthUser.user.id,
objectId: await (0, uuid_1.generateNewIdAsync)(),
origin: 'unknown',
extra1: newAvatarUrl,
type: 'image',
filename: '',
bucket: '',
applicationId: context.getApplicationId(),
}),
},
];
if (extraFile$entity.length > 0) {
extraFileOperations.push({
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'remove',
data: {},
filter: {
id: extraFile$entity[0].id,
},
});
}
Object.assign(updateUserInfo, {
extraFile$entity: extraFileOperations,
});
}
if (Object.keys(updateUserInfo).length > 0) {
// 更新用户基本信息
await context.operate('user', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: updateUserInfo,
filter: {
id: id,
}
}, {});
}
return 0;
}
},
{
name: "刷新令牌",
action: "refreshTokens",
when: "before",
entity: "oauthUser",
fn: async ({ operation }, context) => {
const { filter } = operation;
const oauthUsers = await context.select("oauthUser", {
data: {
id: 1,
accessToken: 1,
accessExpiresAt: 1,
refreshToken: 1,
refreshExpiresAt: 1,
state: {
provider: {
refreshEndpoint: 1,
clientId: 1,
clientSecret: 1,
}
}
},
filter: filter,
}, {});
if (oauthUsers.length === 0) {
return 0;
}
for (const oauthUser of oauthUsers) {
(0, assert_1.default)(oauthUser.state, `oauthUser ${oauthUser.id} 关联的 state 不存在`);
(0, assert_1.default)(oauthUser.state.provider, `oauthUser ${oauthUser.id} 关联的 state 的 provider 不存在`);
const refreshEndpoint = oauthUser.state.provider.refreshEndpoint;
if (!refreshEndpoint) {
console.warn(`oauthUser ${oauthUser.id} 关联的 provider 不支持刷新令牌,跳过`);
continue;
}
// 根据 RFC 6749 规范 使用 refresh token 刷新 access token
const authHeaderRaw = Buffer.from(`${oauthUser.state.provider.clientId}:${oauthUser.state.provider.clientSecret}`).toString('base64');
const resp = await fetch(refreshEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': `Basic ${authHeaderRaw}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: oauthUser.refreshToken,
}),
});
if (!resp.ok) {
console.error(`刷新 oauthUser ${oauthUser.id} 令牌失败: `, await resp.text());
continue;
}
const tokenData = await resp.json();
const now = new Date();
const newAccessToken = tokenData.access_token;
const newRefreshToken = tokenData.refresh_token || oauthUser.refreshToken;
const accessExpiresAt = tokenData.expires_in ? new Date(now.getTime() + tokenData.expires_in * 1000) : oauthUser.accessExpiresAt;
const refreshExpiresAt = tokenData.refresh_expires_in ? new Date(now.getTime() + tokenData.refresh_expires_in * 1000) : oauthUser.refreshExpiresAt;
await context.operate('oauthUser', {
id: await (0, uuid_1.generateNewIdAsync)(),
action: 'update',
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: accessExpiresAt,
refreshExpiresAt: refreshExpiresAt,
},
filter: {
id: oauthUser.id,
}
}, {});
}
return 0;
}
},
];
exports.default = triggers;