Last active
January 5, 2023 00:05
-
-
Save AbeEstrada/96851bc70f8391fd920ca10d3ba2733c to your computer and use it in GitHub Desktop.
Privnote + AWS Lambda
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
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; | |
const s3Client = new S3Client({ region: "us-east-1" }); | |
const Bucket = "BUCKET_NAME_GOES_HERE"; | |
export const handler = async (event) => { | |
let statusCode = 200; | |
let body = ``; | |
if (event.requestContext.http.method === "POST") { | |
// POST / | |
const buff = Buffer.from(event.body ?? "", "base64"); | |
const eventBody = buff.toString("UTF-8"); | |
const params = new URLSearchParams(eventBody); | |
const uuid = params.get("uuid"); | |
const note = params.get("ciphered"); | |
const bytes = Buffer.byteLength(note); | |
// Only allow to save notes below 1MB | |
if (bytes < 1048577) { | |
const bucketParams = { | |
Bucket, | |
ACL: "private", | |
ServerSideEncryption: "AES256", | |
// Do not store the last part of the key | |
Key: uuid?.split("-")?.splice(0, 4)?.join("-"), | |
Body: `${note}`, | |
}; | |
try { | |
// Save note | |
await s3Client.send(new PutObjectCommand(bucketParams)); | |
body = `<div> | |
<p> | |
<label for="note" class="w-100 alert alert-warning fw-light fst-italic py-1">The note will self-destruct after reading it.</label> | |
<input type="text" class="form-control shadow-sm" id="link" value="https://biubkhqe6zz3hypoitljy2vqti0yyvbn.lambda-url.us-east-1.on.aws/${uuid}" onClick="this.select()" readonly /> | |
</p> | |
</div>`; | |
} catch (error) { | |
console.log(error); | |
statusCode = 500; | |
body = `<div class="alert alert-danger">Error: saving</div>`; | |
} | |
} else { | |
statusCode = 500; | |
body = `<div class="alert alert-danger">Error: too big</div>`; | |
} | |
} else { | |
// GET /:uuid | |
const uuid = `${event.requestContext.http.path}`.replaceAll("/", ""); | |
if (uuid !== "") { | |
const bucketParams = { Bucket, Key: uuid?.split("-")?.splice(0, 4)?.join("-") }; | |
try { | |
// Load note | |
const data = await s3Client.send(new GetObjectCommand(bucketParams)); | |
const streamToString = await data.Body?.transformToString(); | |
try { | |
// Delete note | |
await s3Client.send(new DeleteObjectCommand(bucketParams)); | |
// Show note to the user | |
body = `<form id="noteForm"> | |
<div class="col-12"> | |
<label for="note" class="w-100 alert alert-warning fw-light fst-italic py-1">This note was destroyed. If you need to keep it, copy it before closing this window.</label> | |
<textarea class="form-control shadow-sm" id="note" name="note" rows="10" required></textarea> | |
</div> | |
</form> | |
<script type="text/javascript" src="https://unpkg.com/crypto-js/crypto-js.js"></script> | |
<script type="text/javascript"> | |
const formEl = document.getElementById("noteForm"); | |
document.addEventListener("DOMContentLoaded", () => { | |
// Decrypt in the browser | |
const bytes = CryptoJS.AES.decrypt("${streamToString}", "${uuid}"); | |
const originalText = bytes.toString(CryptoJS.enc.Utf8); | |
formEl.elements.note.value = originalText; | |
}); | |
</script>`; | |
} catch (error) { | |
console.log(error); | |
statusCode = 500; | |
body = `<div class="alert alert-danger">Error: deleting</div>`; | |
} | |
} catch (error) { | |
console.log(error); | |
statusCode = 404; | |
body = `<div class="alert alert-danger">Error 404: not found</div>`; | |
} | |
} else { | |
// Default GET / | |
body = `<form id="noteForm" class="row g-3 mx-auto" method="POST"> | |
<div class="col-12"> | |
<label for="note" class="form-label">New note:</label> | |
<textarea class="form-control shadow-sm" id="note" name="note" rows="10" required></textarea> | |
</div> | |
<div class="col-12 mt-4"> | |
<button type="submit" name="submitButton" class="btn btn-primary shadow">Create note</button> | |
</div> | |
</form> | |
<script type="text/javascript" src="https://unpkg.com/crypto-js/crypto-js.js"></script> | |
<script type="text/javascript"> | |
const formEl = document.getElementById("noteForm"); | |
formEl.addEventListener("submit", (event) => { | |
formEl.elements.submitButton.disabled = true; | |
event.preventDefault(); | |
// Generate uuid in the browser | |
const uuid = crypto.randomUUID(); | |
// Cipher text | |
const ciphertext = CryptoJS.AES.encrypt(formEl.elements.note.value, uuid).toString(); | |
// Add a hidden input to the form with the ciphered text | |
const cipheredEl = document.createElement('input'); | |
cipheredEl.type = "hidden"; | |
cipheredEl.name = "ciphered"; | |
cipheredEl.value = ciphertext; | |
formEl.appendChild(cipheredEl); | |
// Add a hidden input with the uuid | |
const uuidEl = document.createElement('input'); | |
uuidEl.type = "hidden"; | |
uuidEl.name = "uuid"; | |
uuidEl.value = uuid; | |
formEl.appendChild(uuidEl); | |
// Submit form | |
formEl.submit(); | |
}); | |
</script>`; | |
} | |
} | |
const html = `<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<title>Private Note</title> | |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GLhlTQ8iRABdZLl6O3oVMWSktQOp6b7In1Zl3/Jr59b6EGGoI1aFkw7cmDA6j6gD" crossorigin="anonymous" /> | |
</head> | |
<body class="bg-body-tertiary"> | |
<nav class="navbar navbar-dark bg-dark"> | |
<div class="container-fluid" style="max-width:720px"> | |
<a class="navbar-brand" href="/">🔒 Private Note</a> | |
</div> | |
</nav> | |
<main class="container my-3" style="max-width:720px"> | |
${body} | |
</main> | |
</body> | |
</html>`; | |
const response = { | |
statusCode, | |
headers: { "Content-Type": "text/html" }, | |
body: html, | |
}; | |
return response; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is my Privnote clone in a single AWS Lambda.
Permissions required: