Menu
CoddyTech

נתיבים מאחורי התחברות

חלק מהיחידה אימות ואבטחה במסלול ה-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 חשוב: בלעדיו הנתיב היה פועל גם כן.

challenge icon

אתגר

קל
  • כתבו את 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');
});
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה אימות ואבטחה