Skip to content

Commit

Permalink
feat(backend): add presigned url route draft
Browse files Browse the repository at this point in the history
  • Loading branch information
elbotho committed Oct 14, 2024
1 parent c5acbf6 commit b78e682
Show file tree
Hide file tree
Showing 4 changed files with 1,424 additions and 2 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "npm-run-all --parallel \"build:*\"",
"build:backend": "esbuild ./src/backend/index.ts --bundle --platform=node --outfile=dist/backend/index.cjs",
"build:frontend": "vite build",
"dev": "docker compose up --watch --build",
"dev": "docker compose up --build",
"format": "npm-run-all --sequential --continue-on-error \"format:*\"",
"format:eslint": "eslint --fix",
"format:prettier": "prettier . --write",
Expand All @@ -22,6 +22,8 @@
"dev:edusharing": "dotenvx run --env-file=.env -- tsx --watch src/edusharing-server/start-server.ts"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.670.0",
"@aws-sdk/s3-request-presigner": "^3.670.0",
"@serlo/editor": "0.16.0",
"express": "^4.19.2",
"fp-ts": "2.16.9",
Expand Down
54 changes: 54 additions & 0 deletions src/backend/get-presigned-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
PutObjectCommand,
PutObjectCommandInput,
S3Client,
} from '@aws-sdk/client-s3'
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import { v1 as uuidv1 } from 'uuid'

const supportedMimeTypes = ['gif', 'jpeg', 'png', 'svg+xml', 'webp'] as const
type SupportedMimeType = (typeof supportedMimeTypes)[number]

// minIO test credentials as fallback
const bucket = process.env.BUCKET_NAME ?? 'serlo-test-bucket'

const s3Client = new S3Client({
region: process.env.BUCKET_REGION ?? 'us-east-1',
credentials: {
accessKeyId: process.env.BUCKET_ACCESS_KEY_ID ?? 'Q3AM3UQ867SPQQA43P2F',
secretAccessKey:
process.env.BUCKET_SECRET_ACCESS_KEY ??
'zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG',
},
endpoint: process.env.BUCKET_ENDPOINT ?? 'https://play.min.io:9000',
forcePathStyle: true, // test, maybe only set on dev
})

const srcPrefix =
process.env.BUCKET_PUBLIC_SRC_PREFIX ?? `https://play.min.io:9000/${bucket}/`

export const getPresignedUrl = async (mimeTypePart: SupportedMimeType) => {
const fullMimeType = `image/${mimeTypePart}`
const fileHash = uuidv1()
const fileExtension = mimeTypePart === 'svg+xml' ? 'svg' : mimeTypePart
const fileName = `${fileHash}.${fileExtension}`

const params: PutObjectCommandInput = {
Key: `${fileHash}.${fileExtension}`,
Bucket: bucket,
ContentType: fullMimeType,
Metadata: { 'Content-Type': fullMimeType },
}

const command = new PutObjectCommand(params)
const signedUrl = await getSignedUrl(s3Client, command, { expiresIn: 3600 })
const imgSrc = `${srcPrefix}${fileName}`
return { signedUrl, imgSrc }
}

export function isValidMimeType(
mimeType: string
): mimeType is SupportedMimeType {
if (typeof mimeType !== 'string') return false
return supportedMimeTypes.includes(mimeType as SupportedMimeType)
}
17 changes: 16 additions & 1 deletion src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { verifyJwt } from './verify-jwt'
import { createJWKSResponse } from './create-jwks-response'
import { signJwtWithBase64Key } from './sign-jwt'
import { edusharingEmbedKeys } from './edusharing-embed-keys'
import { getPresignedUrl, isValidMimeType } from './get-presigned-url'

const ltijsKey = readEnvVariable('LTIJS_KEY')
const mongodbConnectionUri = readEnvVariable('MONGODB_URI')
Expand Down Expand Up @@ -73,7 +74,8 @@ ltijs.setup(
ltijs.whitelist(
'/edusharing-embed/login',
'/edusharing-embed/done',
'/edusharing-embed/keys'
'/edusharing-embed/keys',
'/presigned-url'
)

// Disable COEP
Expand Down Expand Up @@ -148,6 +150,19 @@ ltijs.app.put('/entity', async (req, res) => {
return res.send('Success')
})

ltijs.app.get('/presigned-url', async (req, res) => {
const mimeType = String(req.query.mimeType)

if (!isValidMimeType(mimeType)) {
return res.send(
'Missing or invalid query parameter mimeType. Please provide one of: gif, jpeg, png, svg+xml, webp'
)
}

const response = await getPresignedUrl(mimeType)
res.send(response)
})

// Provide endpoint to start embed flow on edu-sharing
// Called when user clicks on "embed content from edusharing"
ltijs.app.get('/edusharing-embed/start', async (_, res) => {
Expand Down
Loading

0 comments on commit b78e682

Please sign in to comment.