完善
This commit is contained in:
@@ -2,19 +2,26 @@ import { Request, Response } from 'express';
|
||||
import User, { IUser } from '../models/User';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import crypto from 'crypto';
|
||||
import nodemailer from 'nodemailer';
|
||||
import authMiddleware from '../middleware/authMiddleware';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret_key';
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
const EMAIL_USER = process.env.EMAIL_USER;
|
||||
const EMAIL_PASS = process.env.EMAIL_PASS;
|
||||
|
||||
// 用户注册
|
||||
export const register = async (req: Request, res: Response) => {
|
||||
const { username, password } = req.body;
|
||||
export const register = async (req: Request, res: Response): Promise<void> => {
|
||||
const { username, email, password } = req.body;
|
||||
try {
|
||||
const existingUser = await User.findOne({ username });
|
||||
const existingUser = await User.findOne({ $or: [{ username }, { email }] });
|
||||
if (existingUser) {
|
||||
return res.status(400).json({ message: '用户名已存在' });
|
||||
res.status(400).json({ message: '用户名或邮箱已存在' });
|
||||
return;
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const user = new User({ username, password: hashedPassword });
|
||||
const user = new User({ username, email, password: hashedPassword });
|
||||
await user.save();
|
||||
res.status(201).json({ message: '注册成功' });
|
||||
} catch (error) {
|
||||
@@ -23,20 +30,130 @@ export const register = async (req: Request, res: Response) => {
|
||||
};
|
||||
|
||||
// 用户登录
|
||||
export const login = async (req: Request, res: Response) => {
|
||||
export const login = async (req: Request, res: Response): Promise<void> => {
|
||||
const { username, password } = req.body;
|
||||
try {
|
||||
const user = await User.findOne({ username });
|
||||
const user = await User.findOne({ $or: [{ username }, { email: username }] });
|
||||
|
||||
if (!user) {
|
||||
return res.status(400).json({ message: '用户不存在' });
|
||||
res.status(400).json({ message: '用户不存在' });
|
||||
return;
|
||||
}
|
||||
const isMatch = await bcrypt.compare(password, user.password);
|
||||
if (!isMatch) {
|
||||
return res.status(400).json({ message: '密码错误' });
|
||||
res.status(400).json({ message: '密码错误' });
|
||||
return;
|
||||
}
|
||||
const token = jwt.sign({ userId: user._id, username: user.username }, JWT_SECRET, { expiresIn: '7d' });
|
||||
res.status(200).json({ token, username: user.username });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: '登录失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 用户忘记密码
|
||||
export const forgotPassword = async (req: Request, res: Response): Promise<void> => {
|
||||
const { email } = req.body;
|
||||
|
||||
try {
|
||||
const user = await User.findOne({ username: email });
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({ message: '用户不存在' });
|
||||
return;
|
||||
}
|
||||
|
||||
const resetToken = crypto.randomBytes(32).toString('hex');
|
||||
user.resetPasswordToken = resetToken;
|
||||
user.resetPasswordExpires = new Date(Date.now() + 3600000); // 1 小时后过期
|
||||
await user.save();
|
||||
|
||||
res.status(200).json({ message: '密码重置链接已发送到您的邮箱' });
|
||||
} catch (error) {
|
||||
console.error('Forgot password error:', error);
|
||||
res.status(500).json({ message: '重置密码失败' });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取用户个人信息
|
||||
export const getUserProfile = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const user = await User.findById(req.user.userId).select('-password'); // 不返回密码
|
||||
if (!user) {
|
||||
res.status(404).json({ message: '用户未找到' });
|
||||
return;
|
||||
}
|
||||
res.status(200).json({ username: user.username, email: user.email });
|
||||
} catch (error) {
|
||||
console.error('Get user profile error:', error);
|
||||
res.status(500).json({ message: '获取用户信息失败' });
|
||||
}
|
||||
};
|
||||
|
||||
// 修改用户密码
|
||||
export const changePassword = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
const { oldPassword, newPassword } = req.body;
|
||||
|
||||
try {
|
||||
const user = await User.findById(req.user.userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: '用户未找到' });
|
||||
return;
|
||||
}
|
||||
|
||||
const isMatch = await bcrypt.compare(oldPassword, user.password);
|
||||
if (!isMatch) {
|
||||
res.status(400).json({ message: '旧密码不正确' });
|
||||
return;
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(newPassword, 10);
|
||||
user.password = hashedPassword;
|
||||
user.resetPasswordToken = undefined; // 清除重置令牌
|
||||
user.resetPasswordExpires = undefined; // 清除重置令牌过期时间
|
||||
await user.save();
|
||||
|
||||
res.status(200).json({ message: '密码修改成功' });
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({ message: '密码修改失败' });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新用户个人信息(包括邮箱绑定/修改)
|
||||
export const updateUserProfile = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
const { email } = req.body;
|
||||
|
||||
try {
|
||||
const user = await User.findById(req.user.userId);
|
||||
if (!user) {
|
||||
res.status(404).json({ message: '用户未找到' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果用户尝试绑定邮箱
|
||||
if (email && !user.email) {
|
||||
const existingEmailUser = await User.findOne({ email });
|
||||
if (existingEmailUser) {
|
||||
res.status(400).json({ message: '该邮箱已被其他用户绑定' });
|
||||
return;
|
||||
}
|
||||
user.email = email;
|
||||
await user.save();
|
||||
res.status(200).json({ message: '邮箱绑定成功', username: user.username, email: user.email });
|
||||
} else if (email && user.email && user.email !== email) {
|
||||
// 如果用户已绑定邮箱并尝试修改为不同邮箱
|
||||
res.status(400).json({ message: '邮箱已绑定,不支持修改。如需修改,请联系管理员。' });
|
||||
return;
|
||||
} else if (user.email === email) {
|
||||
// 如果用户已绑定邮箱并尝试修改为相同的邮箱,视为无操作
|
||||
res.status(200).json({ message: '邮箱未更改', username: user.username, email: user.email });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({ message: '用户信息更新成功', username: user.username, email: user.email });
|
||||
} catch (error) {
|
||||
console.error('Update user profile error:', error);
|
||||
res.status(500).json({ message: '更新用户信息失败' });
|
||||
}
|
||||
};
|
||||
@@ -1,40 +1,23 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import Todo, { ITodo } from '../models/Todo';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import mongoose from 'mongoose'; // 导入 mongoose
|
||||
import { authMiddleware } from '../middleware/authMiddleware'; // Import authMiddleware
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret_key';
|
||||
|
||||
// 鉴权中间件,解析 token 并注入 req.user
|
||||
export const authMiddleware = (req: any, res: Response, next: NextFunction): void => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
res.status(401).json({ message: '未授权' });
|
||||
return;
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: '无效的 token' });
|
||||
}
|
||||
};
|
||||
|
||||
// 获取所有待办事项(只返回当前用户的)
|
||||
export const getAllTodos = async (req: Request & { user?: any }, res: Response): Promise<Response> => {
|
||||
export const getAllTodos = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const todos = await Todo.find({ user: req.user.userId }).sort({ createdAt: -1 });
|
||||
return res.status(200).json(todos);
|
||||
res.status(200).json(todos);
|
||||
} catch (error) {
|
||||
console.error("Error in getAllTodos:", error);
|
||||
return res.status(500).json({ message: '获取待办事项失败', error });
|
||||
res.status(500).json({ message: '获取待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 创建新的待办事项(自动绑定 user)
|
||||
export const createTodo = async (req: Request & { user?: any }, res: Response): Promise<Response> => {
|
||||
export const createTodo = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { title, description = '', priority = 'medium' } = req.body;
|
||||
const todo = new Todo({
|
||||
@@ -44,24 +27,26 @@ export const createTodo = async (req: Request & { user?: any }, res: Response):
|
||||
user: req.user.userId
|
||||
});
|
||||
const savedTodo = await todo.save();
|
||||
return res.status(201).json(savedTodo);
|
||||
res.status(201).json(savedTodo);
|
||||
} catch (error) {
|
||||
console.error("Error in createTodo:", error);
|
||||
return res.status(400).json({ message: '创建待办事项失败', error });
|
||||
res.status(400).json({ message: '创建待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 更新待办事项
|
||||
export const updateTodo = async (req: Request & { user?: any }, res: Response): Promise<Response> => {
|
||||
export const updateTodo = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params; // 使用 id 作为参数名,便于匹配
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const todo = await Todo.findOne({ _id: id, user: req.user.userId });
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedTodo = await Todo.findByIdAndUpdate(
|
||||
@@ -70,53 +55,57 @@ export const updateTodo = async (req: Request & { user?: any }, res: Response):
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
return res.status(200).json(updatedTodo);
|
||||
res.status(200).json(updatedTodo);
|
||||
} catch (error) {
|
||||
console.error("Error in updateTodo:", error);
|
||||
return res.status(400).json({ message: '更新待办事项失败', error });
|
||||
res.status(400).json({ message: '更新待办事项失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 切换待办事项完成状态
|
||||
export const toggleTodoStatus = async (req: Request & { user?: any }, res: Response): Promise<Response> => {
|
||||
export const toggleTodoStatus = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const todo = await Todo.findOne({ _id: id, user: req.user.userId });
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
return;
|
||||
}
|
||||
|
||||
todo.completed = !todo.completed; // 切换完成状态
|
||||
const updatedTodo = await todo.save();
|
||||
|
||||
return res.status(200).json(updatedTodo);
|
||||
res.status(200).json(updatedTodo);
|
||||
} catch (error) {
|
||||
console.error("Error in toggleTodoStatus:", error);
|
||||
return res.status(400).json({ message: '切换状态失败', error });
|
||||
res.status(400).json({ message: '切换状态失败', error });
|
||||
}
|
||||
};
|
||||
|
||||
// 删除待办事项
|
||||
export const deleteTodo = async (req: Request & { user?: any }, res: Response): Promise<Response> => {
|
||||
export const deleteTodo = async (req: Request & { user?: any }, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||
return res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
res.status(400).json({ message: '无效的待办事项 ID' });
|
||||
return;
|
||||
}
|
||||
|
||||
const todo = await Todo.findOne({ _id: id, user: req.user.userId });
|
||||
if (!todo) {
|
||||
return res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
res.status(404).json({ message: '待办事项不存在或无权访问' });
|
||||
return;
|
||||
}
|
||||
|
||||
await Todo.findByIdAndDelete(id);
|
||||
return res.status(200).json({ message: '待办事项已删除' });
|
||||
res.status(200).json({ message: '待办事项已删除' });
|
||||
} catch (error) {
|
||||
console.error("Error in deleteTodo:", error);
|
||||
return res.status(400).json({ message: '删除待办事项失败', error });
|
||||
res.status(400).json({ message: '删除待办事项失败', error });
|
||||
}
|
||||
};
|
||||
22
server/src/middleware/authMiddleware.ts
Normal file
22
server/src/middleware/authMiddleware.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'secret_key';
|
||||
|
||||
const authMiddleware = (req: any, res: Response, next: NextFunction): void => {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
res.status(401).json({ message: '未授权' });
|
||||
return;
|
||||
}
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as any;
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (err) {
|
||||
res.status(401).json({ message: '无效的 token' });
|
||||
}
|
||||
};
|
||||
|
||||
export default authMiddleware;
|
||||
@@ -2,12 +2,21 @@ import mongoose, { Schema, Document } from 'mongoose';
|
||||
|
||||
export interface IUser extends Document {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
resetPasswordToken?: string;
|
||||
resetPasswordExpires?: Date;
|
||||
}
|
||||
|
||||
const UserSchema: Schema = new Schema({
|
||||
username: { type: String, required: true, unique: true },
|
||||
email: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true }
|
||||
});
|
||||
|
||||
UserSchema.add({
|
||||
resetPasswordToken: String,
|
||||
resetPasswordExpires: Date,
|
||||
});
|
||||
|
||||
export default mongoose.model<IUser>('User', UserSchema);
|
||||
@@ -1,9 +1,14 @@
|
||||
import { Router } from 'express';
|
||||
import { register, login } from '../controllers/authController';
|
||||
import { register, login, forgotPassword, getUserProfile, changePassword, updateUserProfile } from '../controllers/authController';
|
||||
import authMiddleware from '../middleware/authMiddleware';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.post('/register', register);
|
||||
router.post('/login', login);
|
||||
router.post('/forgot-password', forgotPassword);
|
||||
router.get('/profile', authMiddleware, getUserProfile);
|
||||
router.put('/profile', authMiddleware, updateUserProfile);
|
||||
router.put('/change-password', authMiddleware, changePassword);
|
||||
|
||||
export default router;
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
updateTodo,
|
||||
deleteTodo,
|
||||
toggleTodoStatus,
|
||||
authMiddleware
|
||||
} from '../controllers/todoController';
|
||||
import authMiddleware from '../middleware/authMiddleware';
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user