Skip to content

Commit 876c277

Browse files
committed
docs(apps): document the two credential layers and the lifecycle channel
What each event carries, how a delivery is verified, why the webhook secret is what proves a delivery is really Tolgee, and why the first one is the awkward case.
1 parent df0d6d7 commit 876c277

6 files changed

Lines changed: 266 additions & 22 deletions

File tree

apps/create-tolgee-app/README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ available.
7878
Either way, the last step is the same and is done per project:
7979
**Project → Settings → Apps**, enable the app.
8080

81+
Either way, too, the generated server already receives Tolgee's **signed
82+
lifecycle deliveries** — the app-level credentials at registration, the
83+
per-install credentials when an organization installs it, and every later
84+
rotation. That is `server/routes/lifecycle.ts`, one `mountTolgeeLifecycle()`
85+
call; the generated README explains the two credential layers and how a delivery
86+
is proved to be Tolgee.
87+
8188
## Which SDK the generated app gets
8289

8390
The generated app depends on `@tolgee/apps-sdk`, and by default the generator
@@ -95,7 +102,8 @@ summary and again in the next steps.
95102
| `published` | an exact published version, never a range |
96103

97104
**`published` currently refuses to run.** Every `@tolgee/apps-sdk` release on npm
98-
predates `selfRegisterApp` and `applyTolgeeTheme`, which the template imports, so
105+
predates `selfRegisterApp`, `applyTolgeeTheme` and `mountTolgeeLifecycle`, which
106+
the template imports, so
99107
the generator exits with that sentence rather than handing you a project full of
100108
missing-export errors. `PUBLISHED_SDK_RELEASED` in `src/registry.ts` is the flag
101109
to flip — together with `PUBLISHED_SDK_VERSION` — once the SDK is on npm. `auto`

apps/create-tolgee-app/template/README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,44 @@ Registration makes the app known to the organization, not visible in projects.
108108
For each project that should use it: **Project → Settings → Apps**, then enable
109109
**{{name}}**. The dashboard page shows up in that project's menu.
110110

111+
## Credentials Tolgee pushes at you
112+
113+
You never copy a secret into this app. Tolgee POSTs **signed lifecycle
114+
deliveries** to the `baseUrl` in the manifest, and `server/routes/lifecycle.ts`
115+
receives them in a single `mountTolgeeLifecycle(app, …)` call:
116+
117+
| Event | What arrives |
118+
| --- | --- |
119+
| registered | The **app-level** credentials (`tgpub_` / `tgpubs_`) and the webhook signing secret. They identify the app; they reach no data. |
120+
| installed | The **per-install** credentials (`tgapp_` / `tgapps_`), the install id and the organization. These are the ones that act on projects. |
121+
| uninstalled | Nothing to store — the credentials of that install are dropped. |
122+
| secret rotated | The replacement secret, at whichever layer it belongs to. |
123+
124+
Each delivery is signed `HMAC-SHA256(webhookSecret, "<timestamp>.<body>")` and
125+
sent in a `Tolgee-Signature` header. **Holding the webhook secret is what proves
126+
a delivery is really Tolgee** — the SDK verifies every one, refuses a stale or
127+
replayed timestamp, and refuses a first delivery outright once this app already
128+
holds credentials for that instance, so nobody can push their own credentials
129+
over yours. Everything it accepts lands in `.tolgee-dev/install.json`.
130+
131+
The route is mounted **before `express.json()`** on purpose: the signature covers
132+
the exact bytes Tolgee sent, and a body parser would consume them.
133+
134+
Self-registration still works on its own — an app that never receives a delivery
135+
keeps running on the credentials it registered with.
136+
111137
## Layout
112138

113139
```
114140
server/manifest.template.json what the app contributes; __BASE_URL__ is
115141
substituted per request
116142
server/index.ts Express: /manifest.json + self-registration
143+
server/routes/lifecycle.ts receives Tolgee's signed lifecycle deliveries
117144
server/devTunnel.ts the URLs Tolgee reaches this app at
118145
scripts/dev-tunnel.ts opens the tunnel and publishes those URLs
119146
src/App.tsx the dashboard page
120147
.tolgee-dev/tunnel.json the URLs currently in play (gitignored)
121-
.tolgee-dev/install.json install id + app credentials (gitignored)
148+
.tolgee-dev/install.json app-level + per-install credentials (gitignored)
122149
```
123150

124151
## What the SDK gives you
@@ -143,6 +170,8 @@ From `@tolgee/apps-sdk/server`:
143170
- **`fetchAppAccessToken()`** — exchanges the client id/secret for an access
144171
token, for work the app does on its own behalf rather than a user's. Called
145172
with no arguments it uses the stored credentials.
173+
- **`mountTolgeeLifecycle(app, …)`** — receives, verifies and stores everything
174+
Tolgee pushes about this app. One call; see above.
146175

147176
## Changing what the app contributes
148177

apps/create-tolgee-app/template/_env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,14 @@ SERVER_PORT={{serverPort}}
4545
# so set both or neither.
4646
# TOLGEE_APP_CLIENT_ID=
4747
# TOLGEE_APP_CLIENT_SECRET=
48+
49+
# --- Lifecycle deliveries ----------------------------------------------------
50+
# The secret Tolgee signs its lifecycle deliveries (credentials, installs,
51+
# rotations) with. It authenticates nothing towards Tolgee and is never sent
52+
# anywhere — holding it is what proves an incoming delivery really is Tolgee.
53+
#
54+
# Leave unset in development: it arrives with the registration delivery and is
55+
# stored in .tolgee-dev/install.json. Set it in a deployment, to the value
56+
# Tolgee showed when the app was registered, so no first delivery has to be
57+
# taken on trust.
58+
# TOLGEE_APP_WEBHOOK_SECRET=

apps/example-apps/activity-worker/README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,30 @@ Set `TOLGEE_APP_CLIENT_ID` and `TOLGEE_APP_CLIENT_SECRET` only when you deploy
165165
the app somewhere that injects secrets properly — the environment wins over the
166166
local file, and setting either one makes the SDK ignore the file entirely.
167167

168+
## Credentials Tolgee pushes at you
169+
170+
Registration is not the only way this app gets credentials. Tolgee POSTs **signed
171+
lifecycle deliveries** to the `baseUrl` in the manifest, and `server/index.ts`
172+
receives them in a single `mountTolgeeLifecycle(app, …)` call.
173+
174+
There are two credential layers, and the deliveries carry both: **app-level**
175+
(`tgpub_` / `tgpubs_`, at registration) identifies and administers the app
176+
everywhere it is installed and reaches no data; **per-install** (`tgapp_` /
177+
`tgapps_`, at install) is what the worker actually polls Tolgee with. A rotation
178+
of either arrives the same way — and this app drops its cached access token when
179+
one does, so the next poll authenticates with the new secret.
180+
181+
A third secret, the **webhook secret**, arrives with the registration and is
182+
never sent anywhere: Tolgee signs each delivery
183+
`HMAC-SHA256(webhookSecret, "<timestamp>.<body>")` in a `Tolgee-Signature`
184+
header, so holding it is what proves a delivery is really Tolgee. The SDK
185+
verifies every one, refuses a stale or replayed timestamp (5-minute window), and
186+
**refuses a first delivery once this app already holds credentials for that
187+
instance**, so nobody can push their own credentials over yours.
188+
189+
Note what this channel does *not* replace: per-project enablement is still
190+
polled, which is what `fetchAppInstallations()` is for.
191+
168192
## Enabling the app for a project
169193

170194
**Project → Settings → Apps → Activity Worker → enable**
@@ -196,7 +220,7 @@ src/ iframe page (Vite + React)
196220
useActivityFeed.ts polls the app's own /api/feed
197221
feedTypes.ts the feed contract, shared with the server
198222
server/
199-
index.ts manifest + /api/feed + self-registration + worker start
223+
index.ts manifest + /api/feed + self-registration + lifecycle + worker start
200224
activityWorker.ts the two polling loops (installations, activity)
201225
translationChanges.ts pulls translation edits out of an activity revision
202226
tolgeeAccess.ts install-context token, cached until it nears expiry
@@ -208,7 +232,7 @@ scripts/
208232
dev-tunnel.ts opens the tunnel and publishes its URL
209233
.tolgee-dev/ local state, gitignored
210234
tunnel.json the URLs Tolgee currently reaches this app at
211-
install.json install id + app credentials, written at registration
235+
install.json app-level + per-install credentials, written as they arrive
212236
```
213237
214238
## Limits worth knowing

apps/example-apps/keys-showcase/README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,32 @@ makes the SDK ignore the file entirely.
126126
If self-registration fails, the server logs why and keeps serving the manifest, so you can
127127
always fall back to the manual flow.
128128

129+
## Credentials Tolgee pushes at you
130+
131+
Registration is not the only way this app gets credentials. Tolgee POSTs **signed
132+
lifecycle deliveries** to the `baseUrl` in the manifest, and `server/index.ts`
133+
receives them in a single `mountTolgeeLifecycle(app, …)` call — watch the server
134+
log when you register or install the app.
135+
136+
There are two credential layers, and the deliveries carry both:
137+
138+
| Layer | Prefixes | Arrives with | What it does |
139+
| --- | --- | --- | --- |
140+
| App | `tgpub_` / `tgpubs_` | *registered* | Identifies and administers the app everywhere it is installed. Reaches no data. |
141+
| Install | `tgapp_` / `tgapps_` | *installed* | Acts on one organization's projects — what `npm run token` uses. |
142+
143+
A third secret, the **webhook secret**, arrives with the registration and is
144+
never sent anywhere: Tolgee signs each delivery
145+
`HMAC-SHA256(webhookSecret, "<timestamp>.<body>")` in a `Tolgee-Signature`
146+
header, so holding it is what proves a delivery is really Tolgee. The SDK
147+
verifies every one, refuses a stale or replayed timestamp (5-minute window), and
148+
**refuses a first delivery once this app already holds credentials for that
149+
instance** — otherwise anyone could post their own credentials over yours. A
150+
rotation, signed with the secret only Tolgee knows, is accepted and replaces what
151+
is held.
152+
153+
Everything accepted lands in `.tolgee-dev/install.json`, and nothing is printed.
154+
129155
## Enabling the app for a project
130156

131157
Registering installs the app into the organization; it still has to be turned on per project:
@@ -172,7 +198,7 @@ src/ iframe page (Vite + React)
172198
KeysShowcase.tsx the dashboard page: context, theme, resize
173199
useProjectKeys.ts the REST call, via the SDK's typed client
174200
server/
175-
index.ts manifest endpoint + optional self-registration
201+
index.ts manifest endpoint + self-registration + lifecycle deliveries
176202
config.ts env config and the URLs Tolgee should use
177203
devTunnel.ts reads/writes the dev-tunnel state
178204
manifest.template.json __BASE_URL__ is substituted at request time
@@ -181,5 +207,5 @@ scripts/
181207
token.ts machine-to-machine demo
182208
.tolgee-dev/ local state, gitignored
183209
tunnel.json the URLs Tolgee currently reaches this app at
184-
install.json install id + app credentials, written at registration
210+
install.json app-level + per-install credentials, written as they arrive
185211
```

0 commit comments

Comments
 (0)