נתיבים מאחורי התחברות
חלק מהיחידה אימות ואבטחה במסלול ה-Node.js של Coddy. שיעור 6 מתוך 17.
כל נתיב פרטי זקוק לאיתור הפעלה מתוך 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');
});
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.