Skip to content

Commit

Permalink
add access token to qr generator
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Turner authored and Ben Turner committed Feb 14, 2024
1 parent aadd2cb commit 03ca2a4
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 57 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
# VS-attendance-api
# VS-attendance-api
2 changes: 2 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const config: Config = {
testEnvironment: "node",
preset: "ts-jest",
testMatch: ["**/*.test.ts"],
clearMocks: true,
setupFiles: ['<rootDir>/src/__tests__/setup.ts']
};

export default config;
128 changes: 119 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"dependencies": {
"dotenv": "^16.4.2",
"express": "^4.18.2",
"express-jwt": "^8.4.1",
"jimp": "^0.22.10",
"jsonwebtoken": "^9.0.2",
"jsqr": "^1.4.0",
"qrcode": "^1.5.3"
},
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { config } from "dotenv";

config();
File renamed without changes.
18 changes: 18 additions & 0 deletions src/routers/qr-code.router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { mockServer } from "../__tests__/utils";
import { decodeQRCodeDataUrl } from "../services/qr-code.service";

jest.mock("jsonwebtoken", () => ({ sign: () => "ACCESS_TOKEN" }));

describe("qr-code-router.ts", () => {
it("Should generate a QR code as a base64 encoded png", async () => {
const response = await mockServer().get("/api/qr-code/generate");
expect(response.headers["content-type"]).toContain("application/json");
expect(response.body.dataUrl).toContain(
"data:image/png;base64,iVBORw0KGgo",
);
const decoded = await decodeQRCodeDataUrl(response.body.dataUrl);
expect(decoded).toBe(
`${process.env.CLIENT_BASE_URL}?access_token=ACCESS_TOKEN`,
);
});
});
16 changes: 14 additions & 2 deletions src/routers/qr-code.router.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import ex from "express";
import { generateQRCode } from "../services/qr-code.service";
import { sign } from "jsonwebtoken";

const qrCodeRouter = ex.Router();

qrCodeRouter.post("/generate", async (req, res) => {
const dataUrl = await generateQRCode(req.body.userData);
qrCodeRouter.get("/generate", async (req, res) => {
const accessToken = sign(
"" + new Date().getTime(),
process.env.SECRET as string,
{ algorithm: "HS256", expiresIn: 1000 * 60 * 5 },
);
const payload = encodeURI(
`${process.env.CLIENT_BASE_URL}?access_token=${accessToken}`,
);
const dataUrl = await generateQRCode(payload);
res.status(200).send({ dataUrl });
});

// generate qr code with access token (5 min expiry time) encoded in url parameters
// landing page on website where key is decoded, validates against api, then adds entry to spreadsheet

export { qrCodeRouter };
37 changes: 3 additions & 34 deletions src/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { mockServer } from "./__tests__/test-utils";
import Jimp from "jimp";
import jsqr, { QRCode } from "jsqr";
import { config } from "dotenv";
config();
import { mockServer } from "./__tests__/utils";


describe("server.ts", () => {
it("should run", async () => {
Expand All @@ -11,38 +8,10 @@ describe("server.ts", () => {
});

it("should serve documentation site", async () => {
const response = await mockServer().get("/docs");
const response = await mockServer().get("/");
expect(response.headers["content-type"]).toContain("text/html");
expect(response.text).toContain(
"<title>V School Attendance API Documentation</title>",
);
});

it("Should generate a QR code as a base64 encoded png", async () => {
const response = await mockServer()
.post("/api/qr-code/generate")
.send({
userData: { email: "test@test.com", name: "test user", id: "123" },
});
expect(response.headers["content-type"]).toContain("application/json");
expect(response.body.dataUrl).toContain(
"data:image/png;base64,iVBORw0KGgo",
);
const buffer = Buffer.from(
response.body.dataUrl.replace(/^data:image\/[a-z]+;base64,/, ""),
"base64",
);
const image = await Jimp.read(buffer);
const decoded = jsqr(
new Uint8ClampedArray(image.bitmap.data),
image.bitmap.width,
image.bitmap.height,
) as QRCode;
expect(decoded.data).toBe(
`${process.env.ADMIN_CLIENT_BASE_URL}?email=test@test.com&name=test%20user&id=123`,
);
});

// add student to google sheets list
// update sign out time
});
2 changes: 1 addition & 1 deletion src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ server.use(ex.json());
server.use(ex.static(path.resolve(__dirname, "..", "public")));
server.get("/ping", (req, res) => res.status(200).send({ message: "pong" }));

server.get("/docs", (req, res) => {
server.get(["/", "/docs"], (req, res) => {
res
.status(200)
.sendFile(path.resolve(__dirname, "..", "public", "documentation.html"));
Expand Down
30 changes: 25 additions & 5 deletions src/services/qr-code.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { QRCodeData } from "../types";
import QRCode from "qrcode";
import Jimp from "jimp";
import jsqr, { QRCode as QRC } from "jsqr";

export const generateQRCode = async (userData: QRCodeData) => {
const payload = encodeURI(
`${process.env.ADMIN_CLIENT_BASE_URL}?email=${userData.email}&name=${userData.name}&id=${userData.id}`,
);
export const generateQRCode = async <P extends string>(payload: P) => {
return await QRCode.toDataURL(payload, { width: 250 });
};

export const decodeQRCodeDataUrl = async (
dataUrl: string,
): Promise<string | void> => {
try {
const buffer = Buffer.from(
dataUrl.replace(/^data:image\/[a-z]+;base64,/, ""),
"base64",
);
const image = await Jimp.read(buffer);
const decoded = jsqr(
new Uint8ClampedArray(image.bitmap.data),
image.bitmap.width,
image.bitmap.height,
) as QRC;

return decoded.data;
} catch (err) {
console.error(Error("Error: QR Decoding failed"));
console.error(err);
}
};
Loading

0 comments on commit 03ca2a4

Please sign in to comment.