Hanya Catatanmu
Termasuk bagian Autentikasi dan Keamanan dalam perjalanan Node.js di Coddy. Pelajaran 18 dari 24.
Status sudah masuk membuktikan siapa dirimu, bukan bahwa sebuah catatan adalah milikmu. GET /notes/:id mencari catatan hanya berdasarkan id-nya, jadi siapa pun yang sudah masuk dan memiliki id, dari tautan yang dibagikan atau log, dapat membaca catatan itu. Memeriksa pemilik disebut otorisasi.
Masukkan pemilik ke dalam query itu sendiri agar database tidak pernah mengembalikan dokumen milik orang lain. Berikan jawaban 404, bukan 403, jika dokumen tidak ditemukan: 403 akan memberi tahu pengunjung bahwa catatan itu ada.
const order = await orders.findOne({
_id: new ObjectId(req.params.id),
customer: req.user.id,
});
if (!order) {
return res.status(404).json({ error: 'Order not found' });
}Setiap query yang mengakses data satu pengguna memerlukan filter: untuk membaca, memperbarui, dan menghapus.
Tantangan
MudahBab ini kembali membahas login sesi dari bab 2, dan server-nya sudah berkembang. Sekarang ada halaman di GET /, rute untuk satu catatan di GET /notes/:id dan DELETE /notes/:id, serta akun admin. Kedua rute /notes/:id memiliki celah: keduanya mencari catatan hanya berdasarkan id-nya. Pengguna mana pun yang sudah login dan mengetahui sebuah id dapat membaca atau menghapus catatan milik orang lain. Tutup celah ini.
- Di
server.js, dalamGET /notes/:id, cari catatan berdasarkan_iddanownersekaligus. Pemiliknya harus pengguna yang sedang login,req.user.id. - Lakukan hal yang sama di
DELETE /notes/:id, agar tidak ada yang bisa menghapus catatan yang bukan miliknya. - Jika tidak ada catatan yang cocok, kedua rute sudah merespons dengan status 404 dan
{"error": "Note not found"}. Gunakan respons itu juga untuk catatan milik orang lain. Respons 403 akan memberi tahu pengunjung bahwa catatan itu ada.
Coba: Tekan tombol Jalankan.
- Kirim
POST /signup, laluPOST /login, keduanya dengan{"username": "ada", "password": "correct horse"}. - Kirim
POST /notesdengan{"text": "Safe code 4417"}, lalu salin_iddari respons.GET /notes/diikuti id tersebut akan mengembalikan catatan itu. - Buat akun dan login dengan
{"username": "bob", "password": "correct horse"}. KirimGET /notes/diikuti id milik ada: status 404 dan{"error": "Note not found"}. KirimDELETE /notes/diikuti id yang sama: hasilnya juga 404. - Login lagi sebagai ada, dengan body miliknya dari langkah pertama. Catatannya masih ada. Kirim
DELETEsekarang: status 204.
Coba sendiri
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());
// Satu-satunya akun admin. Kata sandinya berasal dari environment.
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 });
});
// Mencari pengguna dari cookie sesi, atau mengembalikan 401.
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();
});
// Catatanmu dalam bentuk halaman.
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>`);
});
// Catatan: setiap pengguna hanya membaca dan menulis catatannya sendiri.
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) });
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) });
if (!deletedCount) {
return res.status(404).json({ error: 'Note not found' });
}
res.status(204).end();
});
// Untuk admin: jumlah akun yang ada.
app.get('/admin', requireUser, async (req, res) => {
res.json({ users: await users.countDocuments() });
});
app.listen(3000, () => {
console.log('Vault is running on port 3000');
});
Pelajaran ini berisi kuis singkat. Mulai pelajarannya untuk menjawab kuis dan melacak progresmu.
Semua pelajaran di Autentikasi dan Keamanan
1Kata Sandi
Byte dan Teks HeksadesimalMembuat Kunci Kata SandiSalt BaruJangan Simpan Kata SandiMembandingkan Byte KunciMemeriksa Kata SandiAturan Pendaftaran4Pemilik dan Keamanan
Hanya CatatanmuPeran AdminLatihan sendiri: Playground Express