mirror of https://github.com/msgbyte/tailchat
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
158 lines
4.3 KiB
TypeScript
158 lines
4.3 KiB
TypeScript
import {
|
|
TcService,
|
|
TcDbService,
|
|
TcPureContext,
|
|
db,
|
|
UserStructWithToken,
|
|
} from 'tailchat-server-sdk';
|
|
import { isValidStaticAssetsUrl } from '../../../lib/utils';
|
|
import type { IAMDocument, IAMModel } from '../models/iam';
|
|
import { strategies } from '../strategies';
|
|
import type { StrategyType } from '../strategies/types';
|
|
|
|
/**
|
|
* Federated Identity Management
|
|
*
|
|
* Unified identity authentication
|
|
*/
|
|
interface IAMService extends TcService, TcDbService<IAMDocument, IAMModel> {}
|
|
class IAMService extends TcService {
|
|
get serviceName() {
|
|
return 'plugin:com.msgbyte.iam';
|
|
}
|
|
|
|
onInit() {
|
|
this.registerLocalDb(require('../models/iam').default);
|
|
|
|
const availableStrategies = strategies.filter((strategy) =>
|
|
strategy.checkAvailable()
|
|
);
|
|
|
|
this.registerAction('availableStrategies', () => {
|
|
return availableStrategies.map((s) => ({
|
|
name: s.name,
|
|
type: s.type,
|
|
icon: s.icon,
|
|
}));
|
|
});
|
|
|
|
availableStrategies.forEach((strategy) => {
|
|
const action = this.buildStrategyAction(strategy);
|
|
const strategyName = strategy.name;
|
|
this.registerAction(`${strategyName}.loginUrl`, action.loginUrl);
|
|
this.registerAction(`${strategyName}.redirect`, action.redirect);
|
|
|
|
this.registerAuthWhitelist([
|
|
`/${strategyName}/loginUrl`,
|
|
`/${strategyName}/redirect`,
|
|
]);
|
|
});
|
|
|
|
this.registerAuthWhitelist(['/availableStrategies']);
|
|
}
|
|
|
|
buildStrategyAction(strategy: StrategyType) {
|
|
const strategyName = strategy.name;
|
|
|
|
return {
|
|
loginUrl: async (ctx: TcPureContext) => {
|
|
return strategy.getUrl();
|
|
},
|
|
redirect: async (ctx: TcPureContext<{ code: string }>) => {
|
|
const code = ctx.params.code;
|
|
|
|
if (!code) {
|
|
throw new Error(JSON.stringify(ctx.params));
|
|
}
|
|
|
|
const providerUserInfo = await strategy.getUserInfo(code);
|
|
|
|
const iamRecord = await this.adapter.model.findOne({
|
|
provider: strategyName,
|
|
providerId: providerUserInfo.id,
|
|
});
|
|
|
|
if (!!iamRecord) {
|
|
// 存在记录,直接签发 token
|
|
const token = await ctx.call('user.signUserToken', {
|
|
userId: iamRecord.userId,
|
|
});
|
|
|
|
return generatePostMessageHtml({
|
|
type: 'token',
|
|
token: token,
|
|
});
|
|
}
|
|
|
|
const username = providerUserInfo.username;
|
|
const email =
|
|
providerUserInfo.email ||
|
|
`${username}.${strategyName}@iam.msgbyte.com`;
|
|
|
|
// 不存在记录,查找是否已经注册过,如果已经注册过需要绑定,如果没有注册过则创建账号并绑定用户关系
|
|
const userInfo = await ctx.call('user.findUserByEmail', {
|
|
email,
|
|
});
|
|
if (!!userInfo) {
|
|
// 用户已存在,需要登录后才能确定绑定关系
|
|
return generatePostMessageHtml({ type: 'existed' });
|
|
}
|
|
|
|
let avatar = providerUserInfo.avatar;
|
|
if (avatar && isValidStaticAssetsUrl(avatar)) {
|
|
try {
|
|
const { url } = (await ctx.call(
|
|
'file.saveFileWithUrl',
|
|
{
|
|
fileUrl: avatar,
|
|
},
|
|
{
|
|
timeout: 3000,
|
|
}
|
|
)) as { url: string };
|
|
avatar = url;
|
|
} catch (err) {
|
|
this.logger.error('Cannot storage avatar', avatar, err);
|
|
}
|
|
}
|
|
|
|
const newUserInfo: UserStructWithToken = await ctx.call(
|
|
'user.register',
|
|
{
|
|
email,
|
|
nickname: providerUserInfo.nickname,
|
|
password: String(new db.Types.ObjectId()), // random password
|
|
avatar,
|
|
}
|
|
);
|
|
|
|
await this.adapter.model.create({
|
|
provider: strategyName,
|
|
providerId: providerUserInfo.id,
|
|
userId: String(newUserInfo._id),
|
|
});
|
|
|
|
return generatePostMessageHtml({
|
|
type: 'token',
|
|
isNew: true,
|
|
token: newUserInfo.token,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
function generatePostMessageHtml(obj: Record<string, any>) {
|
|
const IAM_FE_URL = process.env.IAM_FE_URL || '*';
|
|
|
|
return {
|
|
__raw: true,
|
|
html: `<script>window.opener.postMessage(${JSON.stringify({
|
|
source: 'plugin:com.msgbyte.iam',
|
|
...obj,
|
|
})}, "${IAM_FE_URL}");</script><div>Waiting for main window close</div>`,
|
|
};
|
|
}
|
|
|
|
export default IAMService;
|