access photos via jwt

This commit is contained in:
2020-10-14 20:33:06 +03:00
committed by Stepan Usatiuk
parent 661d2ef0c7
commit 07ccc56636
2 changed files with 112 additions and 0 deletions

View File

@@ -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<IPhotoShowToken>;
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<boolean>;
photosRouter.delete("/photos/byID/:id", async (ctx) => {
if (!ctx.state.user) {

View File

@@ -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}`)