Telegram OAuth (OpenID Connect) - #1251
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7274488688
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
7274488 to
c8ff7d8
Compare
The `telegramOAuth` flag was derived with `some()` over every enabled telegram strategy, so a config running two of them - one legacy widget, one OAuth - reported OAuth for both. The login page renders a single control pointed at `map.customRoutes.telegramAuthUrl`, so the legacy route got a redirect link instead of the widget script and login failed. Which flow a control needs is a property of the one strategy behind its route, so resolve it from the auth URL instead. That also makes it correct for multiDomain, where customRoutes is per domain and each domain can target a different telegram strategy - hence the move out of the global config mutations and into getServerSettings, which has the per-request map config. An auth URL that does not resolve by name (a custom or proxied path) falls back to the only enabled telegram strategy when there is exactly one, and to the legacy widget when it is ambiguous. Reported by chatgpt-codex-connector on WatWowMap#1251. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
There was a problem hiding this comment.
Pull request overview
Adds Telegram OIDC with PKCE while preserving the legacy Login Widget and existing account identity mapping.
Changes:
- Adds conditional OAuth2 strategy registration and ID-token verification.
- Exposes the resolved flow to login, account-linking, and custom-page components.
- Adds dependencies, configuration, types, localization, and resolution tests.
Reviewed changes
Copilot reviewed 18 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
yarn.lock |
Locks new authentication dependencies. |
package.json |
Adds JOSE and OAuth2 packages. |
config/default.json |
Adds Telegram OAuth defaults. |
config/local.example.json |
Documents a Telegram strategy example. |
packages/config/.configref |
Updates generated config reference. |
packages/locales/lib/human/en.json |
Adds account-linking text. |
packages/types/lib/augmentations.d.ts |
Types the Telegram theme palette. |
packages/types/lib/blocks.d.ts |
Types the resolved flow flag. |
server/src/graphql/resolvers.js |
Annotates custom Telegram blocks. |
server/src/routes/authRouter.js |
Handles cancelled Telegram consent. |
server/src/services/TelegramClient.js |
Implements OAuth2 and token validation. |
server/src/utils/getServerSettings.js |
Exposes the domain flow flag. |
server/src/utils/getTelegramStrategy.js |
Resolves strategy flow from auth URLs. |
server/test/telegramStrategyResolution.test.js |
Tests strategy and block resolution. |
src/assets/theme.js |
Adds Telegram branding colors. |
src/components/auth/Telegram.jsx |
Selects OAuth button or legacy widget. |
src/components/Config.jsx |
Loads the flow flag into state. |
src/features/builder/components/Generator.jsx |
Supports custom-block flow selection. |
src/features/profile/LinkAccounts.jsx |
Updates Telegram account linking. |
src/pages/login/Methods.jsx |
Uses the unified Telegram control. |
src/store/useMemory.js |
Initializes the flow flag. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
The primary OAuth flow is wired coherently, but proxied homogeneous configurations can select the wrong frontend flow, non-denial provider failures are misclassified, and multiple configured OAuth strategies share PKCE state. These cases can prevent login or obscure actionable failures. Full review comments:
|
Two review findings on WatWowMap#1251: `profile` is documented as returning `name`; the `given_name`/`family_name` pair only shows up in the example payload. Mapping the pair alone meant a user with no @username was displayed as their numeric id even though the token carried their name, so fall back to splitting `name`. `redirectUri` is not inherited from default.json when a config declares its own `strategies` array, since node-config replaces arrays rather than merging them. Adding just the two advertised credentials therefore left the flow without a `redirect_uri`. Fold it into a single `isOAuthStrategy` predicate shared by the server and the client so a half configured strategy stays on the widget instead of rendering a link for a flow the server cannot start, and log what to add. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
|
Two reproducible edge cases break OAuth state isolation and login-control selection. The 13 focused tests and formatting checks passed, but lint failed because jose is missing locally; a full build and live provider login were not run. Full review comments:
|
Addresses the remaining review findings on WatWowMap#1251: passport-oauth2 derives its session key from the authorization URL host, so every Telegram strategy shared `oauth2:oauth.telegram.org`. Two OAuth strategies started at once overwrote each other's state and PKCE verifier, and the first callback deleted the second flow's state on its way to failing, breaking both logins. Give each strategy its own `sessionKey`. The auth url was matched as a raw string, so `/auth/tg?source=login` resolved to the name `tg?source=login` and fell back to the widget for a route express routes to `tg`. Parse it through `URL` and match on the pathname. An unresolved auth url fell back to the widget whenever more than one Telegram strategy was enabled, even when all of them ran OAuth - an OAuth only config has no reason to set `telegramBotName`, so that left it with no usable login control. Only fall back when the candidates actually disagree. Drop the callback error guard: passport-oauth2 already fails rather than errors on `access_denied`, so the denial reached /blocked without it, while the guard rewrote every other error - `server_error`, `invalid_scope` - into a permission denial and hid the real failure in a debug log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
7aebc51 to
ab892c6
Compare
|
All review findings are addressed. The branch is now a single squashed commit ( Fixed as suggested
Two places I went a different way
Two things worth knowing, both outside this PR's scope
Still untested against Telegram's live servers; that needs a real bot with Allowed URLs registered in BotFather. |
Telegram now runs an OpenID Connect provider at https://oauth.telegram.org, replacing the hash signed Login Widget with an authorization code + PKCE flow. The widget still works and remains the default, so nothing changes for existing installs - a telegram strategy switches to OAuth only once it has a `clientId`, `clientSecret` and `redirectUri`, all from @Botfather. The `profile` scope is required: `sub` is an opaque per client identifier, and the real Telegram user id only arrives as the `id` claim. That is what `users.telegramId`, `strategy.groups` and `strategy.allowedUsers` key off, so existing accounts keep working with no migration and no re-linking. Which flow a login control renders is resolved from the strategy behind its own route, since a config can enable several telegram strategies and each domain, or each custom login page block, can point at a different one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
ab892c6 to
b7df998
Compare
|
A valid mixed-strategy configuration can render an unusable login control. All 14 helper tests and focused formatting checks passed; missing dependencies blocked complete lint/build verification, and network restrictions prevented live Telegram verification. Review comment:
|
Telegram now runs an OIDC provider at
oauth.telegram.org, replacing the hash-signed Login Widget with an authorization-code + PKCE flow.This upgrades the existing
telegramstrategy in place rather than adding a new type. If a strategy has bothclientIdandclientSecret,TelegramClientregisterspassport-oauth2against Telegram's endpoints; without them it keeps the legacy widget. No config rename, no DB migration, no re-linking — admins opt in by adding two keys from @Botfather → your bot → Login Widget.Notes
id_tokenis verified against Telegram's JWKS withjose, covering all four algorithms BotFather offers (RS256, ES256, EdDSA, ES256K) and enforcing signature, issuer and audience in one call.idclaim, notsub.subis opaque per-client;id(profile scope) is the real Telegram user id thatusers.telegramId,strategy.groups,strategy.allowedUsersand thegetChatMemberlookup all key off — so existing accounts carry over.authHandler. Groups, perms, trials and account linking are untouched.authentication.telegramOAuthflag tells the client which flow to render, sinceauthentication.methodsonly carries strategy types./blockedinstead of surfacing as a 500.Config
{ "name": "telegram", "type": "telegram", "enabled": true, "botToken": "123:ABC", "clientId": "123456789", "clientSecret": "...", "redirectUri": "https://your.map/auth/telegram/callback", "groups": [] }The redirect URI must also be registered under Allowed URLs in BotFather.
Testing
Tests, build, lint and prettier all pass; tsc gains no new errors. Verified the OAuth2 wiring (endpoints, S256, session-backed state, the verify arity that delivers id_token), token validation against a locally-signed JWT (id vs sub; wrong audience, wrong issuer and forged signature all rejected), and the flag derivation across config permutations.
Not yet tested against Telegram's live servers — that needs a real bot with Allowed URLs registered.