-
Notifications
You must be signed in to change notification settings - Fork 0
feat(pg-node): add Node.js example using @e4a/pg-js #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| # PostGuard for Business API key (PG-…). Get one from | ||
| # business.staging.postguard.eu (staging) or business.postguard.eu (production). | ||
| PG_API_KEY= | ||
|
|
||
| # Endpoints. Defaults are staging. | ||
| PG_PKG_URL=https://pkg.staging.postguard.eu | ||
| PG_CRYPTIFY_URL=https://storage.staging.postguard.eu | ||
|
|
||
| # Optional: PostGuard website used in the printed download link. | ||
| # Defaults to https://staging.postguard.eu on staging Cryptify, | ||
| # else https://postguard.eu. | ||
| # PG_DOWNLOAD_URL=https://staging.postguard.eu | ||
|
|
||
| # Recipients. Citizen is an exact email match; Organisation matches by email domain. | ||
| PG_CITIZEN_EMAIL=citizen@example.com | ||
| PG_ORGANISATION_EMAIL=noreply@example.org | ||
|
|
||
| # Optional unencrypted message body included in Cryptify's notification mail. | ||
| PG_MESSAGE= | ||
|
|
||
| # Optional comma-separated list of file paths to encrypt. If empty the | ||
| # example uses two in-memory demo files (report.txt + notes.txt). | ||
| # PG_INPUT_FILES=./files/report.pdf,./files/notes.txt |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| node_modules/ | ||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # pg-node: PostGuard Node.js example | ||
|
|
||
| Node.js example demonstrating how to use the [@e4a/pg-js](https://www.npmjs.com/package/@e4a/pg-js) SDK from a server runtime. Mirrors the [pg-sveltekit](../pg-sveltekit) example's "Informatierijk notificeren" flow (citizen + organisation recipients) but as a CLI script — drop-in starting point for backend integrations. | ||
|
|
||
| ## What it does | ||
|
|
||
| Two modes, selected by the script flag: | ||
|
|
||
| 1. **Send** (`npm run send`) — encrypts the input files for a citizen (exact email) and an organisation (email domain), uploads to Cryptify, and asks Cryptify to email each recipient a download link. | ||
| 2. **Upload-only** (`npm run upload`) — same encryption + upload, but silent. Cryptify returns a UUID you can distribute through some other channel. | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Rule: writing-rules] Minor: the |
||
| Files come from `PG_INPUT_FILES` (comma-separated paths) or two in-memory demo files if that is unset. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - **Node.js 22+** — matches `@e4a/pg-js`'s `engines.node`. The SDK also supports Bun and Deno; the same encryption code in `src/encryption.mjs` works there too. | ||
| - A PostGuard for Business API key. | ||
|
|
||
| ## Setup | ||
|
|
||
| ```bash | ||
| cd pg-node | ||
| npm install | ||
| cp .env.example .env | ||
| # edit .env: set at minimum PG_API_KEY | ||
| ``` | ||
|
|
||
| The `package.json` depends on the published [`@e4a/pg-js`](https://www.npmjs.com/package/@e4a/pg-js) (currently `^1.9.0`). | ||
|
|
||
| ## Run | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Rule: writing-rules] Em-dash count is on the high side for a short README (~10 across 75 lines). A few replace cleanly with a period or parentheses (e.g. |
||
| ```bash | ||
| npm run send # encrypt + upload + ask Cryptify to send mails | ||
| npm run upload # encrypt + upload silently, no mails | ||
| ``` | ||
|
|
||
| The script prints the resulting `uuid` and the corresponding `…/download?uuid=…` URL. | ||
|
|
||
| ## Staging Cryptify does not send email | ||
|
|
||
| The default `PG_CRYPTIFY_URL` is `storage.staging.postguard.eu` — the staging deployment. It **does not actually deliver notification emails**, so you can exercise the full upload + notify flow without spamming real inboxes while you integrate. | ||
|
|
||
| - The upload itself works. You get back a real UUID and the download URL is usable. | ||
| - `npm run send` succeeds, but no recipient mail is sent. Open the printed URL yourself to verify the decrypt flow end-to-end. | ||
| - Point `PG_CRYPTIFY_URL` at the production Cryptify host to exercise real email delivery. | ||
|
|
||
| ## Configuration | ||
|
|
||
| | Variable | Description | Default | | ||
| | ----------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | | ||
| | `PG_API_KEY` | PostGuard for Business API key (`PG-…`) | *(required)* | | ||
| | `PG_PKG_URL` | PostGuard PKG server URL | `https://pkg.staging.postguard.eu` | | ||
| | `PG_CRYPTIFY_URL` | Cryptify file-sharing URL | `https://storage.staging.postguard.eu` | | ||
| | `PG_DOWNLOAD_URL` | PostGuard website used in `/download` URLs | `https://staging.postguard.eu` on staging Cryptify, else `https://postguard.eu` | | ||
| | `PG_CITIZEN_EMAIL` | Citizen recipient (exact email match) | `citizen@example.com` | | ||
| | `PG_ORGANISATION_EMAIL` | Organisation recipient (matches by domain) | `noreply@example.org` | | ||
| | `PG_MESSAGE` | Optional unencrypted body for Cryptify's notify mail | *(empty)* | | ||
| | `PG_INPUT_FILES` | Comma-separated file paths to encrypt | two in-memory demo files | | ||
|
|
||
| ## How it maps to the SDK | ||
|
|
||
| The work happens in [`src/encryption.mjs`](./src/encryption.mjs): | ||
|
|
||
| ```js | ||
| const sealed = pg.encrypt({ | ||
| files, | ||
| recipients: [pg.recipient.email(citizen.email), pg.recipient.emailDomain(organisation.email)], | ||
| sign: pg.sign.apiKey(apiKey), | ||
| onProgress, | ||
| signal, | ||
| }); | ||
| const { uuid } = await sealed.upload({ notify: { recipients: true, message, language: 'EN' } }); | ||
| ``` | ||
|
|
||
| `notify` must be nested under an object — the SDK validates the shape and throws a clear `TypeError` if you pass `{ notify: true }` or forget to nest. See the [SDK README](https://github.com/encryption4all/postguard-js#server-side-usage-node-bun-deno) for the full server-side surface. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // CLI entry point. Run with one of: | ||
| // npm run send # encrypts and asks Cryptify to mail recipients | ||
| // npm run upload # encrypts and uploads silently | ||
| // npm start # same as `npm run send` | ||
| // | ||
| // Configuration via .env or environment variables — see .env.example. | ||
|
|
||
| import { readFile } from 'node:fs/promises'; | ||
| import { basename } from 'node:path'; | ||
| import { | ||
| API_KEY, | ||
| CITIZEN_EMAIL, | ||
| ORGANISATION_EMAIL, | ||
| MESSAGE, | ||
| INPUT_FILES, | ||
| DOWNLOAD_URL, | ||
| IS_CRYPTIFY_STAGING, | ||
| CRYPTIFY_URL, | ||
| PKG_URL, | ||
| } from './src/config.mjs'; | ||
| import { encryptAndSend, encryptAndUpload } from './src/encryption.mjs'; | ||
|
|
||
| if (!API_KEY) { | ||
| console.error('Missing PG_API_KEY. Copy .env.example to .env and set it.'); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| const mode = process.argv.includes('--upload-only') ? 'upload-only' : 'send-email'; | ||
|
|
||
| console.log(`pg-node example — mode: ${mode}`); | ||
| console.log(` PKG: ${PKG_URL}`); | ||
| console.log(` Cryptify: ${CRYPTIFY_URL}${IS_CRYPTIFY_STAGING ? ' (staging — no mails actually sent)' : ''}`); | ||
| console.log(` Citizen: ${CITIZEN_EMAIL}`); | ||
| console.log(` Organisation: ${ORGANISATION_EMAIL}`); | ||
| console.log(''); | ||
|
|
||
| const files = await loadFiles(); | ||
| console.log(`Encrypting ${files.length} file(s):`); | ||
| for (const f of files) console.log(` ${f.name} (${f.size} bytes)`); | ||
| console.log(''); | ||
|
|
||
| const abortController = new AbortController(); | ||
| process.on('SIGINT', () => { | ||
| console.log('\nCancelling…'); | ||
| abortController.abort(); | ||
| }); | ||
|
|
||
| const onProgress = (pct) => { | ||
| process.stdout.write(`\r upload progress: ${pct}% `); | ||
| }; | ||
|
|
||
| const t0 = performance.now(); | ||
| const uuid = | ||
| mode === 'send-email' | ||
| ? await encryptAndSend({ | ||
| files, | ||
| citizen: { email: CITIZEN_EMAIL }, | ||
| organisation: { email: ORGANISATION_EMAIL }, | ||
| apiKey: API_KEY, | ||
| message: MESSAGE, | ||
| onProgress, | ||
| signal: abortController.signal, | ||
| }) | ||
| : await encryptAndUpload({ | ||
| files, | ||
| citizen: { email: CITIZEN_EMAIL }, | ||
| organisation: { email: ORGANISATION_EMAIL }, | ||
| apiKey: API_KEY, | ||
| onProgress, | ||
| signal: abortController.signal, | ||
| }); | ||
| const t1 = performance.now(); | ||
|
|
||
| process.stdout.write('\n'); | ||
| console.log(''); | ||
| console.log(`Done in ${(t1 - t0).toFixed(0)}ms`); | ||
| console.log(`UUID: ${uuid}`); | ||
| console.log(`Download: ${DOWNLOAD_URL}/download?uuid=${uuid}`); | ||
|
|
||
| if (mode === 'send-email' && IS_CRYPTIFY_STAGING) { | ||
| console.log(''); | ||
| console.log('Note: staging Cryptify does not actually deliver mails. Open the URL above to test decrypt.'); | ||
| } | ||
|
|
||
| /** Read each path in PG_INPUT_FILES as a File. If empty, return two demo files. */ | ||
| async function loadFiles() { | ||
| if (INPUT_FILES.length === 0) { | ||
| return [ | ||
| new File( | ||
| [new TextEncoder().encode('This is a sample report for PostGuard encryption testing.\n')], | ||
| 'report.txt', | ||
| { type: 'text/plain' } | ||
| ), | ||
| new File( | ||
| [new TextEncoder().encode('Confidential notes — only the intended recipient can read this.\n')], | ||
| 'notes.txt', | ||
| { type: 'text/plain' } | ||
| ), | ||
| ]; | ||
| } | ||
| return Promise.all( | ||
| INPUT_FILES.map(async (path) => { | ||
| const bytes = await readFile(path); | ||
| return new File([bytes], basename(path)); | ||
| }) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stale after the engines bump.
pg-node/package.jsonnow requiresnode >= 22, but the root README still advertises 20.6+. Two consistent options: bump this toNode.js 22+to match the sub-project'sengines, or leave it at 20.6 with a sentence clarifying that pg-node specifically needs 22+. Recommend the former — simpler and matchespg-node/README.md:16.