Yönetici Rolleri
Coddy'nin Node.js Journey'sinin Kimlik Doğrulama ve Güvenlik bölümünün bir parçası. Ders 19 / 24.
GET /admin yalnızca birinin oturum açmış olup olmadığını kontrol eder, bu nedenle her üye bu sayfayı görür. Kullanıcıda saklanan rol, örneğin 'member' veya 'admin', neler yapabileceğini belirtir. Middleware döndüren bir işlev olan middleware fabrikası, her rota için bir rolü kontrol eder:
function requirePlan(plan) {
return (req, res, next) => {
if (req.user.plan !== plan) {
return res.status(402).json({ error: `Needs the ${plan} plan` });
}
next();
};
}
app.get('/exports', requireUser, requirePlan('pro'), sendExports);Bir rolü her zaman sunucu belirler. Kayıt işlemi, req.body içinden yalnızca beklediği alanları kopyalar; dolayısıyla "role": "admin" gönderen bir ziyaretçi bundan hiçbir şey elde edemez.
Görev
KolayGET /admin kaç hesap olduğunu gösterir ve şu anda oturum açmış her üye bunu görebilir. Yalnızca yöneticiler görebilmelidir. Her kullanıcıya sunucu tarafından belirlenen bir rol verilir ve rota bu rolü denetler.
server.jsdosyasında,POST /signupiçinde her yeni kullanıcıyı"role": "member"ile kaydedin. Rolü istek gövdesinden asla almayın:"role": "admin"gönderen bir ziyaretçi yine üye olur.- Bir ara katman yazılımı döndüren
requireRole(role)işlevini yazın. Bu ara katman yazılımıreq.user.roledeğerini denetler:- Değer
roledeğilse 403 durum koduyla ve{"error": "Not allowed"}yanıtını verin. 403 Forbidden, sunucunun kim olduğunuzu bildiği ancak bu işlemi yapmanıza izin verilmediği anlamına gelir. 401, sunucunun kim olduğunuzu bilmediği anlamına gelir. - Değer
roleisenext()çağrısını yapın.
- Değer
requireRole('admin')öğesiniGET /adminrotasına,requireUsersonrasına ekleyin. Bu öğe yalnızcarequireUsertarafından ayarlananreq.userdeğerini okur.
Sunucu başlatıldığında zaten bir yönetici hesabı oluşturur. Kullanıcı adı admin, parolası ise ADMIN_PASSWORD ortam değişkeninden gelen admin-pass-for-tests değeridir.
Deneyin: “Oyna” düğmesine basın. GET /admin isteğini gönderin: durum kodu 401 ve {"error": "Log in first"}. Şimdi mallory adlı bir kullanıcı kendisini yönetici yapmaya çalışıyor. {"username": "mallory", "password": "correct horse", "role": "admin"} ile POST /signup isteğini, ardından {"username": "mallory", "password": "correct horse"} ile POST /login isteğini gönderin. GET /admin isteğini gönderin: durum kodu 403 ve {"error": "Not allowed"}; çünkü fazladan alan onu yönetici yapmadı. {"username": "admin", "password": "admin-pass-for-tests"} ile POST /login isteğini, ardından GET /admin isteğini gönderin: {"users": 2}.
Kendin dene
import express from 'express';
import { ObjectId } from 'mongodb';
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());
// Tek admin hesabı. Parolası ortamdan alınır.
await users.insertOne({ username: 'admin', role: 'admin', passwordHash: await hashPassword(process.env.ADMIN_PASSWORD) });
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,
sameSite: 'lax',
secure: true,
maxAge: 24 * 60 * 60 * 1000,
});
res.json({ username: user.username });
});
// Oturum çerezinin kullanıcısını bulur veya 401 yanıtı verir.
async function requireUser(req, res, next) {
const { sid } = parseCookies(req.get('cookie'));
const session = sid && (await sessions.findOne({ _id: sid }));
const user = session && (await users.findOne({ _id: session.userId }));
if (!user) {
return res.status(401).json({ error: 'Log in first' });
}
req.user = { id: user._id.toHexString(), username: user.username, role: user.role };
next();
}
app.get('/me', requireUser, (req, res) => {
res.json({ username: req.user.username });
});
app.post('/logout', async (req, res) => {
const { sid } = parseCookies(req.get('cookie'));
if (sid) {
await sessions.deleteOne({ _id: sid });
}
res.clearCookie('sid');
res.status(204).end();
});
// Notlarınız bir sayfa olarak.
app.get('/', requireUser, async (req, res) => {
const list = await notes.find({ owner: req.user.id }).toArray();
const items = list.map((note) => `<li>${note.text}</li>`).join('');
res.send(`<!doctype html><title>Vault</title><h1>${req.user.username}'s notes</h1><ul>${items}</ul>`);
});
// Notlar: Her kullanıcı yalnızca kendi notlarını okur ve yazar.
app.get('/notes', requireUser, async (req, res) => {
res.json(await notes.find({ owner: req.user.id }).toArray());
});
app.post('/notes', requireUser, async (req, res) => {
const note = { owner: req.user.id, text: String(req.body.text ?? '') };
await notes.insertOne(note);
res.status(201).json(note);
});
app.get('/notes/:id', requireUser, async (req, res) => {
if (!ObjectId.isValid(req.params.id)) {
return res.status(404).json({ error: 'Note not found' });
}
const note = await notes.findOne({ _id: new ObjectId(req.params.id), owner: req.user.id });
if (!note) {
return res.status(404).json({ error: 'Note not found' });
}
res.json(note);
});
app.delete('/notes/:id', requireUser, async (req, res) => {
if (!ObjectId.isValid(req.params.id)) {
return res.status(404).json({ error: 'Note not found' });
}
const { deletedCount } = await notes.deleteOne({ _id: new ObjectId(req.params.id), owner: req.user.id });
if (!deletedCount) {
return res.status(404).json({ error: 'Note not found' });
}
res.status(204).end();
});
// Yöneticiler için: Kaç hesap olduğunu gösterir.
app.get('/admin', requireUser, async (req, res) => {
res.json({ users: await users.countDocuments() });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Kimlik Doğrulama ve Güvenlik bölümündeki tüm dersler
1Şifreler
Baytlar ve Onaltılık MetinParola Anahtarı TüretmeYeni Bir TuzParolaları Asla SaklamayınAnahtar Baytlarını KarşılaştırmaParolayı DoğrulamaKayıt Kuralları4Sahiplik ve Güvenlik
Yalnızca NotlarınızYönetici RolleriKendi başına pratik yap: Express playground