/
mitg
/
Pass
Обзор
Документация
Войти
/
mitg
/
Pass
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
server.js
284 строки
9 KB
mitg
create: server.js, index.html
29 май 2026, 16:21
Верифицирован
29 май 2026, 16:21
d3f9017
Код
Авторство
О чём код?
const crypto = require('crypto'); const express = require('express'); const session = require('express-session'); const Database = require('better-sqlite3'); const path = require('path'); const { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, } = require('@simplewebauthn/server'); const app = express(); const db = new Database('passkeys.db'); // Создаём таблицы db.exec(` DROP TABLE IF EXISTS credentials; DROP TABLE IF EXISTS users; CREATE TABLE users ( id TEXT PRIMARY KEY, username TEXT UNIQUE NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE credentials ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, public_key TEXT NOT NULL, counter INTEGER NOT NULL DEFAULT 0, transports TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE ); `); app.use(express.json()); app.use(express.static(path.join(__dirname, 'public'))); app.use(session({ secret: 'passkey-hub-secret-key', resave: false, saveUninitialized: false, cookie: { secure: false, httpOnly: true, maxAge: 24 * 60 * 60 * 1000 } })); const RP_ID = 'localhost'; const RP_NAME = 'Passkey Hub'; const ORIGIN = 'http://localhost:3000'; // Регистрация - опции app.post('/register/options', async (req, res) => { const { username } = req.body; if (!username) return res.status(400).json({ error: 'Username required' }); let user = db.prepare('SELECT * FROM users WHERE username = ?').get(username); if (!user) { const userId = crypto.randomUUID(); db.prepare('INSERT INTO users (id, username) VALUES (?, ?)').run(userId, username); user = { id: userId, username }; } try { const options = await generateRegistrationOptions({ rpName: RP_NAME, rpID: RP_ID, userID: Buffer.from(user.id), userName: user.username, attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred', }, }); req.session.challenge = options.challenge; req.session.userId = user.id; console.log('✅ Options for:', username); res.json(options); } catch (err) { console.error('Error:', err); res.status(500).json({ error: err.message }); } }); // Регистрация - проверка app.post('/register/verify', async (req, res) => { try { console.log('🔍 Verifying...'); console.log('Request body:', JSON.stringify(req.body, null, 2)); const verification = await verifyRegistrationResponse({ response: req.body, expectedChallenge: req.session.challenge, expectedOrigin: ORIGIN, expectedRPID: RP_ID, requireUserVerification: false, }); console.log('Verification object:', JSON.stringify(verification, null, 2)); if (verification.verified) { // Пробуем получить данные разными способами let credentialID = null; let credentialPublicKey = null; let counter = 0; if (verification.registrationInfo) { credentialID = verification.registrationInfo.credentialID; credentialPublicKey = verification.registrationInfo.credentialPublicKey; counter = verification.registrationInfo.counter; } // Альтернативный способ получения данных if (!credentialID && req.body.response) { credentialID = req.body.rawId; } console.log('Extracted data:', { hasCredID: !!credentialID, hasPubKey: !!credentialPublicKey, counter: counter }); if (credentialID && credentialPublicKey) { // Конвертируем в base64url const credId = typeof credentialID === 'string' ? credentialID : Buffer.from(credentialID).toString('base64url'); const pubKey = typeof credentialPublicKey === 'string' ? credentialPublicKey : Buffer.from(credentialPublicKey).toString('base64'); const transports = req.body.response?.transports || []; console.log('💾 Saving:', credId.substring(0, 20) + '...'); db.prepare(` INSERT INTO credentials (id, user_id, public_key, counter, transports) VALUES (?, ?, ?, ?, ?) `).run(credId, req.session.userId, pubKey, counter, JSON.stringify(transports)); console.log('✅ Saved!'); res.json({ verified: true }); } else { console.log('❌ Missing credential data'); res.json({ verified: false, error: 'Missing credential data' }); } } else { console.log('❌ Verification failed'); res.json({ verified: false }); } } catch (err) { console.error('❌ Error:', err); res.status(400).json({ error: err.message }); } }); // Логин - опции app.post('/login/options', async (req, res) => { const { username } = req.body; if (!username) return res.status(400).json({ error: 'Username required' }); const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username); if (!user) return res.status(404).json({ error: 'User not found' }); const credentials = db.prepare('SELECT * FROM credentials WHERE user_id = ?').all(user.id); console.log(`🔑 Found ${credentials.length} credentials for ${username}`); const allowCredentials = credentials.map(cred => ({ id: Buffer.from(cred.id, 'base64url'), type: 'public-key', transports: JSON.parse(cred.transports || '[]'), })); const options = await generateAuthenticationOptions({ rpID: RP_ID, allowCredentials: allowCredentials.length ? allowCredentials : undefined, userVerification: 'preferred', }); req.session.challenge = options.challenge; req.session.userId = user.id; res.json(options); }); // Логин - проверка app.post('/login/verify', async (req, res) => { try { console.log('🔍 Verifying login...'); const credentialId = req.body.id; if (!credentialId) { return res.status(400).json({ error: 'Missing credential id' }); } const cred = db.prepare('SELECT * FROM credentials WHERE id = ?').get(credentialId); if (!cred) { console.log('❌ Credential not found:', credentialId); return res.status(400).json({ error: 'Credential not found' }); } const verification = await verifyAuthenticationResponse({ response: req.body, expectedChallenge: req.session.challenge, expectedOrigin: ORIGIN, expectedRPID: RP_ID, authenticator: { credentialID: Buffer.from(cred.id, 'base64url'), credentialPublicKey: Buffer.from(cred.public_key, 'base64'), counter: cred.counter, transports: JSON.parse(cred.transports || '[]'), }, requireUserVerification: false, }); if (verification.verified) { db.prepare('UPDATE credentials SET counter = ? WHERE id = ?').run( verification.authenticationInfo.newCounter, cred.id ); const user = db.prepare('SELECT username FROM users WHERE id = ?').get(cred.user_id); console.log('✅ Login successful:', user.username); res.json({ verified: true, username: user.username }); } else { console.log('❌ Login failed'); res.json({ verified: false }); } } catch (err) { console.error('❌ Error:', err); res.status(400).json({ error: err.message }); } }); // Debug app.get('/debug', (req, res) => { const users = db.prepare('SELECT * FROM users').all(); const credentials = db.prepare('SELECT id, user_id, counter FROM credentials').all(); res.json({ users, credentials }); }); // Проверка существования пользователя и наличия ключей app.post('/check-user', (req, res) => { const { username } = req.body; const user = db.prepare('SELECT id FROM users WHERE username = ?').get(username); let hasKeys = false; if (user) { const count = db.prepare('SELECT COUNT(*) as count FROM credentials WHERE user_id = ?').get(user.id); hasKeys = count.count > 0; } res.json({ exists: !!user, hasKeys }); }); app.listen(3000, () => { console.log('🚀 Server: http://localhost:3000'); console.log('🔍 Debug: http://localhost:3000/debug'); }); // Получение списка ключей пользователя app.get('/user/keys', (req, res) => { const { username } = req.query; if (!username) return res.status(400).json({ error: 'Username required' }); const user = db.prepare('SELECT id FROM users WHERE username = ?').get(username); if (!user) return res.json([]); const keys = db.prepare('SELECT id, created_at FROM credentials WHERE user_id = ? ORDER BY created_at DESC').all(user.id); res.json(keys); }); // Удаление ключа app.delete('/delete-key', (req, res) => { const { keyId } = req.body; if (!keyId) return res.status(400).json({ error: 'Key ID required' }); const result = db.prepare('DELETE FROM credentials WHERE id = ?').run(keyId); if (result.changes > 0) { res.json({ success: true }); } else { res.status(404).json({ error: 'Key not found' }); } });