From 1071560b6f0292a56952d834c0f165f87e176fac Mon Sep 17 00:00:00 2001 From: cedoor Date: Mon, 4 Sep 2023 11:29:03 +0200 Subject: [PATCH 1/2] feat: add server-side pcd verification --- apps/client/src/App.tsx | 95 +++++++++++++++++++++------------------- apps/server/package.json | 2 + apps/server/src/index.ts | 91 ++++++++++++++++++++++---------------- yarn.lock | 1 + 4 files changed, 106 insertions(+), 83 deletions(-) diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index e9a1242..37b70a3 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,75 +1,78 @@ -import { deserialize, EdDSAPCDPackage, init as initEdDSAPCD, verify } from "@pcd/eddsa-pcd" +import { EdDSAPCDPackage } from "@pcd/eddsa-pcd" import { getWithoutProvingUrl, openPassportPopup, usePassportPopupMessages } from "@pcd/passport-interface" -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect } from "react" /** - * This component consumes a message signed with an EdDSA key. If you have provided a valid signature, - * it uses the color as the background of the web app page. This background is preserved as long as - * you do not consume a signature on a different colour. + * This page allows users to get an EdDSA PCD containing a color as a message signed by + * the issuer. If the signature is valid the color is stored in the server and the background color + * of this page will be changed. */ export default function App() { const [passportPCDString] = usePassportPopupMessages() - const [bgColor, setBgColor] = useState() + // Get the latest color stored in the server. useEffect(() => { - // Get the color extracted from the latest valid message signed with an EdDSA key. - const getLatestConsumedColor = async () => { - const response = await fetch(`http://localhost:${process.env.SERVER_PORT}/color/get`, { + ;(async () => { + const response = await fetch(`http://localhost:${process.env.SERVER_PORT}/color`, { method: "GET", mode: "cors" }) - if (!!response.ok) { - const body = await response.json() - setBgColor(body.color) + if (response.status === 404) { + return } - } - getLatestConsumedColor() - }, []) + if (!response.ok) { + alert("Some error occurred") + return + } - useEffect(() => { - // Update the background color accordingly to color change. - const appElement = document.getElementById("app")! + const { color } = await response.json() - appElement.style.backgroundColor = `#${bgColor}` - }, [bgColor]) + setBgColor(color) + })() + }, []) + // Store the color in the server if its PCD is valid + // and then updates the background color of this page. useEffect(() => { ;(async () => { - await initEdDSAPCD() - if (passportPCDString) { - const { pcd: serializedPCD } = JSON.parse(passportPCDString) - - const pcd = await deserialize(serializedPCD) - - if (await verify(pcd)) { - // Get PCD claim (color). - const color = pcd.claim.message[0].toString(16) - - // Store consumed color on the server. - await fetch(`http://localhost:${process.env.SERVER_PORT}/color/set`, { - method: "POST", - mode: "cors", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - color: color - }) + const { pcd } = JSON.parse(passportPCDString) + + const response = await fetch(`http://localhost:${process.env.SERVER_PORT}/color`, { + method: "POST", + mode: "cors", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + pcd }) + }) - alert(`The signature is valid, #${color} will be used as a background color!`) - - setBgColor(color) - } else { - alert(`The signature is not valid!`) + if (!response.ok) { + alert("Some error occurred") + return } + + alert(`The signature is valid, the background color will be changed`) + + const { color } = await response.json() + + setBgColor(color) } })() }, [passportPCDString]) + // Update the background color. + const setBgColor = useCallback((color: string) => { + const appElement = document.getElementById("app")! + + appElement.style.backgroundColor = color + }, []) + + // Get the EdDSA PCD with the color signed by the issuer. const getEdDSAPCD = useCallback(() => { const url = getWithoutProvingUrl( process.env.PCDPASS_URL as string, @@ -80,5 +83,5 @@ export default function App() { openPassportPopup("/popup", url) }, []) - return + return } diff --git a/apps/server/package.json b/apps/server/package.json index 3716c3f..ea4bc3e 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,5 +1,6 @@ { "name": "server", + "type": "module", "version": "0.1.0", "main": "src/index.ts", "packageManager": "yarn@3.6.3", @@ -8,6 +9,7 @@ "start": "ts-node --esm src/index.ts" }, "dependencies": { + "@pcd/eddsa-pcd": "^0.1.1", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2" diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index b5152af..2e9fb27 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,60 +1,77 @@ -import express, { Express, Request, Response } from 'express'; -import dotenv from "dotenv" +import { deserialize, init as initEdDSAPCD, verify } from "@pcd/eddsa-pcd" import cors from "cors" +import dotenv from "dotenv" +import express, { Express, Request, Response } from "express" dotenv.config({ path: `${process.cwd()}/../../.env` }) -const app: Express = express(); -const port = process.env.SERVER_PORT || 3000; +// The PCD package must be initialized before using its methods. +await initEdDSAPCD() + +const app: Express = express() +const port = process.env.SERVER_PORT || 3000 // Middlewares. app.use(express.json()) app.use(express.urlencoded({ extended: true })) app.use(cors()) -app.get('/', (req: Request, res: Response) => { - res.send('Express + TypeScript Server'); -}); +app.get("/", (_req: Request, res: Response) => { + res.send("Express + TypeScript Server") +}) // Store a color on a NodeJS environment variable. -app.post('/color/set', (req: Request, res: Response) => { - try { - if (!req.body.color) { - console.error(`[ERROR] No color specified!`) +app.post("/color", async (req: Request, res: Response) => { + try { + if (!req.body.pcd) { + console.error(`[ERROR] No PCD specified`) + + res.status(400).send() + return + } + + const pcd = await deserialize(req.body.pcd) - res.status(400).send() - } else { - // Set the color on NodeJS `COLOR` environment variable. - process.env['COLOR'] = req.body.color; + if (!(await verify(pcd))) { + console.error(`[ERROR] PCD is not valid`) - console.debug(`[OKAY] color has been set to ${process.env.COLOR}`) + res.status(401).send() + return + } - res.status(200).send() + // Set the color on NodeJS `COLOR` environment variable. + process.env.COLOR = `#${pcd.claim.message[0].toString(16)}` + + console.debug(`[OKAY] color has been set to ${process.env.COLOR}`) + + res.json({ color: process.env.COLOR }).status(200).send() + } catch (error: any) { + console.error(`[ERROR] ${error}`) + + res.send(500) } - } catch (error: any) { - console.error(`[ERROR] ${error}`) - res.send(500) - } -}); +}) // Get the color stored on the NodeJS environment variable. -app.get('/color/get', (req: Request, res: Response) => { - try { - if (!process.env.COLOR) { - console.error(`[ERROR] No color has been saved yet!`) +app.get("/color", (_req: Request, res: Response) => { + try { + if (!process.env.COLOR) { + console.error(`[ERROR] No color has been stored yet`) + + res.status(404).send() + return + } + + console.debug(`[OKAY] color ${process.env.COLOR} has been successfully sent`) - res.status(404).send() - } else { - console.debug(`[OKAY] color ${process.env.COLOR} has been successfully sent`) + res.json({ color: process.env.COLOR }).status(200) + } catch (error: any) { + console.error(`[ERROR] ${error}`) - res.json({ color: process.env.COLOR }).status(200); + res.send(500) } - } catch (error: any) { - console.error(`[ERROR] ${error}`) - res.send(500) - } -}); +}) app.listen(port, () => { - console.log(`⚡️[server]: Server is running at http://localhost:${port}`); -}); + console.log(`⚡️[server]: Server is running at http://localhost:${port}`) +}) diff --git a/yarn.lock b/yarn.lock index 523835b..1471f7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6116,6 +6116,7 @@ __metadata: version: 0.0.0-use.local resolution: "server@workspace:apps/server" dependencies: + "@pcd/eddsa-pcd": ^0.1.1 "@types/cors": ^2.8.13 "@types/dotenv": ^8.2.0 "@types/express": ^4.17.17 From e56c43f86b32aac77b9a9afac1856be8bf52568b Mon Sep 17 00:00:00 2001 From: cedoor Date: Mon, 4 Sep 2023 15:13:46 +0200 Subject: [PATCH 2/2] refactor: check when color is the same --- apps/client/src/App.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/apps/client/src/App.tsx b/apps/client/src/App.tsx index 37b70a3..e110e40 100644 --- a/apps/client/src/App.tsx +++ b/apps/client/src/App.tsx @@ -1,6 +1,6 @@ import { EdDSAPCDPackage } from "@pcd/eddsa-pcd" import { getWithoutProvingUrl, openPassportPopup, usePassportPopupMessages } from "@pcd/passport-interface" -import { useCallback, useEffect } from "react" +import { useCallback, useEffect, useState } from "react" /** * This page allows users to get an EdDSA PCD containing a color as a message signed by @@ -9,6 +9,7 @@ import { useCallback, useEffect } from "react" */ export default function App() { const [passportPCDString] = usePassportPopupMessages() + const [bgColor, setBgColor] = useState() // Get the latest color stored in the server. useEffect(() => { @@ -56,21 +57,24 @@ export default function App() { return } - alert(`The signature is valid, the background color will be changed`) - const { color } = await response.json() + if (bgColor === color) { + alert("The color is the same as the current one") + return + } + setBgColor(color) } })() }, [passportPCDString]) // Update the background color. - const setBgColor = useCallback((color: string) => { + useEffect(() => { const appElement = document.getElementById("app")! - appElement.style.backgroundColor = color - }, []) + appElement.style.backgroundColor = bgColor + }, [bgColor]) // Get the EdDSA PCD with the color signed by the issuer. const getEdDSAPCD = useCallback(() => {