188 lines
7.3 KiB
JavaScript
188 lines
7.3 KiB
JavaScript
// backend/routes/auth/settings.js
|
|
const express = require('express');
|
|
const bcrypt = require('bcryptjs');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const User = require('../../models/User');
|
|
const { requireAuth, makeToken, setCookieToken, upload, getUploadsDir } = require('./helpers');
|
|
|
|
const router = express.Router();
|
|
|
|
// ─── POST /api/auth/change-password ──────────────────────────────────────────
|
|
router.post('/change-password', requireAuth, async (req, res) => {
|
|
try {
|
|
const { currentPassword, newPassword } = req.body;
|
|
if (!currentPassword || !newPassword) {
|
|
return res.status(400).json({ error: 'Current password and new password are required' });
|
|
}
|
|
|
|
const user = await User.findById(req.user.id).select('+password_hash');
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
if (!bcrypt.compareSync(currentPassword, user.password_hash)) {
|
|
return res.status(400).json({ error: 'Password saat ini salah' });
|
|
}
|
|
|
|
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{6,}$/;
|
|
if (!passwordRegex.test(newPassword)) {
|
|
return res.status(400).json({ error: 'Password baru tidak memenuhi kriteria: minimal 6 karakter, serta mengandung huruf besar, huruf kecil, dan angka.' });
|
|
}
|
|
|
|
user.password_hash = bcrypt.hashSync(newPassword, 10);
|
|
await user.save();
|
|
|
|
res.json({ ok: true, message: 'Password berhasil diubah!' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ─── POST /api/auth/change-username ──────────────────────────────────────────
|
|
router.post('/change-username', requireAuth, async (req, res) => {
|
|
try {
|
|
const { currentPassword, newUsername } = req.body;
|
|
if (!currentPassword || !newUsername) {
|
|
return res.status(400).json({ error: 'Current password and new username are required' });
|
|
}
|
|
if (newUsername.length < 4 || /[^a-zA-Z0-9_]/.test(newUsername)) {
|
|
return res.status(400).json({ error: 'Username baru tidak valid (minimal 4 karakter, hanya huruf, angka, dan underscore).' });
|
|
}
|
|
|
|
const user = await User.findById(req.user.id).select('+password_hash');
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
if (!bcrypt.compareSync(currentPassword, user.password_hash)) {
|
|
return res.status(400).json({ error: 'Password saat ini salah' });
|
|
}
|
|
|
|
const existing = await User.findOne({ username: newUsername });
|
|
if (existing) return res.status(400).json({ error: 'Username sudah digunakan oleh akun lain' });
|
|
|
|
user.username = newUsername;
|
|
await user.save();
|
|
|
|
const newToken = makeToken(user);
|
|
setCookieToken(res, newToken);
|
|
|
|
res.json({ ok: true, message: 'Username berhasil diubah!', newUsername });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ─── POST /api/auth/change-account-name ──────────────────────────────────────
|
|
router.post('/change-account-name', requireAuth, async (req, res) => {
|
|
try {
|
|
const { currentPassword, newAccountName } = req.body;
|
|
if (!currentPassword || newAccountName == null) {
|
|
return res.status(400).json({ error: 'Current password and new account name are required' });
|
|
}
|
|
if (!newAccountName.trim()) {
|
|
return res.status(400).json({ error: 'Nama akun tidak boleh kosong' });
|
|
}
|
|
|
|
const user = await User.findById(req.user.id).select('+password_hash');
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
if (!bcrypt.compareSync(currentPassword, user.password_hash)) {
|
|
return res.status(400).json({ error: 'Password saat ini salah' });
|
|
}
|
|
|
|
user.account_name = newAccountName.trim();
|
|
await user.save();
|
|
|
|
const newToken = makeToken(user);
|
|
setCookieToken(res, newToken);
|
|
|
|
res.json({ ok: true, message: 'Nama akun berhasil diubah!', newAccountName: user.account_name });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
// ─── POST /api/auth/upload-profile-picture ───────────────────────────────────
|
|
// Menerima JSON: { profile_picture_base64: "data:image/png;base64,...", user_id? }
|
|
// Menghindari multipart/form-data yang bermasalah melalui Apache proxy layer
|
|
router.post('/upload-profile-picture', requireAuth, async (req, res) => {
|
|
try {
|
|
const { profile_picture_base64, user_id } = req.body;
|
|
|
|
if (!profile_picture_base64) {
|
|
return res.status(400).json({ error: 'No image data provided. Please select an image file first.' });
|
|
}
|
|
|
|
// Validasi format base64 data URL
|
|
const matches = profile_picture_base64.match(/^data:image\/(png|jpg|jpeg|gif|webp);base64,(.+)$/);
|
|
if (!matches) {
|
|
return res.status(400).json({ error: 'Invalid image format. Only PNG, JPG, GIF, WEBP are allowed.' });
|
|
}
|
|
|
|
const ext = matches[1] === 'jpeg' ? 'jpg' : matches[1];
|
|
const base64Data = matches[2];
|
|
|
|
// Validasi ukuran (max 5MB uncompressed)
|
|
const fileSizeBytes = Buffer.byteLength(base64Data, 'base64');
|
|
if (fileSizeBytes > 5 * 1024 * 1024) {
|
|
return res.status(400).json({ error: 'Image too large. Maximum size is 5MB.' });
|
|
}
|
|
|
|
// Tentukan target user (self atau admin update user lain)
|
|
const targetId = user_id || req.user.id;
|
|
const user = await User.findById(targetId);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
// Hapus foto profil lama jika ada
|
|
if (user.profile_picture) {
|
|
const oldPath = path.join(getUploadsDir(), user.profile_picture);
|
|
if (fs.existsSync(oldPath)) {
|
|
try { fs.unlinkSync(oldPath); } catch (_) {}
|
|
}
|
|
}
|
|
|
|
// Simpan file baru
|
|
const filename = `profile-${targetId}-${Date.now()}.${ext}`;
|
|
const filePath = path.join(getUploadsDir(), filename);
|
|
fs.writeFileSync(filePath, base64Data, 'base64');
|
|
|
|
user.profile_picture = filename;
|
|
await user.save();
|
|
|
|
// Perbarui token hanya jika user mengupdate foto dirinya sendiri
|
|
if (String(targetId) === String(req.user.id)) {
|
|
const newToken = makeToken(user);
|
|
setCookieToken(res, newToken);
|
|
}
|
|
|
|
res.json({ ok: true, message: 'Profile picture updated successfully.', profile_picture: filename });
|
|
} catch (err) {
|
|
console.error('[Upload Error]', err);
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
|
|
// ─── POST /api/auth/remove-profile-picture ───────────────────────────────────
|
|
router.post('/remove-profile-picture', requireAuth, async (req, res) => {
|
|
try {
|
|
const user = await User.findById(req.user.id);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
if (user.profile_picture) {
|
|
const filePath = path.join(getUploadsDir(), user.profile_picture);
|
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
}
|
|
|
|
user.profile_picture = null;
|
|
await user.save();
|
|
|
|
const newToken = makeToken(user);
|
|
setCookieToken(res, newToken);
|
|
|
|
res.json({ ok: true, message: 'Foto profil berhasil dihapus' });
|
|
} catch (err) {
|
|
res.status(500).json({ error: err.message });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|