Skip to content

Telegram OAuth (OpenID Connect) - #1251

Open
Fabio1988 wants to merge 45 commits into
WatWowMap:mainfrom
Fabio1988:feat/telegram-oauth
Open

Telegram OAuth (OpenID Connect)#1251
Fabio1988 wants to merge 45 commits into
WatWowMap:mainfrom
Fabio1988:feat/telegram-oauth

Conversation

@Fabio1988

@Fabio1988 Fabio1988 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

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 telegram strategy in place rather than adding a new type. If a strategy has both clientId and clientSecret, TelegramClient registers passport-oauth2 against 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

  • The id_token is verified against Telegram's JWKS with jose, covering all four algorithms BotFather offers (RS256, ES256, EdDSA, ES256K) and enforcing signature, issuer and audience in one call.
  • Identity comes from the id claim, not sub. sub is opaque per-client; id (profile scope) is the real Telegram user id that users.telegramId, strategy.groups, strategy.allowedUsers and the getChatMember lookup all key off — so existing accounts carry over.
  • Telegram has no UserInfo endpoint, so the profile is read from the token claims and handed to the existing authHandler. Groups, perms, trials and account linking are untouched.
  • A derived authentication.telegramOAuth flag tells the client which flow to render, since authentication.methods only carries strategy types.
  • A cancelled consent screen now lands on /blocked instead 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.

turtlesocks-bot and others added 30 commits June 5, 2026 13:43
…wMap#1225)

* fix(scanArea): prevent crash when area feature has no name/key

Guard the scan area search filter against features missing a properties.key (which happens when a scan area polygon has no name set), instead of throwing TypeError: Cannot read properties of undefined (reading 'toLowerCase').

Also fixes a longstanding typo (geoJsonFilName / geoJsonFilname -> geoJsonFileName) in the multi-domain example config and docs.

* fix: copilot comments

---------

Co-authored-by: Mygod <contact-git@mygod.be>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/config/lib/mutations.js Outdated
Telegram now runs an OIDC provider at oauth.telegram.org, replacing the
hash-signed Login Widget with an authorization-code + PKCE flow.

The existing `telegram` strategy is upgraded in place rather than adding a
new type: when a strategy has both `clientId` and `clientSecret`,
TelegramClient registers a passport-oauth2 strategy against Telegram's
endpoints; without them it keeps registering the legacy widget strategy.
No config rename, no DB migration, no re-linking.

The `id_token` is verified against Telegram's JWKS with `jose`, which
covers all four signing algorithms BotFather offers (RS256, ES256, EdDSA,
ES256K) and enforces signature, issuer and audience in one call. Telegram
has no UserInfo endpoint, so the profile is read from the token claims and
handed to the existing authHandler, leaving groups, perms, trials and
account linking untouched.

Identity comes from the `id` claim, not `sub`. `sub` is an opaque
per-client identifier; `id` (profile scope) is the real Telegram user id
that users.telegramId, strategy.groups, strategy.allowedUsers and the
getChatMember lookup all key off, so existing accounts carry over.

Client-side, a derived `authentication.telegramOAuth` flag tells the app
which flow to render, since `authentication.methods` only carries strategy
types. TelegramLogin picks the button or the widget from it, and the three
call sites (login page, profile linking, login-page builder) share it.

Also sends a cancelled Telegram consent screen to /blocked instead of
letting passport's AuthorizationError surface as a 500.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
@Fabio1988
Fabio1988 force-pushed the feat/telegram-oauth branch from 7274488 to c8ff7d8 Compare August 30, 2026 12:37
Fabio1988 and others added 2 commits August 30, 2026 14:43
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
Custom login page blocks carry their own `telegramAuthUrl`, which can point
at a different strategy than the domain's `customRoutes` default, so they
cannot inherit the page level flag either.

The customComponent resolver now annotates each telegram block with the
flow resolved from that block's own route, recursing into parent blocks,
and Generator passes it down. TelegramLogin prefers an explicitly resolved
flow and falls back to the domain default when a caller does not supply
one, so the login page and profile linking are unchanged.

The block list is copied rather than mutated, since it comes straight off
the shared config object. customComponent returns a JSON scalar, so the
added field needs no schema change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QB1uHoTq84BLq1ikVBFheY
@Mygod
Mygod requested a balanced review from Copilot August 30, 2026 17:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 id_token has no `id` claim, the `profile` scope was not granted',
)
}
const firstName = claimToString(payload.given_name)
tokenURL: TG_TOKEN_URL,
clientID: clientId,
clientSecret,
callbackURL: this.strategy.redirectUri,
@Mygod

Mygod commented Aug 30, 2026

Copy link
Copy Markdown
Collaborator

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:

  • [P2] Preserve OAuth when unresolved strategies agree — server/src/utils/getTelegramStrategy.js:44-48
    When a custom or proxied telegramAuthUrl does not contain /auth/<name> and multiple enabled Telegram strategies are all OAuth, this count-based fallback returns null. isTelegramOAuth() then reports false and renders the legacy widget even though no legacy strategy exists, so login cannot start; infer the common flow when all candidates agree, reserving the legacy fallback for mixed modes.

  • [P2] Propagate non-denial OAuth errors — server/src/routes/authRouter.js:76-79
    When Telegram returns an OAuth error other than cancellation, such as server_error, temporarily_unavailable, or invalid_scope, this condition still rewrites it to access_denied before Passport or the error middleware sees it. Users receive a misleading permission denial and the actual provider/configuration failure is reduced to a debug log; normalize only genuine denial codes and propagate other errors.

  • [P2] Namespace PKCE state by strategy — server/src/services/TelegramClient.js:362-364
    When two Telegram OAuth strategies are started concurrently in the same map session, such as two custom login controls opened in separate tabs, passport-oauth2 gives both instances the default session key derived only from oauth.telegram.org. The second request overwrites the first strategy's state and verifier, so the first callback fails validation and consumes the second flow's state as well; provide a strategy-specific sessionKey.

@Mygod
Mygod changed the base branch from develop to main August 30, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants