From 63bcc4bafd4700c0ed8774131398444e690e54e5 Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Thu, 27 Feb 2025 16:34:20 +0530 Subject: [PATCH] update webauthn flow --- src/server.ts | 2 ++ src/user/user.schema.ts | 4 +-- src/user/user.service.ts | 21 +++++++++++- src/utils/mail.ts | 2 +- src/webauthn/webauthn.route.ts | 61 +++++++++++----------------------- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/server.ts b/src/server.ts index 2f665ce..ab18017 100644 --- a/src/server.ts +++ b/src/server.ts @@ -19,6 +19,7 @@ import { noteSchemas } from "./note/note.schema"; import { webAuthnRoutes } from "./webauthn/webauthn.route"; import { configSchemas } from "./config/config.schema"; import { mailSchemas } from "./mailProxy/mailProxy.schema"; +import { viewSchemas } from "./view/view.schema"; const app = fastify({ logger: true, trustProxy: true }); @@ -51,6 +52,7 @@ for (const schema of [ ...noteSchemas, ...configSchemas, ...mailSchemas, + ...viewSchemas, ]) { app.addSchema(schema); } diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index a9e364c..e9adba0 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -36,7 +36,7 @@ const userSchema = new mongoose.Schema({ }, { _id: false } ), - otp: new mongoose.Schema( + token: new mongoose.Schema( { value: String, expiry: Date, @@ -61,7 +61,7 @@ const userCore = { invalid_type_error: "Email must be a valid string", }) .email(), - avatar: z.string().url().optional(), + avatar: z.string().optional(), role: z.enum(roles), orgId: z.string().optional(), }; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 5df4eaf..b16b2e9 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,16 +1,31 @@ import mongoose from "mongoose"; -import { generateId } from "../utils/id"; +import { generateId, generateToken } from "../utils/id"; import { CreateUserInput, UpdateUserInput, userModel } from "./user.schema"; +import { sendMail } from "../utils/mail"; export async function createUser(input: CreateUserInput, tenantId: string) { + const token = await generateToken(); + const user = await userModel.create({ tenantId: tenantId, pid: generateId(), name: input.firstName + " " + input.lastName, createdAt: new Date(), + token: { + value: token, + expiry: new Date(Date.now() + 3600 * 6 * 1000), + }, ...input, }); + const sent = await sendMail( + input.email, + "You have been invited to Quicker Permtis.", + `Click here to register.` + ); + return user; } @@ -24,6 +39,10 @@ export async function getUser(userId: string) { }); } +export async function getUserByToken(token: string) { + return await userModel.findOne({ "token.value": token }); +} + export async function getUserByEmail(email: string) { return await userModel.findOne({ email: email }); } diff --git a/src/utils/mail.ts b/src/utils/mail.ts index 5604f91..910f9e8 100644 --- a/src/utils/mail.ts +++ b/src/utils/mail.ts @@ -45,7 +45,7 @@ export async function sendMail( toAddress: to, subject, content: body, - mailFormat: "plaintext", + mailFormat: "html", }, }); } catch (err) { diff --git a/src/webauthn/webauthn.route.ts b/src/webauthn/webauthn.route.ts index cb92435..29ee281 100644 --- a/src/webauthn/webauthn.route.ts +++ b/src/webauthn/webauthn.route.ts @@ -8,10 +8,8 @@ import { verifyRegistrationResponse, } from "@simplewebauthn/server"; import { isoUint8Array } from "@simplewebauthn/server/helpers"; -import { getUserByEmail } from "../user/user.service"; +import { getUserByEmail, getUserByToken } from "../user/user.service"; import { createSession } from "../auth/auth.service"; -import { generateOTP } from "../utils/id"; -import { sendMail } from "../utils/mail"; const rpID = process.env.SERVER_DOMAIN; const rpName = "Quicker Permits"; @@ -19,36 +17,36 @@ const origin = `https://${rpID}`; export async function webAuthnRoutes(fastify: FastifyInstance) { // Registration request - fastify.post<{ Body: { email: string } }>( + fastify.post<{ Body: { token: string } }>( "/register/request", { schema: { body: { type: "object", properties: { - email: { type: "string" }, + token: { type: "string" }, }, }, }, }, async (req, res: FastifyReply) => { - const { email } = req.body; + const { token } = req.body; - if (!email) { - return res.code(400).send({ error: "Email is required" }); + if (!token) { + return res.code(400).send({ error: "bad request" }); } - const userInDB = await getUserByEmail(email); - if (!userInDB) return res.code(400).send({ error: "not allowed" }); - - const otp = generateOTP(); + const userInDB = await getUserByToken(token); + if (!userInDB) return res.code(400).send({ error: "bad request" }); + if (new Date() > userInDB.token.expiry) + return res.code(400).send({ error: "link expired" }); const userId = userInDB.id; const options = await generateRegistrationOptions({ rpName, rpID, userID: isoUint8Array.fromUTF8String(userId), - userName: email, + userName: userInDB.email, attestationType: "none", excludeCredentials: userInDB.passKeys.map((cred) => ({ // @ts-ignore @@ -61,23 +59,11 @@ export async function webAuthnRoutes(fastify: FastifyInstance) { userInDB.challenge = { value: options.challenge, - expiry: new Date(Date.now() + 60 * 5 * 1000), - }; - - userInDB.otp = { - value: otp, - expiry: new Date(Date.now() + 60 * 5 * 1000), + expiry: new Date(Date.now() + 60 * 10 * 1000), }; await userInDB.save(); - const sent = await sendMail( - email, - "Code for Quicker Permits Authentication", - `Your code is ${otp}` - ); - if (!sent) return res.code(500).send({ error: "server error" }); - return res.send(options); } ); @@ -85,8 +71,7 @@ export async function webAuthnRoutes(fastify: FastifyInstance) { // Registration verification fastify.post<{ Body: { - email: string; - code: string; + token: string; attestationResponse: RegistrationResponseJSON; }; }>( @@ -96,31 +81,25 @@ export async function webAuthnRoutes(fastify: FastifyInstance) { body: { type: "object", properties: { - email: { type: "string" }, - code: { type: "string" }, + token: { type: "string" }, attestationResponse: { type: "object", additionalProperties: true }, }, }, }, }, async (req, res: FastifyReply) => { - const { email, code, attestationResponse } = req.body; + const { token, attestationResponse } = req.body; - const userInDB = await getUserByEmail(email); - if (!userInDB) return res.code(400).send({ error: "not allowed" }); + const userInDB = await getUserByToken(token); + if (!userInDB) return res.code(400).send({ error: "bad request" }); const challenge = userInDB.challenge; - const requiredOTP = userInDB.otp; + const tokenInDb = userInDB.token; + if (!challenge) return res.code(400).send({ error: "not allowed" }); - if (!challenge || !requiredOTP) - return res.code(400).send({ error: "not allowed" }); - - if (new Date() > challenge.expiry || new Date() > requiredOTP.expiry) + if (new Date() > challenge.expiry || new Date() > tokenInDb.expiry) return res.code(400).send({ error: "challenge expired" }); - if (code != requiredOTP.value) - return res.code(400).send({ error: "invalid code" }); - try { const verification = await verifyRegistrationResponse({ response: attestationResponse,