353 lines
9.0 KiB
TypeScript
353 lines
9.0 KiB
TypeScript
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);
|