import express from 'express'; import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, } from '@simplewebauthn/server'; import dbClient from './../db/index.js'; import crypto from 'node:crypto'; import limitedUsers from '../middlewares/limitedUsers.js'; var router = express.Router(); const userPasskeys = []; const origin = `https://${process.env.RP_ID}`; const challenges = {}; const users = {}; const currentLogin = {}; router.get("/register", limitedUsers, function(_, res) { res.render("users/register"); }); router.get("/login", function(_, res) { res.render("users/login"); }); router.get("/generate-auth-options/:username", async function(req, res, next) { dbClient.query({ text: 'SELECT * FROM users WHERE username=$1', values: [req.params.username] }) .then((result) => { const user = result.rows[0]; if (!user) { throw new Error("ya pas de user wesh"); } return dbClient.query({ text: ` SELECT * FROM user_passkeys up JOIN passkeys ON up.passkey_id = passkeys.id WHERE up.user_id = $1 `, values: [user.id] }); }) .then(async (passkeyData) => { const options = await generateAuthenticationOptions({ rpID: process.env.RP_ID, allowCredentials: passkeyData.rows.map(passkey => ({ id: passkey.id, transports: passkey.transports, })), }); currentLogin[req.params.username] = options; return options; }) .then((options) => { res.json(options); }) .catch((err) => { console.error("Error in login"); console.error(err); next(err); }); }); router.post("/verify-auth/", function(req, res) { console.log("Verify auth"); const { authResp, username } = req.body; dbClient.query({ text: 'SELECT * FROM users WHERE username=$1', values: [username] }) .then((result) => { const user = result.rows[0]; if (!user) { throw new Error("ya pas de user wesh"); } return dbClient.query({ text: ` SELECT * FROM passkeys WHERE id = $1 `, values: [authResp.id] }); }) .then(async (passkeyData) => { const passkey = passkeyData.rows[0]; return verifyAuthenticationResponse({ response: authResp, expectedChallenge: currentLogin[username].challenge, expectedOrigin: origin, expectedRPID: process.env.RP_ID, credential: { id: passkey.id, publicKey: new Uint8Array(passkey['public_key']), counter: passkey.counter, transports: passkey.transports, }, equireUserVerification: false, }); }) .then(verification => { const header = Buffer.from(JSON.stringify({ 'alg': 'HS256', 'type': "JWT" })).toString('base64url'); const payload = Buffer.from(JSON.stringify({ verified: true, username: username })).toString('base64url'); const hash = crypto.createHmac('sha256', process.env.AUTH_JWT_SECRET); hash.update(header + "." + payload); const token = header + "." + payload + "." + hash.digest('base64url'); res.cookie('auth_token', token, { httpOnly: true, secure: true, sameSite: 'Strict' }); res.send(verification); }) .catch(err => { console.log("ya erreur") console.error(err); res.status(400).send({ error: err.message }); }); }); router.post("/register/setup", limitedUsers, async function(req, res, next) { const { username } = req.body; generateRegistrationOptions({ rpName: process.env.RP_NAME, rpID: process.env.RP_ID, userName: username, attestationType: 'none', excludeCredentials: userPasskeys.map(passkey => ({ id: passkey.id, transports: passkey.transports, })), authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred', authenticatorAttachment: 'platform', }, }) .then((options) => { challenges[username] = options.challenge; users[username] = options.user; res.send(options); }) .catch((err) => { console.error("Something went wrong"); console.error(err); next(err); }); }); router.post("/register/verify", limitedUsers, function(req, res, next) { const { registration, username } = req.body; verifyRegistrationResponse({ response: registration, expectedChallenge: challenges[username], expectedOrigin: origin, expectedRPID: process.env.RP_ID, }) .then(async (verification) => { const user = users[username]; await dbClient.query("BEGIN"); await dbClient.query({ text: 'INSERT INTO users(id, username) VALUES($1, $2)', values: [user.id, user.name], }); await dbClient.query({ text: 'INSERT INTO passkeys(id, public_key, counter, transports) VALUES($1, $2, $3, $4)', values: [ verification.registrationInfo.credential.id, Buffer.from(verification.registrationInfo.credential.publicKey), verification.registrationInfo.credential.counter, verification.registrationInfo.credential.transports.join(',') ] }); await dbClient.query({ text: 'INSERT INTO user_passkeys(user_id, passkey_id) VALUES($1, $2)', values: [user.id, verification.registrationInfo.credential.id] }); dbClient.query("COMMIT"); res.send(verification); }) .catch((err) => { dbClient.query("ROLLBACK"); console.error("Something went wrong") console.error(err); next(err); }) .finally(() => { delete challenges[username]; delete users[username]; }); }); export default router;