Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ kits.json kit refs for `npm run kits`
2. **Layers.** Schema → controller → service → kit. Controllers check permissions and map; services hold
the logic and never import GraphQL; storage changes go to db-kit, finance rules to finance-kit,
posting to tally-kit, permissions to auth-kit, catalog data to catalog-kit, layouts to report-kit /
invoice-kit.
invoice-kit, mail designs to mail-kit (written in its mail design language, never HTML).
3. **Tenant isolation.** No argument carries an `orgId` or `actorId`. They come from the session
(`actorOf`). Every id is checked against the organisation (`mustOwn`, `owned`, `scopedFind`).
4. **Permissions per branch.** Load a record, then `authorize(ctx, perm, record.branchId)`; lists use
Expand Down
2 changes: 1 addition & 1 deletion docs/02-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ src/
processors.ts password-reset, document rendering (call services)
bullmq.ts createBullJobQueue (producer), startWorkers (consumer)
inline.ts createInlineJobQueue (no Redis: runs at once in this process)
mailer.ts webhookMailer (MAIL_WEBHOOK_URL)
mailer.ts mailerFromConfig: mail-kit over SMTP_URL (mailKitMailer), else webhookMailer (MAIL_WEBHOOK_URL)
schema/<module>.graphql.ts SDL
controllers/<module>.controller.ts
services/<area>.service.ts, index.ts (createServices), util.ts
Expand Down
4 changes: 2 additions & 2 deletions docs/06-auth-and-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ end all of them.

`requestPasswordReset(email, orgCode?)` always returns `true`, whether or not the account exists, and
only queues a job. The worker ([16-worker.md](16-worker.md)), or the API itself without Redis, creates
the token and sends `PASSWORD_RESET_URL?token=…` through the `Mailer` (`MAIL_WEBHOOK_URL`, or
`createContainer({ config, mailer })`). The token is never returned by the API and never stored raw.
the token and sends `PASSWORD_RESET_URL?token=…` through the `Mailer` (mail-kit over `SMTP_URL`,
`MAIL_WEBHOOK_URL`, or `createContainer({ config, mailer })`). The token is never returned by the API and never stored raw.
`resetPassword(token, newPassword)` consumes it once.

## Permissions
Expand Down
4 changes: 3 additions & 1 deletion docs/09-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Copy `.env.example` to `.env` for local work; never commit it.
| `QUEUE_PREFIX` | `mgpl` | BullMQ key prefix ([16-worker.md](16-worker.md)) |
| `WORKER_CONCURRENCY` | `4` | Jobs per worker at once, per queue |
| `JOB_RESULT_TTL_MS` | `3600000` (1 h) | How long finished jobs and their documents stay readable |
| `MAIL_WEBHOOK_URL` | empty | Email relay for reset links: POST `{ to, subject, text }` |
| `SMTP_URL` | empty | Mail through mail-kit: `smtps://user:pass@host:465` (TLS) or `smtp://user:pass@host:587` (STARTTLS). Preferred over `MAIL_WEBHOOK_URL` |
| `MAIL_FROM` | — (required with `SMTP_URL` in production) | Sender, e.g. `Maa Gayatri Path Lab <no-reply@your-domain>` |
| `MAIL_WEBHOOK_URL` | empty | Email relay for reset links when `SMTP_URL` is not set: POST `{ to, subject, text }` |
| `MGPL_SIGNATURE_HEADER_FILE` | — | PNG for the header variant (outside the repo) |
| `MGPL_SIGNATURE_NONHEADER_FILE` | — | PNG for the non-header variant |
| `MGPL_SIGNATURE_FILE` | — | One file for both |
Expand Down
16 changes: 11 additions & 5 deletions docs/12-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ flowchart LR
R --> W1["worker #1"]
R --> W2["worker #2"]
W1 & W2 --> DB
W1 & W2 -- "MAIL_WEBHOOK_URL" --> MAIL["email relay"]
W1 & W2 -- "mail-kit (SMTP_URL)" --> MAIL["mail server"]
A1 & A2 -. reads .-> SIG["signature files<br/>(secret volume)"]
W1 & W2 -. reads .-> SIG
```
Expand Down Expand Up @@ -42,7 +42,7 @@ report-kit's resvg is a native module: build the image on the same platform it r
|---|---|
| ☐ | `NODE_ENV=production`, `DATABASE_URL`, `REDIS_URL` set from secrets |
| ☐ | `JWT_SECRET` set from secrets (≥ 32 random characters, e.g. `openssl rand -base64 48`), the same on every API server |
| ☐ | At least one worker running (`node dist/worker.js`) with `MAIL_WEBHOOK_URL` set |
| ☐ | At least one worker running (`node dist/worker.js`) with `SMTP_URL` and `MAIL_FROM` set |
| ☐ | `CORS_ORIGINS` lists exactly the app origins (no `*`) |
| ☐ | `GRAPHIQL` and `INTROSPECTION` off (the defaults in production) |
| ☐ | `TRUST_PROXY=true` behind the load balancer (login limits use the client IP) |
Expand All @@ -57,9 +57,15 @@ report-kit's resvg is a native module: build the image on the same platform it r

## Sending password-reset emails

Set `MAIL_WEBHOOK_URL`: the worker (or the API, without Redis) POSTs `{ to, subject, text }` JSON to it.
Point it at your email provider's HTTP API or a small function in front of it. Without it, reset links
are not sent (the API still answers `true`). To send another way, embed with your own `Mailer`, in the
Set `SMTP_URL` and `MAIL_FROM`. The worker (or the API, without Redis) sends the mail with
[mail-kit](https://github.com/mgpl-diagnostics/mail-kit): the "password-reset" mail is written in its mail
design language and goes out as HTML plus plain text, in the lab's brand (name, address, phone from
report-kit's lab profile). Any SMTP provider works (Gmail or Zoho app passwords, Amazon SES, Brevo);
mail-kit refuses to send without TLS to a remote server. The provider's SPF and DKIM records must cover
the domain in `MAIL_FROM`.

Without `SMTP_URL`, `MAIL_WEBHOOK_URL` still works: the worker POSTs `{ to, subject, text }` JSON to it.
With neither, reset links are not sent (the API still answers `true`). To send another way, embed with your own `Mailer`, in the
worker's process when you run workers:

```ts
Expand Down
5 changes: 3 additions & 2 deletions docs/16-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ flowchart LR
R --> W2["worker #2"]
W1 & W2 --> SVC["the same services<br/>(auth-kit · db-kit · report-kit · invoice-kit)"]
SVC --> DB[("PostgreSQL / MongoDB")]
W1 -- "Mailer (MAIL_WEBHOOK_URL)" --> M["email relay"]
W1 -- "mail-kit (SMTP_URL)" --> M["mail server"]
C -- "job(id) (poll)" --> API
API -- "Job.fromId" --> R
```
Expand Down Expand Up @@ -69,7 +69,8 @@ what development and one-server installs get.
| `QUEUE_PREFIX` | `mgpl` | Redis key prefix (separate apps or environments) |
| `WORKER_CONCURRENCY` | `4` | Jobs per worker at once, per queue |
| `JOB_RESULT_TTL_MS` | `3600000` | How long finished jobs (and their documents) stay readable |
| `MAIL_WEBHOOK_URL` | — | Email relay: POST `{ to, subject, text }` JSON (your provider's API or a small function) |
| `SMTP_URL`, `MAIL_FROM` | — | Mail through mail-kit (our mail design language → HTML + text, sent over SMTP) |
| `MAIL_WEBHOOK_URL` | — | Fallback when `SMTP_URL` is not set: POST `{ to, subject, text }` JSON to a relay |

Shutdown (`SIGTERM`) lets the jobs in hand finish, then closes the database.

Expand Down
7 changes: 4 additions & 3 deletions docs/LOCAL-AGENT-BRIEF.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Follow [12-deployment.md](12-deployment.md). In particular:
Never commit them.
- `JWT_SECRET` from your secret store (≥ 32 random characters: `openssl rand -base64 48`), the same on every API server and worker.
- Run at least one worker: `node dist/worker.js` with `REDIS_URL` and the API's environment.
- `MAIL_WEBHOOK_URL` (an HTTP relay to the email provider) for password-reset links, and `PASSWORD_RESET_URL`.
- `SMTP_URL` + `MAIL_FROM` (mail-kit sends over SMTP) for password-reset links, and `PASSWORD_RESET_URL`.
- `npm run seed` once, with the branch's GST state code (`SEED_BRANCH_STATE`, 22 for Chhattisgarh) and
the organisation's GSTIN (`SEED_ORG_GSTIN`); billing needs the first, GST returns the second.
- Add every other branch with `createBranch` **including its `stateCode`**.
Expand All @@ -169,8 +169,9 @@ Added after the first release (auth-kit 1.2.0, client 1.2.0, api with JWT and th
- **Run the worker** next to the API (same image, command `node dist/worker.js`), at least one, with
`WORKER_CONCURRENCY` sized to the CPU (rendering is CPU-bound). Check its log line
`[worker] queues mail, documents on mgpl:`.
- **Email relay.** Set `MAIL_WEBHOOK_URL` to a small function that forwards `{ to, subject, text }` to
the lab's email provider. Done when "Forgot password" delivers a link.
- **Email.** Set `SMTP_URL` (e.g. `smtps://user:app-password@smtp.example.com:465`) and `MAIL_FROM`;
mail-kit sends the mails. The provider's SPF/DKIM must cover the sending domain. Done when "Forgot
password" delivers a link.
- **Secret rotation drill.** Set `JWT_SECRET=new,old`, deploy, wait 15 minutes, set `JWT_SECRET=new`.
- **Device binding** is `enforce` by default. If staff share browsers or wipe storage often and get
signed out, switch to `DEVICE_BINDING=flag` and read the `auth.device_mismatch` audit entries first.
Expand Down
4 changes: 4 additions & 0 deletions kits.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"$comment": "Kit sources for `npm run kits` (CI and local builds until every kit is on GitHub Packages with Actions access for this repo). Order = build order. `ref` is a tag or commit on github.com/mgpl-diagnostics/<name>.",
"kits": [
{
"name": "mail-kit",
"ref": "3e217f56ae36fab59412dcb005862f41ad7120d4"
},
{
"name": "catalog-kit",
"ref": "v1.0.0"
Expand Down
Loading
Loading