Migrate project from typing_practiceweb
This commit is contained in:
296
server/routes/auth.ts
Normal file
296
server/routes/auth.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import express, { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { config } from '../config';
|
||||
import { MongoError } from 'mongodb';
|
||||
import { User, type IUser, type UserStats } from '../models/User';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { Session } from 'express-session';
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
interface CustomSession extends Session {
|
||||
userId?: string;
|
||||
isAuthenticated?: boolean;
|
||||
}
|
||||
|
||||
// 扩展 Request 类型
|
||||
interface CustomRequest extends Request {
|
||||
session: CustomSession;
|
||||
}
|
||||
|
||||
// 定义 JWT payload 的类型
|
||||
interface UserPayload {
|
||||
_id: string;
|
||||
username: string;
|
||||
fullname: string;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
exp?: number;
|
||||
}
|
||||
|
||||
// 扩展 Express 的 Request 类型
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: UserPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 定义路由处理函数类型
|
||||
type RouteHandler = (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => Promise<void | any>; // 允许任何返回值
|
||||
|
||||
// 登录处理函数
|
||||
const loginHandler: RouteHandler = async (req: CustomRequest, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
console.log('Login attempt:', { username });
|
||||
|
||||
const user = await User.findOne({ username });
|
||||
|
||||
if (!user) {
|
||||
console.log('User not found');
|
||||
res.status(401).json({ message: '用户名不存在' });
|
||||
return;
|
||||
}
|
||||
const isPasswordValid = await user.comparePassword(password);
|
||||
if (!isPasswordValid) {
|
||||
console.log('Password mismatch');
|
||||
return res.status(401).json({ message: '密码错误' });
|
||||
}
|
||||
|
||||
// 确保设置这些session值
|
||||
req.session.userId = (user._id as mongoose.Types.ObjectId).toString();
|
||||
req.session.isAuthenticated = true;
|
||||
|
||||
// 如果有重定向参数,则重定向回原始OAuth2请求
|
||||
const redirectUrl = req.query.redirect || '/';
|
||||
|
||||
// 生成 JWT token
|
||||
const token = jwt.sign(
|
||||
{
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
config.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
// 调试:打印 token 信息(不打印完整 token)
|
||||
console.log('Generated token info:', {
|
||||
username: user.username,
|
||||
tokenLength: token.length,
|
||||
tokenStart: token.substring(0, 20) + '...'
|
||||
});
|
||||
|
||||
console.log('Login successful:', {
|
||||
username, fullname:user.fullname,
|
||||
isAdmin: user.isAdmin,
|
||||
hasToken: !!token
|
||||
});
|
||||
|
||||
res.json({
|
||||
token,
|
||||
user: {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
success: true,
|
||||
redirectUrl
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ message: '登录失败' });
|
||||
}
|
||||
};
|
||||
|
||||
// 注册处理函数
|
||||
const registerHandler: RouteHandler = async (req, res) => {
|
||||
try {
|
||||
const { username, password, email,fullname } = req.body;
|
||||
|
||||
console.log('Registration attempt:', { username, email,fullname });
|
||||
if (!email) {
|
||||
return res.status(400).json({ message: '邮箱是必填项' });
|
||||
}
|
||||
// 检查用户名是否存在
|
||||
const existingUsername = await User.findOne({ username });
|
||||
if (existingUsername) {
|
||||
return res.status(400).json({ message: '用户名已存在' });
|
||||
}
|
||||
|
||||
|
||||
const existingEmail = await User.findOne({ email });
|
||||
if (existingEmail) {
|
||||
return res.status(400).json({ message: '邮箱已被使用' });
|
||||
}
|
||||
|
||||
const userCount = await User.countDocuments();
|
||||
const isAdmin = userCount === 0;
|
||||
|
||||
const user = new User({
|
||||
username,
|
||||
password,
|
||||
email,fullname ,
|
||||
isAdmin
|
||||
});
|
||||
|
||||
await user.save();
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
fullname: user.fullname,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
config.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
console.log('Registration successful:', {
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin,
|
||||
hasToken: !!token
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
token,
|
||||
user: {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
fullname:user.fullname,
|
||||
email: user.email,
|
||||
isAdmin: user.isAdmin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
|
||||
if (error instanceof MongoError && error.code === 11000) {
|
||||
res.status(400).json({ message: '用户名或邮箱已存在' });
|
||||
} else {
|
||||
res.status(500).json({
|
||||
message: '注册失败:' + (error instanceof Error ? error.message : '未知错误')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
// 中间件:验证 token
|
||||
const authMiddleware: RouteHandler = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
return res.status(401).json({ message: '未登录' });
|
||||
}
|
||||
|
||||
const token = authHeader.substring(7);
|
||||
const decoded = jwt.verify(token, config.JWT_SECRET) as UserPayload;
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
res.status(401).json({ message: '登录已过期,请重新登录' });
|
||||
}
|
||||
};
|
||||
|
||||
// 修改密码处理函数
|
||||
const changePasswordHandler: RouteHandler = async (req, res) => {
|
||||
try {
|
||||
if (!req.user?._id) {
|
||||
return res.status(401).json({ message: '未登录' });
|
||||
}
|
||||
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
|
||||
// 参数验证
|
||||
if (!oldPassword || !newPassword) {
|
||||
return res.status(400).json({ message: '请提供原密码和新密码' });
|
||||
}
|
||||
|
||||
if (newPassword.length < 6) {
|
||||
return res.status(400).json({ message: '新密码长度至少6个字符' });
|
||||
}
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findById(req.user._id);
|
||||
if (!user) {
|
||||
return res.status(404).json({ message: '用户不存在' });
|
||||
}
|
||||
|
||||
// 验证原密码
|
||||
const isPasswordValid = await user.comparePassword(oldPassword);
|
||||
if (!isPasswordValid) {
|
||||
console.log('Old password mismatch for user:', user.username);
|
||||
return res.status(401).json({ message: '原密码错误' });
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
console.log('Password changed successfully for user:', user.username);
|
||||
res.json({ message: '密码修改成功' });
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
message: '修改密码失败:' + (error instanceof Error ? error.message : '未知错误')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 登出处理函数
|
||||
const logoutHandler: RouteHandler = async (req: CustomRequest, res) => {
|
||||
try {
|
||||
console.log('Logout request received');
|
||||
|
||||
// 清除 session
|
||||
if (req.session) {
|
||||
req.session.destroy((err) => {
|
||||
if (err) {
|
||||
console.error('Session destruction error:', err);
|
||||
return res.status(500).json({ message: '登出失败' });
|
||||
}
|
||||
|
||||
// 清除 cookie
|
||||
res.clearCookie('connect.sid');
|
||||
|
||||
console.log('Logout successful');
|
||||
res.json({ success: true, message: '登出成功' });
|
||||
});
|
||||
} else {
|
||||
console.log('No session to destroy');
|
||||
res.json({ success: true, message: '登出成功' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Logout error:', error);
|
||||
res.status(500).json({ message: '登出失败' });
|
||||
}
|
||||
};
|
||||
|
||||
// 登录路由
|
||||
router.post('/login', loginHandler);
|
||||
|
||||
// 注册路由
|
||||
router.post('/register', registerHandler);
|
||||
|
||||
// 登出路由
|
||||
router.post('/logout', logoutHandler);
|
||||
|
||||
// 修改密码路由
|
||||
router.post('/change-password', authMiddleware, changePasswordHandler);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user