昨天為TypeORM和SQLte連接方式做了除錯[Day 29] Simple Auth App : 修復TypeORM 問題與DataSource,整理完DB連接問題和ORM後可以來實作裡面的邏輯了。
專案成果在這
首要先安裝用來加密、JWT token和Cors的lib
npm i --save-dev @types/bcrypt
npm i --save-dev @types/jsonwebtoken
npm install cors
npm install --save-dev @types/cors
// Sign Up API
app.post('/api/signup', async (req: Request, res: Response) => {
// hash the password
const hashedPassword = await bcrypt.hash(req.body.password, 10);
try {
// check if the user already exists
let existUser = await userRepository.findOneBy({ email: req.body.email });
if (existUser) {
return res.status(400).json({ message: "User already exists" });
}
// create a new user
let user = new User();
user.email = req.body.email;
user.password = hashedPassword;
user.name = req.body.name || '';
// save the user
await userRepository.save(user);
res.json({ message: "User registered successfully", user: user });
} catch (err) {
res.status(500).json({ message: "Error registering the user" });
}
});
// Login API
app.post('/api/login', async (req: Request, res: Response) => {
const user = await userRepository.findOneBy({email: req.body.email});
if (!user || !await bcrypt.compare(req.body.password, user.password)) {
return res.status(400).json({message: "Invalid email or password"});
}
user.loginCount++;
user.lastLoginTimestamp = new Date();
await AppDataSource.getRepository(User).save(user);
const tokenPayload = { userId: user.id, email: user.email };
const token = jwt.sign(tokenPayload, JWT_SECRET, { expiresIn: '1h' });
res.json({name: user.name, email: user.email, token: token});
});
這邊需要在註冊時使用第三方API或是自己處理寄送認證信,在信中的link為/api/verify-email/:token
,而使用者點擊時可以啟動該帳號的驗證狀態。
// Email Verification API
app.get('/api/verify-email/:token', (req: Request, res: Response) => {
// TODO: Implement email verification logic using the provided token
res.json({ message: "Email verified successfully" });
});
首先要先建立JWT verify middleware
// Define a middleware function to verify the token
const verifyToken = (req: CustomRequest, res: Response, next: any) => {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.split(' ')[1];
req.user = jwt.verify(token, JWT_SECRET) as any;
next();
} else {
return res.status(401).json({ message: 'Unauthorized' });
}
};
// User Profile API
// @ts-ignore
app.get('/api/profile', verifyToken, async (req: CustomRequest, res: Response) => {
console.log(req.user)
const user = await userRepository.findOneBy({email: req.user.email});
if (!user) {
return res.status(400).json({ message: "User not found" });
}
res.json({email: user.email, name: user.name, loginCount: user.loginCount, lastLoginTimestamp: user.lastLoginTimestamp, emailVerified: user.emailVerified});
});
// Reset Password API
// @ts-ignore
app.post('/api/reset-password', verifyToken, async (req: CustomRequest, res: Response) => {
const user = await userRepository.findOneBy({email: req.user.email});
if (!user) {
return res.status(400).json({ message: "User not found" });
}
// change password
user.password = await bcrypt.hash(req.body.password, 10);
await userRepository.save(user);
res.json({ message: "Password reset successfully" });
});
這次的挑戰就告一段落了,這次實戰的作業還有很多功能需要實現,只有簡單的實現註冊、登入、重置密碼與取得資料API功能,在架構上沒有做到優化,在理想上應該要分成這樣,第一次寫TypeScript + Express + TypeORM學到了不少經驗。
分離路由 (Routes):
對於每個功能(例如用戶認證、用戶資料、等等)建立一個單獨的路由文件。
分離控制器 (Controllers):
控制器應該包含您的路由處理程序的邏輯。例如,用戶註冊、登錄、驗證等方法應放在UserController.ts中。
分離中間件 (Middlewares):
JWT認證或其他中間件應該放在其自己的文件中。
配置 (Config):
把所有的配置,如資料庫連接設定、JWT密鑰等放在一個集中的地方。
src/
|-- config/
| |-- db.ts
| |-- jwt.ts
|-- routes/
| |-- authRoutes.ts
| |-- userRoutes.ts
|-- controllers/
| |-- AuthController.ts
| |-- UserController.ts
|-- middlewares/
| |-- authenticateJWT.ts
|-- entity/
| |-- User.ts
|-- data-source.ts
|-- index.ts (這裡只做基本的設定和啟動伺服器)