diff --git a/models/user.js b/models/user.js index eaeaa9b..0cfb321 100644 --- a/models/user.js +++ b/models/user.js @@ -29,8 +29,14 @@ const User = mongoose.model( authDuration: { type: Number, }, - regDuration: { - type: Number, + registrationTimings: { + type: Object, + }, + passwordAuthTimings: { + type: Object, + }, + fido2AuthTimings: { + type: Object, }, userAgent: { type: Object diff --git a/routes/register.js b/routes/register.js index 9e3e683..4e28316 100644 --- a/routes/register.js +++ b/routes/register.js @@ -1,5 +1,5 @@ import express from "express"; -import fido2lib from "@dannymoerkerke/fido2-lib"; +import fido2lib from "fido2-library"; import base64url from "base64url"; import crypto from "crypto"; import bcrypt from "bcrypt"; @@ -57,7 +57,7 @@ router.post("/registration-options", async (req, res) => { }); router.post("/register", async (req, res) => { - const { credential, email, firstName, lastName, password, regDuration, userAgent } = req.body; + const { credential, email, firstName, lastName, password, userAgent } = req.body; const challenge = new Uint8Array(req.session.challenge.data).buffer; const base64RawId = credential.rawId; @@ -91,12 +91,11 @@ router.post("/register", async (req, res) => { lastName, email, password: hash, - regDuration: regDuration, userAgent: userAgent, }); user.save(); - const secretVerificationCode = crypto.randomBytes(32).toString("hex") + const secretVerificationCode = crypto.randomBytes(32).toString("hex"); const verificationCode = await AccountVerificationCode.create({ email: user.email, code: secretVerificationCode, @@ -121,57 +120,66 @@ router.post("/authentication-options", async (req, res) => { const authnOptions = await fido.assertionOptions(); const { email } = req.body; const user = await User.findOne({ email }); - if (user) { - req.session.challenge = Buffer.from(authnOptions.challenge); - req.session.userHandle = user.id; - authnOptions.challenge = Buffer.from(authnOptions.challenge); - authnOptions.rawId = user.credentialId; - res.json(authnOptions); - } else { - res.status(404).json({ error: "User does not exist." }); + + if (!user) { + return res.status(401).json({ error: "User does not exist." }); } + + if (user.status !== "active") { + return res.status(401).json({ error: "Please activate your account first." }); + } + + req.session.challenge = Buffer.from(authnOptions.challenge); + req.session.userHandle = user.id; + authnOptions.challenge = Buffer.from(authnOptions.challenge); + authnOptions.rawId = user.credentialId; + res.json(authnOptions); }); router.post("/authenticate", async (req, res) => { const { credential, authDuration, password, email, method } = req.body; + const user = await User.findOne({ email }); + console.log("🚀 ~ file: register.js ~ line 138 ~ router.post ~ user", user); + + if (!user) { + return res.status(401).json({ error: "Incorrect login details" }); + } + + if (user.status !== "active") { + return res.status(401).json({ error: "Please activate your account first." }); + } + if (method === "password") { try { - const user = await User.findOne({ email }); - if (user) { - if (verifyPassword(user, password)) { - return res.status(200).json({ status: "ok" }); - } else return res.status(401).json({ error: "Incorrect login details" }); + if (verifyPassword(user, password)) { + return res.status(200).json({ status: "ok" }); } else return res.status(401).json({ error: "Incorrect login details" }); } catch (err) { return res.status(500).json({ error: "Server error: " + err.message }); } - } - - credential.rawId = new Uint8Array(Buffer.from(credential.rawId, "base64")).buffer; - - const challenge = new Uint8Array(req.session.challenge.data).buffer; - const { publicKey, prevCounter } = req.session; - - if (publicKey === "undefined" || prevCounter === undefined) { - console.log("Not found"); - res.status(404).json({ error: "Credential not found" }); } else { - const assertionExpectations = { - challenge, - origin: config.siteUrl, - factor: "either", - publicKey, - prevCounter, - userHandle: new Uint8Array(Buffer.from(req.session.userHandle, "base64")).buffer, - }; - try { - await fido.assertionResult(credential, assertionExpectations); - await User.findOneAndUpdate({ email: email }, { authDuration: authDuration }); - res.json({ status: "ok" }); - } catch (e) { - console.log(e); - res.status(500).json({ error: "Failed due internal server error" }); + credential.rawId = new Uint8Array(Buffer.from(credential.rawId, "base64")).buffer; + const challenge = new Uint8Array(req.session.challenge.data).buffer; + const { publicKey, prevCounter } = req.session; + if (publicKey === "undefined" || prevCounter === undefined) { + res.status(404).json({ error: "Credential not found" }); + } else { + const assertionExpectations = { + challenge, + origin: config.siteUrl, + factor: "either", + publicKey, + prevCounter, + userHandle: new Uint8Array(Buffer.from(req.session.userHandle, "base64")).buffer, + }; + try { + await fido.assertionResult(credential, assertionExpectations); + await User.findOneAndUpdate({ email: email }, { authDuration: authDuration }); + res.json({ status: "ok" }); + } catch (e) { + res.status(500).json({ error: "Failed due internal server error" }); + } } } }); diff --git a/routes/timings.js b/routes/timings.js new file mode 100644 index 0000000..29d360c --- /dev/null +++ b/routes/timings.js @@ -0,0 +1,40 @@ +import express from "express"; +// import cfg from "config"; +import User from "../models/user.js"; + +// const mode = process.env.NODE_ENV || "dev"; +// const config = cfg.get(mode); +const router = express.Router(); + +router.post("/registration", async (req, res) => { + const { email, registrationTimings } = req.body; + try { + await User.findOneAndUpdate({ email: email }, { registrationTimings: registrationTimings }); + return res.status(200).json({ status: "Success" }); + } catch (err) { + return res.status(500).json({ error: "Server error: " + err }); + } +}); + +router.post("/fido2-authentication", async (req, res) => { + const { email, fido2AuthTimings } = req.body; + try { + await User.findOneAndUpdate({ email: email }, { fido2AuthTimings }); + return res.status(200).json({ status: "Success" }); + } catch (err) { + return res.status(500).json({ error: "Server error: " + err }); + } +}); + +router.post("/pw-authentication", async (req, res) => { + const { email, passwordAuthTimings } = req.body; + try { + await User.findOneAndUpdate({ email: email }, { passwordAuthTimings }); + return res.status(200).json({ status: "Success" }); + } catch (err) { + return res.status(500).json({ error: "Server error: " + err }); + } +}); + + +export default router; diff --git a/routes/verification.js b/routes/verification.js index 7a544aa..d6271fa 100644 --- a/routes/verification.js +++ b/routes/verification.js @@ -12,8 +12,17 @@ router.post("/verify-account", async (req, res) => { try { const verificationCode = await AccountVerificationCode.findOne({ code: secretCode }); const user = await User.findOne({ email: email }); - if ((user && verificationCode) && verificationCode.email === user.email) { - await User.findOneAndUpdate({ email: email }, { status: "active" }); + if (user && verificationCode && verificationCode.email === user.email) { + await User.findOneAndUpdate( + { email: email }, + { + status: "active", + registrationTimings: { + ...user.registrationTimings, + verificationDuration: (Date.now() - user.registrationTimings.regEndTime) / 1000, + }, + } + ); return res.status(200).json({ status: "Success" }); } else { return res.status(401).json({ error: "Incorrect verification code" }); diff --git a/server.js b/server.js index 68e53fa..25ff0a8 100644 --- a/server.js +++ b/server.js @@ -11,6 +11,7 @@ import errorHandler from "errorhandler"; import connectMongo from "connect-mongo"; import registerRoutes from "./routes/register.js"; import verificationRoutes from "./routes/verification.js"; +import timingRoutes from "./routes/timings.js"; import dbConnection from "./helpers/db.js"; const app = express(); @@ -74,7 +75,7 @@ app.use(errorHandler()); app.use("/api/v1", registerRoutes); app.use("/api/v1/verification", verificationRoutes); - +app.use("/api/v1/timing", timingRoutes) app.listen(process.env.PORT || config.port, () => { console.log(chalk.yellow(".......................................")); console.log(chalk.green(config.name));