Files
pathtoglory_blog/app/server/routes/users.js

220 lines
5.8 KiB
JavaScript

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;