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
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ AZURE_EMAIL_SENDER="noreply@polinetwork.org"
# OAUTH_BACKEND_CLIENT_ID=""
# OAUTH_BACKEND_PRIVATE_JWK="" # the backend client's private JWK as JSON, with kid and alg
# OAUTH_BOT_CLIENT_ID="" # same registered client ID as on the Telegram bot; required for its token socket
# LEGACY_ANONYMOUS="deny" # default; "allow" is the RFC rollback switch: anonymous requests may run procedures that record no actor
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ Requirements:
Every caller authenticates with a PoliNetwork IdP token ([RFC v3](https://github.com/PoliNetworkOrg/auth/blob/main/docs/idp-telegram-integration-rfc-v3.md), Phase 5):

- **With a token** (`Authorization: Bearer <IdP access token>`): the token is verified with [`@polinetwork/auth-kit`](https://github.com/PoliNetworkOrg/auth-kit), and the call is fully enforced. Every procedure declares, per actor kind, the scope it needs and, for people, the permission they must hold in the IdP's access snapshot. A token that fails verification is rejected. The bot acts for a Telegram user with `X-PN-Actor: telegram:<id>`, which only a service token with `backend:tg:act-as` may send.
- **Without a token**: refused (`UNAUTHORIZED`). `LEGACY_ANONYMOUS=allow` is only a rollback switch: it lets anonymous requests run procedures that do not record who acted, with a null actor and no permissions, and logs each call as `[AUTH] legacy anonymous call`. Writes that record their actor (grants, `tg.groups.leaveChat`, label and web content writes) and the IdP procedures are token-only and refuse anonymous calls either way.
- **Without a token**: refused (`UNAUTHORIZED`). There is no anonymous access, and handlers always receive the token's actor in `ctx.actor`.

Procedures are built with `policy(...)` from `src/trpc.ts`; the bare tRPC procedure is not exported, and `tests/idp-router.test.ts` fails if any procedure lacks a policy. `policy(rules, { tokenOnly: true })` refuses anonymous calls and gives the handler a non-null `ctx.actor`.
Procedures are built with `policy(...)` from `src/trpc.ts`; the bare tRPC procedure is not exported, and `tests/idp-router.test.ts` fails if any procedure lacks a policy.

Who acted always comes from the token, never from the input: procedures take no actor fields (`createdBy`, `modifiedBy`, `updatedBy`, `adderId`, `interruptedById`, `performerId`, `adminId`). Inputs are plain zod objects, so a key an older client still sends is stripped, not rejected. Content tables record the author in `*_by_sub` columns; rows written before the IdP migration keep their author's Telegram ID in `legacy_*_by_tg_id`. Grants are in `tg_grants` and record the grantor's IdP subject. People sign in only through the IdP: the backend has no login of its own. The bot records moderation actions with `tg.auditLog.record`, which takes an idempotency key and flags `idp_permission` actions the snapshot does not back.

Expand Down
4 changes: 4 additions & 0 deletions drizzle/0029_remove_audit_admin_id.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- admin_id is superseded by actor_tg_id. Copy any value an older release wrote only to admin_id, so no author is lost.
UPDATE "tg_audit_log" SET "actor_tg_id" = "admin_id" WHERE "actor_tg_id" IS NULL AND "admin_id" IS NOT NULL;--> statement-breakpoint
DROP INDEX "auditlog_adminid_idx";--> statement-breakpoint
ALTER TABLE "tg_audit_log" DROP COLUMN "admin_id";
Loading
Loading