로그인이 필요한 라우트
Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 17개 중 6번째.
모든 비공개 경로에는 GET /me에서 사용하는 세션 조회가 필요합니다. 각 경로에 복사해 넣으면 한 번은 빠뜨리게 되고, 그러면 해당 경로가 누구에게나 열리게 됩니다. 대신 미들웨어로 한 번만 작성하세요.
미들웨어는 경로보다 먼저 실행됩니다. 응답을 보내고 실행을 멈추거나, 찾은 내용을 req에 넣고 next()를 호출합니다. 필요한 각 경로에 핸들러보다 앞서 전달하세요.
function requireAdult(req, res, next) {
if (Number(req.query.age) < 18) {
return res.status(403).json({ error: 'Adults only' });
}
req.adult = true;
next();
}
app.get('/wines', requireAdult, (req, res) => res.json(wines));return이 중요합니다. 이것이 없으면 경로도 실행됩니다.
챌린지
쉬움requireUser를 작성하세요: 유효한 세션이 없으면 401{ "error": "Log in first" }을 반환하고, 그렇지 않으면req.user = { id, username }을 설정하고next()를 호출합니다.GET /me,GET /notes,POST /notes에서 이를 사용합니다. 노트에는owner: req.user.id를 저장하고,GET /notes는 자신의 노트만 나열합니다.
로그아웃한 상태에서 GET /notes를 요청한 다음, 로그인하고 노트를 추가하세요.
직접 해보기
import express from 'express';
import { randomBytes } from 'node:crypto';
import { parseCookies } from './cookies.js';
import { notes, sessions, users } from './db.js';
import { hashPassword, verifyPassword } from './passwords.js';
const app = express();
app.use(express.json());
app.post('/signup', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const password = String(req.body.password ?? '');
if (!username || password.length < 8) {
return res.status(400).json({ error: 'Pick a username and a password of at least 8 characters' });
}
if (await users.findOne({ username })) {
return res.status(409).json({ error: 'That username is taken' });
}
await users.insertOne({ username, passwordHash: await hashPassword(password) });
res.status(201).json({ username });
});
app.post('/login', async (req, res) => {
const username = String(req.body.username ?? '').trim().toLowerCase();
const user = await users.findOne({ username });
if (!user || !(await verifyPassword(String(req.body.password ?? ''), user.passwordHash))) {
return res.status(401).json({ error: 'Wrong username or password' });
}
const sid = randomBytes(24).toString('base64url');
await sessions.insertOne({ _id: sid, userId: user._id, createdAt: new Date() });
res.cookie('sid', sid, { httpOnly: true });
res.json({ username: user.username });
});
app.get('/me', async (req, res) => {
const { sid } = parseCookies(req.get('cookie'));
const session = sid && (await sessions.findOne({ _id: sid }));
if (!session) {
return res.status(401).json({ error: 'Log in first' });
}
const user = await users.findOne({ _id: session.userId });
res.json({ username: user.username });
});
// 이 브라우저가 GET /visit을 호출한 횟수를 셉니다.
app.get('/visit', (req, res) => {
const cookies = parseCookies(req.get('cookie'));
const visits = Number(cookies.visits ?? 0) + 1;
res.cookie('visits', String(visits));
res.json({ visits });
});
// 메모. 현재는 누구나 읽고 쓸 수 있습니다.
app.get('/notes', async (req, res) => {
res.json(await notes.find().toArray());
});
app.post('/notes', async (req, res) => {
const note = { text: String(req.body.text ?? '') };
await notes.insertOne(note);
res.status(201).json(note);
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.