189 lines
8.1 KiB
JavaScript
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;
|