-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e40ce2c
commit 6a1008a
Showing
2 changed files
with
102 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,83 +1,89 @@ | ||
import { Request, Response, Router } from "express"; | ||
import { strongJwtVerification } from "../../middleware/verify-jwt"; | ||
import { JwtPayload } from "../auth/auth-schemas"; | ||
import { Router } from "express"; | ||
import { Role } from "../auth/auth-schemas"; | ||
import { StatusCode } from "status-code-enum"; | ||
import { hasElevatedPerms } from "../../common/auth"; | ||
import { getAuthenticatedUser } from "../../common/auth"; | ||
import { createSignedPostUrl, getSignedDownloadUrl } from "./s3-service"; | ||
import specification, { Tag } from "../../middleware/specification"; | ||
import Config from "../../common/config"; | ||
import { S3DownloadURLSchema, S3UploadURLSchema } from "./s3-schemas"; | ||
import { UserIdSchema } from "../user/user-schemas"; | ||
import { z } from "zod"; | ||
|
||
const s3Router = Router(); | ||
|
||
/** | ||
* @api {get} /s3/upload GET /s3/upload | ||
* @apiGroup s3 | ||
* @apiDescription Gets a presigned upload url to the resume s3 bucket for the currently authenticated user, valid for 60s. | ||
* | ||
* @apiSuccess (200: Success) {String} url presigned URL | ||
* | ||
* @apiSuccessExample Example Success Response: | ||
* HTTP/1.1 200 OK | ||
* { | ||
"url": "https://resume-bucket-dev.s3.us-east-2.amazonaws.com/randomuser?randomstuffs", | ||
} | ||
*/ | ||
s3Router.get("/upload/", strongJwtVerification, async (_req: Request, res: Response) => { | ||
const payload = res.locals.payload as JwtPayload; | ||
const userId = payload.id; | ||
s3Router.get( | ||
"/upload/", | ||
specification({ | ||
method: "get", | ||
path: "/s3/upload/", | ||
tag: Tag.S3, | ||
role: Role.USER, | ||
summary: "Gets a upload url for the resume of the currently authenticated user", | ||
description: `This is a presigned url from s3 that is valid for ${Config.RESUME_URL_EXPIRY_SECONDS} seconds`, | ||
responses: { | ||
[StatusCode.SuccessOK]: { | ||
description: "The upload url", | ||
schema: S3UploadURLSchema, | ||
}, | ||
}, | ||
}), | ||
async (req, res) => { | ||
const { id: userId } = getAuthenticatedUser(req); | ||
const { url, fields } = await createSignedPostUrl(userId); | ||
return res.status(StatusCode.SuccessOK).send({ url, fields }); | ||
}, | ||
); | ||
|
||
const { url, fields } = await createSignedPostUrl(userId); | ||
s3Router.get( | ||
"/download/", | ||
specification({ | ||
method: "get", | ||
path: "/s3/download/", | ||
tag: Tag.S3, | ||
role: Role.USER, | ||
summary: "Gets a download url for the resume of the currently authenticated user", | ||
description: `This is a presigned url from s3 that is valid for ${Config.RESUME_URL_EXPIRY_SECONDS} seconds`, | ||
responses: { | ||
[StatusCode.SuccessOK]: { | ||
description: "The download url", | ||
schema: S3DownloadURLSchema, | ||
}, | ||
}, | ||
}), | ||
async (req, res) => { | ||
const { id: userId } = getAuthenticatedUser(req); | ||
const downloadUrl = await getSignedDownloadUrl(userId); | ||
return res.status(StatusCode.SuccessOK).send({ url: downloadUrl }); | ||
}, | ||
); | ||
|
||
return res.status(StatusCode.SuccessOK).send({ url: url, fields: fields }); | ||
}); | ||
s3Router.get( | ||
"/download/:id", | ||
specification({ | ||
method: "get", | ||
path: "/s3/download/{id}", | ||
tag: Tag.S3, | ||
role: Role.ADMIN, | ||
summary: "Gets a download url for the resume of the specified user", | ||
description: | ||
"Admin-only because this is for a specific user, use `GET /s3/download/` for the currently authenticated user", | ||
parameters: z.object({ | ||
id: UserIdSchema, | ||
}), | ||
responses: { | ||
[StatusCode.SuccessOK]: { | ||
description: "The download url", | ||
schema: S3DownloadURLSchema, | ||
}, | ||
}, | ||
}), | ||
async (req, res) => { | ||
const userId = req.params.id; | ||
|
||
/** | ||
* @api {get} /s3/download GET /s3/download | ||
* @apiGroup s3 | ||
* @apiDescription Gets a presigned download url for the resume of the currently authenticated user, valid for 60s. | ||
* | ||
* @apiSuccess (200: Success) {String} url presigned URL | ||
* | ||
* @apiSuccessExample Example Success Response: | ||
* HTTP/1.1 200 OK | ||
* { | ||
"url": "https://resume-bucket-dev.s3.us-east-2.amazonaws.com/randomuser?randomstuffs", | ||
} | ||
*/ | ||
s3Router.get("/download/", strongJwtVerification, async (_req: Request, res: Response) => { | ||
const payload = res.locals.payload as JwtPayload; | ||
const userId = payload.id; | ||
const downloadUrl = getSignedDownloadUrl(userId); | ||
const downloadUrl = await getSignedDownloadUrl(userId); | ||
|
||
return res.status(StatusCode.SuccessOK).send({ url: downloadUrl }); | ||
}); | ||
|
||
/** | ||
* @api {get} /s3/download/:USERID GET /s3/download/:USERID | ||
* @apiGroup s3 | ||
* @apiDescription Gets a presigned download url for the resume of the specified user, given that the caller has elevated perms | ||
* | ||
* @apiSuccess (200: Success) {String} url presigned URL | ||
* | ||
* @apiSuccessExample Example Success Response: | ||
* HTTP/1.1 200 OK | ||
* { | ||
"url": "https://resume-bucket-dev.s3.us-east-2.amazonaws.com/randomuser?randomstuffs", | ||
} | ||
* @apiError (403: Forbidden) {String} Forbidden | ||
* @apiErrorExample Example Error Response: | ||
* HTTP/1.1 403 Forbidden | ||
* {"error": "Forbidden"} | ||
*/ | ||
s3Router.get("/download/:USERID", strongJwtVerification, async (req: Request, res: Response) => { | ||
const userId = req.params.USERID as string; | ||
const payload = res.locals.payload as JwtPayload; | ||
|
||
if (!hasElevatedPerms(payload)) { | ||
return res.status(StatusCode.ClientErrorForbidden).send({ error: "Forbidden" }); | ||
} | ||
|
||
const downloadUrl = await getSignedDownloadUrl(userId); | ||
|
||
return res.status(StatusCode.SuccessOK).send({ url: downloadUrl }); | ||
}); | ||
return res.status(StatusCode.SuccessOK).send({ url: downloadUrl }); | ||
}, | ||
); | ||
|
||
export default s3Router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { z } from "zod"; | ||
|
||
export const S3DownloadURLSchema = z.object({ | ||
url: z.string().openapi({ example: "https://resume-bucket-dev.s3.us-east-2.amazonaws.com/abcd" }), | ||
}); | ||
|
||
export const S3UploadURLSchema = S3DownloadURLSchema.extend({ | ||
fields: z.any(), | ||
}).openapi("S3UploadURL", { | ||
example: { | ||
url: "https://resume-bucket-dev.s3.us-east-2.amazonaws.com/", | ||
fields: { | ||
success_action_status: "201", | ||
"Content-Type": "application/pdf", | ||
bucket: "resume-bucket-dev", | ||
"X-Amz-Algorithm": "AWS4-HMAC-SHA256", | ||
"X-Amz-Credential": "ABCD/20241013/us-east-2/s3/aws4_request", | ||
"X-Amz-Date": "20241013T081251Z", | ||
key: "github1234.pdf", | ||
Policy: "eyJ==", | ||
"X-Amz-Signature": "bfe6f0c382", | ||
}, | ||
}, | ||
}); |