feat: passkey в профиле и входе, кнопка админки в кабинете

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shop
2026-05-17 12:58:00 +03:00
parent 711110c03b
commit e71bfa35dc
12 changed files with 757 additions and 11 deletions
+50 -2
View File
@@ -4,9 +4,10 @@ const { query, formatPrice } = require('../db');
const { getCart, cartCount } = require('../cart');
const { requireAuth } = require('../middleware/auth');
const { requireCookieConsent } = require('../middleware/cookieConsent');
const { ROLE_LABELS } = require('../constants/roles');
const { ROLES, ROLE_LABELS } = require('../constants/roles');
const { asyncHandler } = require('../utils/asyncHandler');
const { expireOldReservations } = require('../services/reservations');
const webauthn = require('../services/webauthn');
const router = express.Router();
@@ -21,7 +22,7 @@ router.use((req, res, next) => {
async function loadAccountUser(userId) {
const { rows } = await query(
'SELECT id, email, name, role, created_at FROM users WHERE id = $1',
'SELECT id, email, name, role, created_at, passkey_enabled FROM users WHERE id = $1',
[userId]
);
return rows[0];
@@ -44,12 +45,16 @@ function accountRender(res, options) {
success,
activeTab,
formatPrice,
passkeys,
isAdmin,
} = options;
res.render('account/index', {
title: 'Личный кабинет',
user,
orderCount,
reservations: reservations || [],
passkeys: passkeys || [],
isAdmin: Boolean(isAdmin),
roleLabels: ROLE_LABELS,
formatPrice: formatPrice || res.locals.formatPrice,
error: error || null,
@@ -78,10 +83,14 @@ router.get(
[user.id]
);
const passkeys = await webauthn.getCredentialsForUser(user.id);
accountRender(res, {
user,
orderCount: countResult.rows[0].n,
reservations,
passkeys,
isAdmin: user.role === ROLES.ADMIN,
formatPrice,
success: req.query.success ? decodeURIComponent(String(req.query.success)) : null,
error: req.query.error ? decodeURIComponent(String(req.query.error)) : null,
@@ -187,4 +196,43 @@ router.post(
})
);
router.post(
'/passkey/disable',
requireAuth,
asyncHandler(async (req, res) => {
const { current_password } = req.body;
if (!(await verifyPassword(req.session.userId, current_password))) {
return res.redirect(
'/account?tab=passkey&error=' + encodeURIComponent('Неверный пароль')
);
}
await webauthn.disablePasskeys(req.session.userId);
res.redirect(
'/account?tab=passkey&success=' + encodeURIComponent('Вход по passkey отключён')
);
})
);
router.post(
'/passkey/credentials/:id/delete',
requireAuth,
asyncHandler(async (req, res) => {
const { current_password } = req.body;
if (!(await verifyPassword(req.session.userId, current_password))) {
return res.redirect(
'/account?tab=passkey&error=' + encodeURIComponent('Неверный пароль')
);
}
const credId = parseInt(req.params.id, 10);
if (!Number.isFinite(credId)) {
return res.redirect('/account?tab=passkey&error=' + encodeURIComponent('Некорректный ключ'));
}
const ok = await webauthn.deleteCredential(req.session.userId, credId);
if (!ok) {
return res.redirect('/account?tab=passkey&error=' + encodeURIComponent('Ключ не найден'));
}
res.redirect('/account?tab=passkey&success=' + encodeURIComponent('Passkey удалён'));
})
);
module.exports = router;