132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import { app } from '@/app.ts';
|
||
import { User } from '@/models/user.ts';
|
||
import MD5 from 'crypto-js/md5.js';
|
||
import { authCan } from '@/auth/index.ts';
|
||
import jsonwebtoken from 'jsonwebtoken';
|
||
|
||
import { redis } from '@/app.ts';
|
||
import { createCookie, clearCookie } from './me.ts';
|
||
import z from 'zod';
|
||
|
||
app
|
||
.route({
|
||
path: 'user',
|
||
key: 'webLogin',
|
||
description: 'web登录接口,配合插件使用',
|
||
middleware: [authCan],
|
||
metadata: {
|
||
args: {
|
||
loginToken: z.string().describe('web登录令牌,服务端生成,客户端保持一致'),
|
||
sign: z.string().describe('签名,服务端生成,客户端保持一致'),
|
||
randomId: z.string().describe('随机字符串,服务端和客户端保持一致'),
|
||
}
|
||
}
|
||
})
|
||
.define(async (ctx) => {
|
||
const tokenUser = ctx.state.tokenUser;
|
||
const token = ctx.query.token;
|
||
const { loginToken, sign, randomId } = ctx.query || {};
|
||
const setErrorLoginTokenRedis = async (loginToken: string) => {
|
||
await redis.set(loginToken, JSON.stringify({}), 'EX', 2 * 60); // 2分钟
|
||
};
|
||
if (!tokenUser) {
|
||
if (token) {
|
||
console.log('web-login, token', ' run clearCookie', token, tokenUser);
|
||
// clearCookie(ctx);
|
||
} else {
|
||
// const message = 'token is expired, please login in web page. ';
|
||
}
|
||
try {
|
||
ctx.res.setHeader('Content-Type', 'text/html');
|
||
const createRedirectHtml = () => {
|
||
const reqUrl = ctx.req.url;
|
||
return `
|
||
<html lang="zh-CN">
|
||
<body>
|
||
<h1>login with web page</h1>
|
||
<a href="${reqUrl}">${reqUrl}</a>
|
||
<script>
|
||
const redirect = new URL('${reqUrl}', window.location.origin);
|
||
const encodeRedirect = encodeURIComponent(redirect.toString());
|
||
const toPage = new URL('/root/center/login/?user-check=true&redirect='+encodeRedirect, window.location.origin);
|
||
setTimeout(() => {
|
||
window.location.href = toPage.toString();
|
||
}, 1000);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
`;
|
||
};
|
||
ctx.res.end(createRedirectHtml());
|
||
} catch (e) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'token is expired and redirect error');
|
||
}
|
||
return;
|
||
}
|
||
if (!loginToken) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'loginToken is required');
|
||
}
|
||
|
||
if (!randomId) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'randomId is required');
|
||
}
|
||
const tokenSecret = 'xiao' + randomId;
|
||
if (sign) {
|
||
let payload: any = {};
|
||
try {
|
||
payload = jsonwebtoken.verify(loginToken, tokenSecret);
|
||
} catch (e) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'loginToken error');
|
||
}
|
||
const { timestamp } = payload;
|
||
|
||
const checkSign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||
if (sign !== checkSign) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'sign error');
|
||
}
|
||
}
|
||
|
||
const user = await User.findByPk(tokenUser.id);
|
||
if (!user) {
|
||
await setErrorLoginTokenRedis(loginToken);
|
||
ctx.throw(400, 'user not found');
|
||
}
|
||
const data = await user.createToken(null, 'jwks', { loginWith: 'cli' });
|
||
await redis.set(loginToken, JSON.stringify(data), 'EX', 10 * 60); // 10分钟
|
||
ctx.body = 'success';
|
||
})
|
||
.addTo(app);
|
||
|
||
app
|
||
.route({
|
||
path: 'user',
|
||
key: 'checkLoginStatus',
|
||
description: '循环检查登陆状态',
|
||
})
|
||
.define(async (ctx) => {
|
||
const { loginToken } = ctx.query;
|
||
if (!loginToken) {
|
||
ctx.throw(400, 'loginToken is required');
|
||
}
|
||
// const data = tokenData[loginToken];
|
||
const data = await redis.get(loginToken);
|
||
if (data) {
|
||
const token = JSON.parse(data);
|
||
if (token.accessToken) {
|
||
ctx.body = token;
|
||
createCookie(token, ctx);
|
||
} else {
|
||
ctx.throw(500, 'Checked error Failed, login failed, please login again');
|
||
}
|
||
await redis.expire(loginToken, 2 * 60); // 2分钟
|
||
} else {
|
||
ctx.throw(400, 'Checked Failed');
|
||
}
|
||
})
|
||
.addTo(app);
|