import { app } from '@/app.ts'; import { Org } from '@/models/org.ts'; import { User } from '@/models/user.ts'; import { domain } from '@/modules/domain.ts'; /** * 当配置了domain后,创建cookie,当get请求地址的时候,会自动带上cookie * @param token * @param ctx * @returns */ export const createCookie = (token: any, ctx: any) => { if (!domain) { return; } //TODO, 获取访问的 hostname, 如果访问的和 domain 的不一致,也创建cookie const browser = ctx.req.headers['user-agent']; const isBrowser = browser.includes('Mozilla'); // 浏览器 if (isBrowser && ctx.res.cookie) { ctx.res.cookie('token', token.accessToken || token?.token, { maxAge: 7 * 24 * 60 * 60 * 1000, // 过期时间, 设置7天 domain, path: '/', sameSite: 'lax', httpOnly: true, }); } }; export type ReqHeaders = { host: string; 'x-forwarded-for': string; 'x-real-ip': string; 'sec-ch-ua': string; // 浏览器 'sec-ch-ua-mobile': string; // 移动设备 'sec-ch-ua-platform': string; // 平台 'sec-ch-ua-arch': string; // 架构 'sec-ch-ua-bitness': string; // 位数 'sec-ch-ua-full-version': string; // 完整版本 'sec-ch-ua-full-version-list': string; // 完整版本列表 'sec-fetch-dest': string; // 目标 'sec-fetch-mode': string; // 模式 'sec-fetch-site': string; // 站点 'sec-fetch-user': string; // 用户 'upgrade-insecure-requests': string; // 升级不安全请求 'user-agent': string; // 用户代理 accept: string; // 接受 'accept-language': string; // 接受语言 'accept-encoding': string; // 接受编码 'cache-control': string; // 缓存控制 pragma: string; // 预先 expires: string; // 过期 connection: string; // 连接 cookie: string; // 饼干 }; export const getSomeInfoFromReq = (ctx: any) => { const headers = ctx.req?.headers as ReqHeaders; if (!headers) { console.log('no req headers', ctx.req); return { 'user-agent': '', browser: '', isBrowser: false, host: '', ip: '', headers: {}, }; } const userAgent = headers?.['user-agent'] || ''; const host = headers?.['host']; const ip = headers?.['x-forwarded-for'] || ctx.req?.connection?.remoteAddress; return { 'user-agent': userAgent, browser: userAgent, isBrowser: userAgent.includes('Mozilla'), host, ip, headers, }; }; export const clearCookie = (ctx: any) => { if (!domain) { return; } ctx.res.cookie('token', '', { maxAge: 0, domain, sameSite: 'lax', httpOnly: true, }); }; app .route({ path: 'user', key: 'me', middleware: ['auth'], }) .define(async (ctx) => { const tokenUser = ctx.state?.tokenUser || {}; const { id } = tokenUser; const user = await User.findByPk(id, { logging: false, }); if (!user) { ctx.throw(500, 'user not found'); } user.setTokenUser(tokenUser); ctx.body = await user.getInfo(); }) .addTo(app); app .route({ path: 'user', key: 'login', middleware: ['auth-can'], }) .define(async (ctx) => { const oldToken = ctx.query.token; const tokenUser = ctx.state?.tokenUser || {}; const { username, email, password, loginType = 'default' } = ctx.query; if (!username && !email) { ctx.throw(400, 'username or email is required'); } let user: User | null = null; if (username) { user = await User.findOne({ where: { username }, logging: false }); } if (!user && email) { user = await User.findOne({ where: { email } }); } if (!user) { ctx.throw(500, 'Login Failed'); } if (tokenUser.id === user.id) { // 自己刷新自己的token const token = await User.oauth.resetToken(oldToken, { ...tokenUser.oauthExpand, }); createCookie(token, ctx); ctx.body = token; return; } if (!user.checkPassword(password)) { ctx.throw(500, 'Password error'); } user.expireOrgs(); const someInfo = getSomeInfoFromReq(ctx); const token = await user.createToken(null, loginType, { ip: someInfo.ip, browser: someInfo['user-agent'], host: someInfo.host, }); createCookie(token, ctx); ctx.body = token; }) .addTo(app); app .route({ path: 'user', key: 'logout', }) .define(async (ctx) => { const token = ctx.query?.token; const { tokens = [] } = ctx.query?.data || {}; clearCookie(ctx); let needDelTokens = Array.from(new Set([...tokens, token].filter(Boolean))); for (const token of needDelTokens) { try { await User.oauth.delToken(token); } catch (e) { // console.log('logout error', e); console.log('error token is has been deleted', token); } } ctx.body = { code: 200, message: 'Logout Success', }; }) .addTo(app); app .route({ path: 'user', key: 'auth', middleware: ['auth-can'], }) .define(async (ctx) => { const { checkToken: token } = ctx.query; try { const result = await User.verifyToken(token); if (result) { delete result.oauthExpand; } ctx.body = result || {}; } catch (e) { ctx.throw(401, 'Token InValid '); } }) .addTo(app); app .route({ path: 'user', key: 'updateSelf', middleware: ['auth'], }) .define(async (ctx) => { const { username, password, description, avatar, email } = ctx.query.data || {}; const tokenUser = ctx.state?.tokenUser || {}; const { id } = tokenUser; const user = await User.findByPk(id); if (!user) { ctx.throw(404, 'user not found'); } user.setTokenUser(tokenUser); if (username) { user.username = username; } if (password && user.type !== 'org') { user.createPassword(password); } if (description) { user.description = description; } if (avatar) { user.avatar = avatar; } if (email) { user.email = email; } await user.save(); ctx.body = await user.getInfo(); }) .addTo(app); app .route({ path: 'user', key: 'switchCheck', middleware: ['auth'], }) .define(async (ctx) => { const token = ctx.query.token; const { username, accessToken } = ctx.query.data || {}; if (accessToken && username) { const accessUser = await User.verifyToken(accessToken); const refreshToken = accessUser.oauthExpand?.refreshToken; if (refreshToken) { const result = await User.oauth.refreshToken(refreshToken); createCookie(result, ctx); ctx.body = result; return; } else if (accessUser) { await User.oauth.delToken(accessToken); const result = await User.oauth.generateToken(accessUser, { ...accessUser.oauthExpand, hasRefreshToken: true, }); createCookie(result, ctx); ctx.body = result; return; } } else { const result = await ctx.call( { path: 'user', key: 'switchOrg', payload: { data: { username, }, token, }, }, { res: ctx.res, req: ctx.req, }, ); if (result.code === 200) { ctx.body = result.body; } else { ctx.throw(result.code, result.message); } } }) .addTo(app); app .route({ path: 'user', key: 'switchOrg', middleware: ['auth'], }) .define(async (ctx) => { const tokenUser = ctx.state.tokenUser; const token = ctx.query.token; const tokenUsername = tokenUser.username; const userId = tokenUser.userId; let { username } = ctx.query.data || {}; const user = await User.findByPk(userId); if (!user) { ctx.throw('user not found'); } if (!username) { username = user.username; } const orgs = await user.getOrgs(); const orgsList = [tokenUser.username, user.username, , ...orgs]; if (orgsList.includes(username)) { if (tokenUsername === username) { const result = await User.oauth.resetToken(token); createCookie(result, ctx); await User.oauth.delToken(token); ctx.body = result; } else { const user = await User.findOne({ where: { username } }); const result = await user.createToken(userId, 'default'); createCookie(result, ctx); ctx.body = result; } } else { ctx.throw(403, 'Permission denied'); } }) .addTo(app); app .route({ path: 'user', key: 'refreshToken', }) .define(async (ctx) => { const { refreshToken } = ctx.query.data || {}; try { if (!refreshToken) { ctx.throw(400, 'Refresh Token is required'); } const result = await User.oauth.refreshToken(refreshToken); if (result) { console.log('refreshToken result', result); createCookie(result, ctx); ctx.body = result; } else { ctx.throw(500, 'Refresh Token Failed, please login again'); } } catch (e) { console.log('refreshToken error', e); ctx.throw(400, 'Refresh Token Failed'); } }) .addTo(app);