import { DataTypes, Model, Sequelize } from 'sequelize'; import { useContextKey } from '@kevisual/context'; import { Redis } from 'ioredis'; import { SyncOpts, User } from './user.ts'; import { oauth } from '../oauth/auth.ts'; import { OauthUser } from '../oauth/oauth.ts'; export const redis = useContextKey('redis'); const UserSecretStatus = ['active', 'inactive', 'expired'] as const; const randomString = (length: number) => { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; }; type Data = { [key: string]: any; /** * 微信开放平台的某一个应用的openid */ wxOpenid?: string; /** * 微信开放平台的unionid:主要 */ wxUnionid?: string; /** * 微信公众号的openid:次要 */ wxmpOpenid?: string; } export class UserSecret extends Model { static oauth = oauth; declare id: string; declare token: string; declare userId: string; declare orgId: string; declare title: string; declare description: string; declare status: (typeof UserSecretStatus)[number]; declare expiredTime: Date; declare data: Data; /** * 验证token * @param token * @returns */ static async verifyToken(token: string) { if (!oauth.isSecretKey(token)) { return await oauth.verifyToken(token); } const secretToken = await oauth.verifyToken(token); if (secretToken) { console.log('verifyToken: verified as normal token'); return secretToken; } console.log('verifyToken: try to verify as secret key'); const userSecret = await UserSecret.findOne({ where: { token }, }); if (!userSecret) { return null; // 如果没有找到对应的用户密钥,则返回null } if (userSecret.isExpired()) { return null; // 如果用户密钥已过期,则返回null } if (userSecret.status !== 'active') { return null; // 如果用户密钥状态不是active,则返回null } // 如果用户密钥未过期,则返回用户信息 const oauthUser = await userSecret.getOauthUser(); if (!oauthUser) { return null; // 如果没有找到对应的oauth用户,则返回null } await oauth.saveSecretKey(oauthUser, userSecret.token); // 存储到oauth中的token store中 return oauthUser; } /** * owner 组织用户的 oauthUser * @returns */ async getOauthUser(opts?: { wx?: boolean }) { const user = await User.findOne({ where: { id: this.userId }, attributes: ['id', 'username', 'type', 'owner', 'data'], }); let org: User = null; if (!user) { return null; // 如果没有找到对应的用户,则返回null } const expiredTime = this.expiredTime ? new Date(this.expiredTime).getTime() : null; const oauthUser: Partial = { id: user.id, username: user.username, type: 'user', oauthExpand: { expiredTime: expiredTime, }, }; if (this.orgId) { org = await User.findOne({ where: { id: this.orgId }, attributes: ['id', 'username', 'type', 'owner'], }); if (org) { oauthUser.id = org.id; oauthUser.username = org.username; oauthUser.type = 'org'; oauthUser.uid = user.id; } else { console.warn(`getOauthUser: org not found for orgId ${this.orgId}`); } } return oauth.getOauthUser(oauthUser); } isExpired() { if (!this.expiredTime) { return false; // 没有设置过期时间 } const now = Date.now(); const expiredTime = new Date(this.expiredTime); return now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期 } /** * 检查是否过期,如果过期则更新状态为expired * * @returns */ async checkOnUse() { if (!this.expiredTime) { return { code: 200 } } try { const now = Date.now(); const expiredTime = new Date(this.expiredTime); const isExpired = now > expiredTime.getTime(); // 如果当前时间大于过期时间,则认为已过期 if (isExpired) { this.status = 'active'; const expireTime = UserSecret.getExpiredTime(); this.expiredTime = expireTime; await this.save() } if (this.status !== 'active') { this.status = 'active'; await this.save() } return { code: 200 }; } catch (e) { console.error('checkExpiredAndUpdate error', this.id, this.title); return { code: 500, message: 'checkExpiredAndUpdate error' } } } async createNewToken() { if (this.token) { await oauth.delToken(this.token); } const token = await UserSecret.createToken(); this.token = token; await this.save(); return token; } static async createToken() { let token = oauth.generateSecretKey(); // 确保生成的token是唯一的 while (await UserSecret.findOne({ where: { token } })) { token = oauth.generateSecretKey(); } return token; } /** * 根据 unionid 生成redis的key * `wxmp:unionid:token:${unionid}` * @param unionid * @returns */ static wxRedisKey(unionid: string) { return `wxmp:unionid:token:${unionid}`; } static getExpiredTime(expireDays?: number) { const defaultExpireDays = expireDays || 365; const expireTime = defaultExpireDays * 24 * 60 * 60 * 1000; return new Date(Date.now() + expireTime) } static async createSecret(tokenUser: { id: string; uid?: string, title?: string }, expireDays = 365) { const token = await UserSecret.createToken(); let userId = tokenUser.id; let orgId: string = null; if (tokenUser.uid) { userId = tokenUser.uid; orgId = tokenUser.id; // 如果是组织用户,则uid是组织ID } const userSecret = await UserSecret.create({ userId, orgId, token, title: tokenUser.title || randomString(6), expiredTime: UserSecret.getExpiredTime(expireDays), }); return userSecret; } async getPermission(opts: { id: string; uid?: string }) { const { id, uid } = opts; let userId: string = id; let hasPermission = false; let isUser = false; let isAdmin: boolean = null; if (uid) { userId = uid; } if (!id) { return { hasPermission, isUser, isAdmin, }; } if (this.userId === userId) { hasPermission = true; isUser = true; } if (hasPermission) { return { hasPermission, isUser, isAdmin, }; } if (this.orgId) { const orgUser = await User.findByPk(this.orgId); if (orgUser && orgUser.owner === userId) { isAdmin = true; hasPermission = true; } } return { hasPermission, isUser, isAdmin, }; } } /** * 组织模型,在sequelize之后初始化 */ export const UserSecretInit = async (newSequelize?: any, tableName?: string, sync?: SyncOpts) => { const sequelize = useContextKey('sequelize'); UserSecret.init( { id: { type: DataTypes.UUID, primaryKey: true, defaultValue: DataTypes.UUIDV4, }, description: { type: DataTypes.TEXT, allowNull: true, }, status: { type: DataTypes.STRING, allowNull: true, defaultValue: 'active', comment: '状态', }, title: { type: DataTypes.TEXT, allowNull: true, }, expiredTime: { type: DataTypes.DATE, allowNull: true, }, token: { type: DataTypes.STRING, allowNull: false, comment: '用户密钥', defaultValue: '', }, userId: { type: DataTypes.UUID, allowNull: true, }, data: { type: DataTypes.JSONB, allowNull: true, defaultValue: {}, }, orgId: { type: DataTypes.UUID, allowNull: true, comment: '组织ID', }, }, { sequelize: newSequelize || sequelize, modelName: tableName || 'cf_user_secret', }, ); if (sync) { await UserSecret.sync({ alter: true, logging: false, ...sync }).catch((e) => { console.error('UserSecret sync', e); }); return UserSecret; } return UserSecret; }; export const UserSecretModel = useContextKey('UserSecretModel', () => UserSecret);