From 07ccc5663666abf046d381b5e022f6a597d2d8d3 Mon Sep 17 00:00:00 2001 From: Stepan Usatiuk Date: Wed, 14 Oct 2020 20:33:06 +0300 Subject: [PATCH] access photos via jwt --- src/routes/photos.ts | 66 ++++++++++++++++++++++++++++ src/tests/integration/photos.test.ts | 46 +++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/src/routes/photos.ts b/src/routes/photos.ts index 805a149..b01d7b4 100644 --- a/src/routes/photos.ts +++ b/src/routes/photos.ts @@ -5,6 +5,8 @@ import { IAPIResponse } from "~types"; import * as fs from "fs/promises"; import send = require("koa-send"); import { getHash, getSize } from "~util"; +import * as jwt from "jsonwebtoken"; +import { config } from "~config"; export const photosRouter = new Router(); @@ -191,6 +193,39 @@ photosRouter.get("/photos/byID/:id", async (ctx) => { } as IPhotosByIDGetRespBody; }); +photosRouter.get("/photos/showByID/:id/:token", async (ctx) => { + const { id, token } = ctx.params as { + id: number | undefined; + token: string | undefined; + }; + + if (!(id && token)) { + ctx.throw(400); + return; + } + + try { + jwt.verify(token, config.jwtSecret) as IPhotoJSON; + } catch (e) { + ctx.throw(401); + } + + const photoJson = jwt.decode(token) as IPhotoJSON; + const { user } = photoJson; + + const photo = await Photo.findOne({ + id, + user: { id: user }, + }); + + if (!photo || !(await photo.isUploaded())) { + ctx.throw(404); + return; + } + + await send(ctx, photo.getPath()); +}); + photosRouter.get("/photos/showByID/:id", async (ctx) => { if (!ctx.state.user) { ctx.throw(401); @@ -216,6 +251,37 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => { await send(ctx, photo.getPath()); }); +export type IPhotoShowToken = string; +export type IPhotosGetShowTokenByID = IAPIResponse; +photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => { + if (!ctx.state.user) { + ctx.throw(401); + } + + const { id } = ctx.params as { + id: number | undefined; + }; + + if (!id) { + ctx.throw(400); + } + + const { user } = ctx.state; + + const photo = await Photo.findOne({ id, user }); + if (!photo || !(await photo.isUploaded())) { + ctx.throw(404); + return; + } + + const token = jwt.sign(photo.toJSON(), config.jwtSecret, { + expiresIn: "1h", + algorithm: "HS256", + }); + + ctx.body = { error: false, data: token } as IPhotosGetShowTokenByID; +}); + export type IPhotosByIDDeleteRespBody = IAPIResponse; photosRouter.delete("/photos/byID/:id", async (ctx) => { if (!ctx.state.user) { diff --git a/src/tests/integration/photos.test.ts b/src/tests/integration/photos.test.ts index 99f7081..f047a1c 100644 --- a/src/tests/integration/photos.test.ts +++ b/src/tests/integration/photos.test.ts @@ -7,6 +7,7 @@ import { Photo, IPhotoJSON } from "~entity/Photo"; import { IPhotosNewPostBody } from "~routes/photos"; import * as fs from "fs/promises"; import { constants as fsConstants } from "fs"; +import * as jwt from "jsonwebtoken"; import { catPath, @@ -20,6 +21,7 @@ import { seedDB, } from "./util"; import { sleep } from "deasync"; +import { config } from "~config"; const callback = app.callback(); @@ -79,6 +81,50 @@ describe("photos", function () { ); }); + it("should show a photo using access token", async function () { + const getTokenResp = await request(callback) + .get(`/photos/getShowByIDToken/${seed.dogPhoto.id}`) + .set({ + Authorization: `Bearer ${seed.user2.toJWT()}`, + }) + .expect(200); + + expect(getTokenResp.body.error).to.be.false; + const token = getTokenResp.body.data as string; + + const response = await request(callback) + .get(`/photos/showByID/${seed.dogPhoto.id}/${token}`) + .expect(200); + expect(parseInt(response.header["content-length"])).to.equal( + dogFileSize, + ); + + const tokenSelfSigned = jwt.sign( + seed.dogPhoto.toJSON(), + config.jwtSecret, + { + expiresIn: "1m", + }, + ); + + const responseSS = await request(callback) + .get(`/photos/showByID/${seed.dogPhoto.id}/${tokenSelfSigned}`) + .expect(200); + expect(parseInt(responseSS.header["content-length"])).to.equal( + dogFileSize, + ); + }); + + it("should not show a photo using expired access token", async function () { + const token = jwt.sign(seed.dogPhoto.toJSON(), config.jwtSecret, { + expiresIn: "0s", + }); + + const response = await request(callback) + .get(`/photos/showByID/${seed.dogPhoto.id}/${token}`) + .expect(401); + }); + it("should not show a photo without jwt", async function () { const response = await request(callback) .get(`/photos/byID/${seed.dogPhoto.id}`)