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

189 lines
8.1 KiB
JavaScript

import assert from 'assert';
import { extraFileProjection } from '../types/Projection';
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
import { composeFileUrlBackend } from '../utils/cos/index.backend';
import { processUserInfo } from '../utils/oauth';
const triggers = [
{
name: "加载用户信息",
action: "loadUserInfo",
when: "before",
entity: "oauthUser",
fn: async ({ operation }, context) => {
const { filter } = operation;
assert(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: extraFileProjection,
filter: {
tag1: 'avatar',
}
},
},
providerConfig: {
type: 1,
},
},
filter: {
id: filter.id,
},
}, {});
assert(findOauthUser, `oauthUser ${filter.id} 不存在`);
assert(findOauthUser.user, `oauthUser ${filter.id} 关联的 user 不存在`);
assert(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 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 composeFileUrlBackend(application, extraFile$entity[0], context) !== newAvatarUrl)) {
console.log("需要更新用户头像为:", newAvatarUrl);
// 需要更新新的avatar extra file
const extraFileOperations = [
{
id: await generateNewIdAsync(),
action: 'create',
data: Object.assign({
id: await generateNewIdAsync(),
tag1: 'avatar',
entity: 'user',
entityId: findOauthUser.user.id,
objectId: await generateNewIdAsync(),
origin: 'unknown',
extra1: newAvatarUrl,
type: 'image',
filename: '',
bucket: '',
applicationId: context.getApplicationId(),
}),
},
];
if (extraFile$entity.length > 0) {
extraFileOperations.push({
id: await 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 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) {
assert(oauthUser.state, `oauthUser ${oauthUser.id} 关联的 state 不存在`);
assert(oauthUser.state.provider, `oauthUser ${oauthUser.id} 关联的 state 的 provider 不存在`);
const refreshEndpoint = oauthUser.state.provider.refreshEndpoint;
assert(refreshEndpoint, `oauthUser ${oauthUser.id} 关联的 provider 不支持刷新令牌`);
// 根据 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 generateNewIdAsync(),
action: 'update',
data: {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
accessExpiresAt: accessExpiresAt,
refreshExpiresAt: refreshExpiresAt,
},
filter: {
id: oauthUser.id,
}
}, {});
}
return 0;
}
},
];
export default triggers;