Menu
CoddyTech

Giriş Gerektiren Rotalar

Coddy'nin Node.js Journey'sinin Kimlik Doğrulama ve Güvenlik bölümünün bir parçası. Ders 11 / 24.

Her özel rota, GET /me üzerinden oturum sorgulamasına ihtiyaç duyar. Her rotaya kopyalarsan bir keresinde unutulur ve o rota herkese açık kalır. Bunun yerine bir ara katman yazılımı olarak bir kez yaz.

Ara katman yazılımı rotadan önce çalışır. req üzerine req.user gibi bir şey eklediğinde rota bunu oradan okur. Bir kullanıcının _id değeri bir ObjectId'dir; user._id.toHexString() bunu metin olarak verir. Ya bir yanıt gönderip işlemi durdurur ya da bulduğu şeyi req üzerine ekleyip next() çağırır. Bunu ihtiyaç duyan her rotaya, işleyiciden önce geçir:

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 önemlidir: o olmazsa rota da çalışır.

challenge icon

Görev

Kolay

Notlar hâlâ herkesin erişimine açık. Onları özel hâle getir: yalnızca giriş yapmış bir kullanıcı notları okuyabilir veya ekleyebilir ve her kullanıcı yalnızca kendi notlarını görür.

  1. server.js dosyasında requireUser(req, res, next) ara katman işlevini yaz. Oturum arama işlemini GET /me içinden buraya taşı: sid çerezini oku, oturumu bul, ardından oturumun kullanıcısını bul.
    • Böyle bir kullanıcı yoksa 401 durum koduyla {"error": "Log in first"} yanıtını ver ve burada dur.
    • Aksi takdirde, kullanıcıyı iki alanı olan bir nesne olarak req.user üzerinden isteğe ekle, ardından next() çağrısını yap. Bu iki alan id (kullanıcının _id değerinin metin hâli) ve username alanlarıdır. ObjectId'nin toHexString() yöntemi, "66f1c2a9e4b0a1b2c3d4e5f6" gibi metin hâlini verir.
  2. requireUser işlevini, her rotanın kendi işlevinden önce GET /me, GET /notes ve POST /notes rotalarına ekle. Bunun ardından GET /me, req.user üzerinden okuduğu {"username": "ada"} yanıtını verir.
  3. POST /notes içinde her notu, req.user.id değerini tutan bir owner alanıyla kaydet. Yanıt yeni nottur: {"owner": "66f1c2a9e4b0a1b2c3d4e5f6", "text": "Door code 1234", "_id": "66f1c2b0e4b0a1b2c3d4e5f7"}
  4. GET /notes içinde yalnızca owner değeri giriş yapmış kullanıcıya ait olan notları döndür.

Deneyin: Çalıştır'a bas. GET /notes isteği gönder: 401 durum kodu ve {"error": "Log in first"} yanıtını al. {"username": "ada", "password": "correct horse"} içeriğiyle önce POST /signup, ardından POST /login isteği gönder. {"text": "Door code 1234"} içeriğiyle POST /notes isteği gönder: 201 durum kodu ve owner alanı da dahil olmak üzere notu al. GET /notes isteği gönder: bu tek notu içeren bir liste al. Şimdi {"username": "bob", "password": "correct horse"} içeriğiyle kaydol ve giriş yap. bob hiçbir şey yazmadığı için GET /notes, [] döndürür.

Kendin dene

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 });
});

// Bu tarayıcının GET /visit isteğini kaç kez yaptığını sayar.
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 });
});

// Notlar. Şimdilik herkes bunları okuyabilir ve yazabilir.
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 iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Kimlik Doğrulama ve Güvenlik bölümündeki tüm dersler

Kendi başına pratik yap: Express playground