import { useContextKey } from '@kevisual/context'; import { Redis } from 'ioredis'; import { User } from './user.ts'; import { oauth, jwksManager } from '../oauth/auth.ts'; import { OauthUser } from '../oauth/oauth.ts'; import { db } from '../../modules/db.ts'; import { cfUserSecrets, cfUser } from '../../db/drizzle/schema.ts'; import { eq, InferSelectModel, InferInsertModel } from 'drizzle-orm'; const userSecretsTable = cfUserSecrets; const usersTable = cfUser; export type UserSecretData = { [key: string]: any; wxOpenid?: string; wxUnionid?: string; wxmpOpenid?: string; }; export type UserSecretSelect = InferSelectModel; export type UserSecretInsert = InferInsertModel; 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; }; export class UserSecret { static oauth = oauth; id: string; token: string; userId: string; orgId: string; title: string; description: string; status: (typeof UserSecretStatus)[number]; expiredTime: Date; data: UserSecretData; constructor(data: UserSecretSelect) { Object.assign(this, data); } /** * 验证token * @param token * @returns */ static async verifyToken(token: string) { if (token?.includes?.('.')) { // 先尝试作为jwt token验证,如果验证成功则直接返回用户信息 console.log('[jwksManager] 验证token'); return await jwksManager.verify(token); } if (!oauth.isSecretKey(token)) { return await oauth.verifyToken(token); } const secretToken = await oauth.verifyToken(token); if (secretToken) { return secretToken; } console.log('verifyToken: try to verify as secret key'); const userSecrets = await db.select().from(userSecretsTable).where(eq(userSecretsTable.token, token)).limit(1); if (userSecrets.length === 0) { return null; // 如果没有找到对应的用户密钥,则返回null } const userSecret = new UserSecret(userSecrets[0]); 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; } /** * 根据主键查找 */ static async findByPk(id: string): Promise { const secrets = await db.select().from(userSecretsTable).where(eq(userSecretsTable.id, id)).limit(1); return secrets.length > 0 ? new UserSecret(secrets[0]) : null; } /** * 根据条件查找一个 */ static async findOne(where: { token?: string; id?: string }): Promise { let query = db.select().from(userSecretsTable); if (where.token) { query = query.where(eq(userSecretsTable.token, where.token)) as any; } else if (where.id) { query = query.where(eq(userSecretsTable.id, where.id)) as any; } const secrets = await query.limit(1); return secrets.length > 0 ? new UserSecret(secrets[0]) : null; } /** * owner 组织用户的 oauthUser * @returns */ async getOauthUser(opts?: { wx?: boolean }) { const users = await db.select({ id: usersTable.id, username: usersTable.username, type: usersTable.type, owner: usersTable.owner, data: usersTable.data, }).from(usersTable).where(eq(usersTable.id, this.userId)).limit(1); let org: any = null; if (users.length === 0) { return null; // 如果没有找到对应的用户,则返回null } const user = users[0]; 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) { const orgUsers = await db.select({ id: usersTable.id, username: usersTable.username, type: usersTable.type, owner: usersTable.owner, }).from(usersTable).where(eq(usersTable.id, this.orgId)).limit(1); if (orgUsers.length > 0) { org = orgUsers[0]; 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 save() { await db.update(userSecretsTable).set({ token: this.token, userId: this.userId, orgId: this.orgId, title: this.title, description: this.description, status: this.status, expiredTime: this.expiredTime ? this.expiredTime.toISOString() : null, data: this.data, updatedAt: new Date().toISOString(), }).where(eq(userSecretsTable.id, this.id)); } 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({ 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 = null; if (tokenUser.uid) { userId = tokenUser.uid; orgId = tokenUser.id; } const insertData: Partial = { userId, token, title: tokenUser.title || randomString(6), expiredTime: UserSecret.getExpiredTime(expireDays).toISOString(), }; if (orgId !== null && orgId !== undefined) { insertData.orgId = orgId; } const inserted = await db.insert(userSecretsTable).values(insertData).returning(); return new UserSecret(inserted[0]); } 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 orgUsers = await db.select().from(usersTable).where(eq(usersTable.id, this.orgId)).limit(1); if (orgUsers.length > 0 && orgUsers[0].owner === userId) { isAdmin = true; hasPermission = true; } } return { hasPermission, isUser, isAdmin, }; } } export const UserSecretModel = useContextKey('UserSecretModel', () => UserSecret);