2025-04-03 20:08:40 +08:00

353 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);