From d30314188cb18e8613ae1b830996f57af3287b67 Mon Sep 17 00:00:00 2001 From: Afonso Jorge Ramos Date: Tue, 12 May 2026 02:22:58 +0100 Subject: [PATCH] refactor(forges): route device-flow revoke-access hint through the adapter --- src/renderer/routes/LoginWithDeviceFlow.tsx | 40 ++++++++++----------- src/renderer/utils/forges/github/adapter.ts | 2 ++ src/renderer/utils/forges/types.ts | 7 ++++ 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/renderer/routes/LoginWithDeviceFlow.tsx b/src/renderer/routes/LoginWithDeviceFlow.tsx index 790040016..cb5630aaf 100644 --- a/src/renderer/routes/LoginWithDeviceFlow.tsx +++ b/src/renderer/routes/LoginWithDeviceFlow.tsx @@ -18,8 +18,8 @@ import type { DeviceFlowSession } from '../utils/auth/types'; import { getAlternateScopeNames, getRecommendedScopeNames } from '../utils/auth/scopes'; import { rendererLogError, toError } from '../utils/core/logger'; +import { getAdapter } from '../utils/forges/registry'; import { copyToClipboard, openExternalLink } from '../utils/system/comms'; -import { openAccountSettings } from '../utils/system/links'; interface LocationState { account?: Account; @@ -37,6 +37,9 @@ export const LoginWithDeviceFlowRoute: FC = () => { // here through a registered adapter, so a missing forge would indicate a // mis-registered route — fall back to 'github' to preserve existing UX. const forge: Forge = routeForge ?? reAuthAccount?.forge ?? 'github'; + const adapter = getAdapter(forge); + const revokeHostname = reAuthAccount?.hostname ?? Constants.GITHUB_HOSTNAME; + const revokeAccessUrl = adapter.getDeviceFlowRevokeAccessUrl?.(revokeHostname); const { loginWithDeviceFlowStart, loginWithDeviceFlowPoll, loginWithDeviceFlowComplete } = useAppContext(); @@ -229,25 +232,22 @@ export const LoginWithDeviceFlowRoute: FC = () => { - - - Note: to change previously granted permissions, revoke Gitify's access at{' '} - - , then re-authorize above. - - + {revokeAccessUrl && ( + + + Note: to change previously granted permissions, revoke Gitify's access at{' '} + + , then re-authorize above. + + + )} ); diff --git a/src/renderer/utils/forges/github/adapter.ts b/src/renderer/utils/forges/github/adapter.ts index e89e8a627..41a58faef 100644 --- a/src/renderer/utils/forges/github/adapter.ts +++ b/src/renderer/utils/forges/github/adapter.ts @@ -131,6 +131,8 @@ export const githubAdapter: ForgeAdapter = { pollDeviceFlow: pollGitHubDeviceFlow, performWebOAuth: performGitHubWebOAuth, exchangeAuthCodeForToken: exchangeAuthCodeForAccessToken, + getDeviceFlowRevokeAccessUrl: (hostname) => + getDeveloperSettingsURL({ hostname, method: 'GitHub App' } as Account), hasRequiredScopes: (account) => accountHasScopes(account, 'REQUIRED'), hasRecommendedScopes: (account) => accountHasScopes(account, 'RECOMMENDED'), diff --git a/src/renderer/utils/forges/types.ts b/src/renderer/utils/forges/types.ts index e6404bae7..fc3233edc 100644 --- a/src/renderer/utils/forges/types.ts +++ b/src/renderer/utils/forges/types.ts @@ -196,6 +196,13 @@ export interface ForgeAdapter { performWebOAuth?(options: LoginOAuthWebOptions): Promise; /** Exchange an OAuth web-flow authorization code for an access token. */ exchangeAuthCodeForToken?(authCode: AuthCode, options: LoginOAuthWebOptions): Promise; + /** + * URL where the user can revoke the device-flow OAuth grant on the given + * hostname (so they can re-authorize with different scopes). Required when + * `startDeviceFlow` is provided — the route uses it to render the + * "revoke access" hint. + */ + getDeviceFlowRevokeAccessUrl?(hostname: Hostname): Link; // --- OAuth scopes --- // Forges without an OAuth scope concept (e.g. Gitea) report `true` for