diff --git a/Notesnook.Inbox.API/Dockerfile b/Notesnook.Inbox.API/Dockerfile index 3291172..87a9ca6 100644 --- a/Notesnook.Inbox.API/Dockerfile +++ b/Notesnook.Inbox.API/Dockerfile @@ -1,4 +1,4 @@ -FROM oven/bun:1.2.21-slim +FROM oven/bun:1.3.5-slim RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app diff --git a/Notesnook.Inbox.API/README.md b/Notesnook.Inbox.API/README.md new file mode 100644 index 0000000..2500945 --- /dev/null +++ b/Notesnook.Inbox.API/README.md @@ -0,0 +1,68 @@ +# Notesnook Inbox API + +## Running locally + +### Requirements + +- Bun (v1.3.0 or higher) + +### Commands + +- `bun install` - Install dependencies +- `bun run dev` - Start the development server +- `bun run build` - Build the project for production +- `bun run start` - Start the production server + +## Self-hosting + +The easiest way to self-host is with Docker or Docker Compose. + +Prerequisites: + +- `docker` (Engine) installed +- `docker-compose` (optional, for multi-service setups) + +Build and run with Docker: + +```bash +# build the image from the current folder +docker build -t notesnook-inbox-api . + +# run the container (example) +docker run --rm -p 3000:3000 \ + -e PORT=3000 \ + -e NOTESNOOK_API_SERVER_URL="https://api.notesnook.com" \ + notesnook-inbox-api +``` + +Docker Compose (example): + +```yaml +services: + inbox-api: + image: notesnook-inbox-api + build: . + ports: + - "3000:3000" + environment: + PORT: 3000 + NOTESNOOK_API_SERVER_URL: "https://api.notesnook.com" + restart: unless-stopped +``` + +Environment variables: + +- `PORT` — port the service listens on (default: `5181`) +- `NOTESNOOK_API_SERVER_URL` — base URL of the Notesnook API used to fetch public inbox keys + +_If you prefer running without Docker, use `bun install` and `bun run start` with the environment variables set._ + +## Writing from scratch + +The inbox API server is pretty simple to write from scratch in any programming language and/or framework. There's only one endpoint that needs to be implemented, which does these three steps: + +1. Fetch the user's public inbox API key from the Notesnook API. +2. Encrypt the payload using `openpgp` or any other `openpgp` compatible library. +3. Post the encrypted payload to the Notesnook API. + +You can refer to the [source code](./src/index.ts) for implementation details. diff --git a/Notesnook.Inbox.API/src/index.ts b/Notesnook.Inbox.API/src/index.ts index c48affe..3ffb1c5 100644 --- a/Notesnook.Inbox.API/src/index.ts +++ b/Notesnook.Inbox.API/src/index.ts @@ -33,9 +33,15 @@ interface EncryptedInboxItem { alg: string; } +/** + * Encrypts raw data using OpenPGP with the recipient's public key + * + * @param {string} rawData - The plaintext data to encrypt + * @param {string} rawPublicKey - The recipient's OpenPGP public key + */ async function encrypt( rawData: string, - rawPublicKey: string + rawPublicKey: string, ): Promise { const publicKey = await openpgp.readKey({ armoredKey: rawPublicKey }); const message = await openpgp.createMessage({ text: rawData }); @@ -57,11 +63,11 @@ async function getInboxPublicEncryptionKey(apiKey: string) { headers: { Authorization: apiKey, }, - } + }, ); if (!response.ok) { throw new Error( - `failed to fetch inbox public encryption key: ${await response.text()}` + `failed to fetch inbox public encryption key: ${await response.text()}`, ); } @@ -71,7 +77,7 @@ async function getInboxPublicEncryptionKey(apiKey: string) { async function postEncryptedInboxItem( apiKey: string, - item: EncryptedInboxItem + item: EncryptedInboxItem, ) { const response = await fetch(`${NOTESNOOK_API_SERVER_URL}/inbox/items`, { method: "POST", @@ -92,9 +98,12 @@ app.use( rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute limit: 60, - }) + }), ); -app.post("/inbox", async (req, res) => { +app.get("/health", (_, res) => { + return res.status(200).json({ status: "ok" }); +}); +app.post("/", async (req, res) => { try { const apiKey = req.headers["authorization"]; if (!apiKey) { @@ -117,7 +126,7 @@ app.post("/inbox", async (req, res) => { const encryptedItem = await encrypt( JSON.stringify(validationResult.data), - inboxPublicKey + inboxPublicKey, ); console.log("[info] encrypted item");