diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index d652819ae..541344f66 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -12,6 +12,7 @@ import { AppProvider } from './context/App'; import { AccountsRoute } from './routes/Accounts'; import { FiltersRoute } from './routes/Filters'; import { LoginRoute } from './routes/Login'; +import { LoginWithDeviceFlowRoute } from './routes/LoginWithDeviceFlow'; import { LoginWithOAuthAppRoute } from './routes/LoginWithOAuthApp'; import { LoginWithPersonalAccessTokenRoute } from './routes/LoginWithPersonalAccessToken'; import { NotificationsRoute } from './routes/Notifications'; @@ -78,6 +79,10 @@ export const App = () => { path="/accounts" /> } path="/login" /> + } + path="/login-device-flow" + /> } path="/login-personal-access-token" diff --git a/src/renderer/__helpers__/test-utils.tsx b/src/renderer/__helpers__/test-utils.tsx index 6a4a8cbdf..1a35488b2 100644 --- a/src/renderer/__helpers__/test-utils.tsx +++ b/src/renderer/__helpers__/test-utils.tsx @@ -42,6 +42,9 @@ export function AppContextProvider({ // Default mock implementations for all required methods loginWithGitHubApp: jest.fn(), + startGitHubDeviceFlow: jest.fn(), + pollGitHubDeviceFlow: jest.fn(), + completeGitHubDeviceLogin: jest.fn(), loginWithOAuthApp: jest.fn(), loginWithPersonalAccessToken: jest.fn(), logoutFromAccount: jest.fn(), diff --git a/src/renderer/__mocks__/account-mocks.ts b/src/renderer/__mocks__/account-mocks.ts index 16787ffa4..3ded02c4e 100644 --- a/src/renderer/__mocks__/account-mocks.ts +++ b/src/renderer/__mocks__/account-mocks.ts @@ -14,7 +14,7 @@ export const mockGitHubAppAccount: Account = { platform: 'GitHub Cloud', method: 'GitHub App', token: 'token-987654321' as Token, - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, user: mockGitifyUser, hasRequiredScopes: true, }; @@ -23,7 +23,7 @@ export const mockPersonalAccessTokenAccount: Account = { platform: 'GitHub Cloud', method: 'Personal Access Token', token: 'token-123-456' as Token, - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, user: mockGitifyUser, hasRequiredScopes: true, }; @@ -41,7 +41,7 @@ export const mockGitHubCloudAccount: Account = { platform: 'GitHub Cloud', method: 'Personal Access Token', token: 'token-123-456' as Token, - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, user: mockGitifyUser, version: 'latest', hasRequiredScopes: true, diff --git a/src/renderer/constants.ts b/src/renderer/constants.ts index c81c24317..b4f4e89ad 100644 --- a/src/renderer/constants.ts +++ b/src/renderer/constants.ts @@ -1,5 +1,5 @@ -import type { ClientID, ClientSecret, Hostname, Link } from './types'; -import type { LoginOAuthAppOptions } from './utils/auth/types'; +import type { ClientID, Hostname, Link } from './types'; +import type { LoginOAuthDeviceOptions } from './utils/auth/types'; export const Constants = { STORAGE_KEY: 'gitify-storage', @@ -10,11 +10,10 @@ export const Constants = { ALTERNATE: ['read:user', 'notifications', 'public_repo'], }, - DEFAULT_AUTH_OPTIONS: { + OAUTH_DEVICE_FLOW: { hostname: 'github.com' as Hostname, clientId: process.env.OAUTH_CLIENT_ID as ClientID, - clientSecret: process.env.OAUTH_CLIENT_SECRET as ClientSecret, - } satisfies LoginOAuthAppOptions, + } satisfies LoginOAuthDeviceOptions, GITHUB_API_BASE_URL: 'https://api.github.com', GITHUB_API_GRAPHQL_URL: 'https://api.github.com/graphql', diff --git a/src/renderer/context/App.tsx b/src/renderer/context/App.tsx index 4d37c4491..43037dea5 100644 --- a/src/renderer/context/App.tsx +++ b/src/renderer/context/App.tsx @@ -25,6 +25,7 @@ import type { FilterSettingsValue, GitifyError, GitifyNotification, + Hostname, SettingsState, SettingsValue, Status, @@ -32,7 +33,8 @@ import type { } from '../types'; import { FetchType } from '../types'; import type { - LoginOAuthAppOptions, + DeviceFlowSession, + LoginOAuthWebOptions, LoginPersonalAccessTokenOptions, } from '../utils/auth/types'; @@ -42,9 +44,11 @@ import { exchangeAuthCodeForAccessToken, getAccountUUID, hasAccounts, - performGitHubOAuth, + performGitHubWebOAuth, + pollGitHubDeviceFlow, refreshAccount, removeAccount, + startGitHubDeviceFlow, } from '../utils/auth/utils'; import { decryptValue, @@ -75,7 +79,13 @@ export interface AppContextState { auth: AuthState; isLoggedIn: boolean; loginWithGitHubApp: () => Promise; - loginWithOAuthApp: (data: LoginOAuthAppOptions) => Promise; + startGitHubDeviceFlow: () => Promise; + pollGitHubDeviceFlow: (session: DeviceFlowSession) => Promise; + completeGitHubDeviceLogin: ( + token: Token, + hostname?: Hostname, + ) => Promise; + loginWithOAuthApp: (data: LoginOAuthWebOptions) => Promise; loginWithPersonalAccessToken: ( data: LoginPersonalAccessTokenOptions, ) => Promise; @@ -396,27 +406,67 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }, [auth]); /** - * Login with GitHub App. - * - * Note: although we call this "Login with GitHub App", this function actually - * authenticates via a predefined "Gitify" GitHub OAuth App. + * Start a GitHub device flow session. + */ + const startGitHubDeviceFlowWithDefaults = useCallback( + async () => await startGitHubDeviceFlow(), + [], + ); + + /** + * Poll GitHub device flow session for completion. + */ + const pollGitHubDeviceFlowWithSession = useCallback( + async (session: DeviceFlowSession) => await pollGitHubDeviceFlow(session), + [], + ); + + /** + * Persist GitHub app login after device flow completes. + */ + const completeGitHubDeviceLogin = useCallback( + async ( + token: Token, + hostname: Hostname = Constants.OAUTH_DEVICE_FLOW.hostname, + ) => { + const updatedAuth = await addAccount(auth, 'GitHub App', token, hostname); + + persistAuth(updatedAuth); + }, + [auth, persistAuth], + ); + + /** + * Login with GitHub App using device flow so the client secret is never bundled or persisted. */ const loginWithGitHubApp = useCallback(async () => { - const { authCode } = await performGitHubOAuth(); - const token = await exchangeAuthCodeForAccessToken(authCode); - const hostname = Constants.DEFAULT_AUTH_OPTIONS.hostname; + const session = await startGitHubDeviceFlowWithDefaults(); + const intervalMs = Math.max(5000, session.intervalSeconds * 1000); - const updatedAuth = await addAccount(auth, 'GitHub App', token, hostname); + while (Date.now() < session.expiresAt) { + const token = await pollGitHubDeviceFlowWithSession(session); - persistAuth(updatedAuth); - }, [auth, persistAuth]); + if (token) { + await completeGitHubDeviceLogin(token, session.hostname); + return; + } + + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + + throw new Error('Device code expired before authorization completed'); + }, [ + startGitHubDeviceFlowWithDefaults, + pollGitHubDeviceFlowWithSession, + completeGitHubDeviceLogin, + ]); /** * Login with custom GitHub OAuth App. */ const loginWithOAuthApp = useCallback( - async (data: LoginOAuthAppOptions) => { - const { authOptions, authCode } = await performGitHubOAuth(data); + async (data: LoginOAuthWebOptions) => { + const { authOptions, authCode } = await performGitHubWebOAuth(data); const token = await exchangeAuthCodeForAccessToken(authCode, authOptions); const updatedAuth = await addAccount( @@ -490,6 +540,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { auth, isLoggedIn, loginWithGitHubApp, + startGitHubDeviceFlow: startGitHubDeviceFlowWithDefaults, + pollGitHubDeviceFlow: pollGitHubDeviceFlowWithSession, + completeGitHubDeviceLogin, loginWithOAuthApp, loginWithPersonalAccessToken, logoutFromAccount, @@ -520,6 +573,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { auth, isLoggedIn, loginWithGitHubApp, + startGitHubDeviceFlowWithDefaults, + pollGitHubDeviceFlowWithSession, + completeGitHubDeviceLogin, loginWithOAuthApp, loginWithPersonalAccessToken, logoutFromAccount, diff --git a/src/renderer/routes/Login.tsx b/src/renderer/routes/Login.tsx index c8dbffda6..711ce3007 100644 --- a/src/renderer/routes/Login.tsx +++ b/src/renderer/routes/Login.tsx @@ -1,4 +1,4 @@ -import { type FC, useCallback, useEffect } from 'react'; +import { type FC, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { KeyIcon, MarkGithubIcon, PersonIcon } from '@primer/octicons-react'; @@ -12,31 +12,18 @@ import { Centered } from '../components/layout/Centered'; import { Size } from '../types'; import { showWindow } from '../utils/comms'; -import { rendererLogError } from '../utils/logger'; export const LoginRoute: FC = () => { const navigate = useNavigate(); - const { loginWithGitHubApp, isLoggedIn } = useAppContext(); + const { isLoggedIn } = useAppContext(); useEffect(() => { if (isLoggedIn) { showWindow(); navigate('/', { replace: true }); } - }, [isLoggedIn]); - - const loginUser = useCallback(async () => { - try { - await loginWithGitHubApp(); - } catch (err) { - rendererLogError( - 'loginWithGitHubApp', - 'failed to login with GitHub', - err, - ); - } - }, [loginWithGitHubApp]); + }, [isLoggedIn, navigate]); return ( @@ -54,7 +41,7 @@ export const LoginRoute: FC = () => { + + {session && ( + + )} + + + ); +}; diff --git a/src/renderer/routes/LoginWithOAuthApp.tsx b/src/renderer/routes/LoginWithOAuthApp.tsx index ac5674194..599e53c87 100644 --- a/src/renderer/routes/LoginWithOAuthApp.tsx +++ b/src/renderer/routes/LoginWithOAuthApp.tsx @@ -28,7 +28,7 @@ import { Footer } from '../components/primitives/Footer'; import { Header } from '../components/primitives/Header'; import type { ClientID, ClientSecret, Hostname, Token } from '../types'; -import type { LoginOAuthAppOptions } from '../utils/auth/types'; +import type { LoginOAuthWebOptions } from '../utils/auth/types'; import { getNewOAuthAppURL, @@ -116,7 +116,7 @@ export const LoginWithOAuthAppRoute: FC = () => { const verifyLoginCredentials = useCallback( async (data: IFormData) => { try { - await loginWithOAuthApp(data as LoginOAuthAppOptions); + await loginWithOAuthApp(data as LoginOAuthWebOptions); navigate(-1); } catch (err) { rendererLogError( diff --git a/src/renderer/utils/auth/types.ts b/src/renderer/utils/auth/types.ts index 684f73904..71a7fa324 100644 --- a/src/renderer/utils/auth/types.ts +++ b/src/renderer/utils/auth/types.ts @@ -10,12 +10,27 @@ export type AuthMethod = 'GitHub App' | 'Personal Access Token' | 'OAuth App'; export type PlatformType = 'GitHub Cloud' | 'GitHub Enterprise Server'; -export interface LoginOAuthAppOptions { +export interface LoginOAuthDeviceOptions { + hostname: Hostname; + clientId: ClientID; +} + +export interface LoginOAuthWebOptions { hostname: Hostname; clientId: ClientID; clientSecret: ClientSecret; } +export interface DeviceFlowSession { + hostname: Hostname; + clientId: ClientID; + deviceCode: string; + userCode: string; + verificationUri: string; + intervalSeconds: number; + expiresAt: number; +} + export interface LoginPersonalAccessTokenOptions { hostname: Hostname; token: Token; @@ -24,5 +39,5 @@ export interface LoginPersonalAccessTokenOptions { export interface AuthResponse { authMethod: AuthMethod; authCode: AuthCode; - authOptions: LoginOAuthAppOptions; + authOptions: LoginOAuthWebOptions; } diff --git a/src/renderer/utils/auth/utils.test.ts b/src/renderer/utils/auth/utils.test.ts index 98fc67a8c..3fc7b92ec 100644 --- a/src/renderer/utils/auth/utils.test.ts +++ b/src/renderer/utils/auth/utils.test.ts @@ -14,7 +14,7 @@ import type { Hostname, Token, } from '../../types'; -import type { AuthMethod } from './types'; +import type { AuthMethod, LoginOAuthWebOptions } from './types'; import * as comms from '../../utils/comms'; import * as apiClient from '../api/client'; @@ -24,11 +24,23 @@ import { getNewOAuthAppURL, getNewTokenURL } from './utils'; jest.mock('@octokit/oauth-methods', () => ({ ...jest.requireActual('@octokit/oauth-methods'), + createDeviceCode: jest.fn(), + exchangeDeviceCode: jest.fn(), exchangeWebFlowCode: jest.fn(), })); -import { exchangeWebFlowCode } from '@octokit/oauth-methods'; +import { + createDeviceCode, + exchangeDeviceCode, + exchangeWebFlowCode, +} from '@octokit/oauth-methods'; +const createDeviceCodeMock = createDeviceCode as jest.MockedFunction< + typeof createDeviceCode +>; +const exchangeDeviceCodeMock = exchangeDeviceCode as jest.MockedFunction< + typeof exchangeDeviceCode +>; const exchangeWebFlowCodeMock = exchangeWebFlowCode as jest.MockedFunction< typeof exchangeWebFlowCode >; @@ -38,6 +50,12 @@ describe('renderer/utils/auth/utils.ts', () => { configureAxiosHttpAdapterForNock(); }); + const webAuthOptions: LoginOAuthWebOptions = { + hostname: 'github.com' as Hostname, + clientId: 'FAKE_CLIENT_ID_123' as ClientID, + clientSecret: 'FAKE_CLIENT_SECRET_123' as ClientSecret, + }; + describe('authGitHub', () => { jest.spyOn(logger, 'rendererLogInfo').mockImplementation(); const openExternalLinkSpy = jest @@ -48,14 +66,55 @@ describe('renderer/utils/auth/utils.ts', () => { jest.clearAllMocks(); }); - it('should call performGitHubOAuth using gitify oauth app - success auth flow', async () => { + it('should authenticate using device flow for GitHub app', async () => { + createDeviceCodeMock.mockResolvedValueOnce({ + data: { + device_code: 'device-code', + user_code: 'user-code', + verification_uri: 'https://github.com/login/device', + expires_in: 900, + interval: 5, + }, + } as unknown as Awaited>); + + exchangeDeviceCodeMock.mockResolvedValueOnce({ + authentication: { + token: 'device-token', + }, + } as unknown as Awaited>); + + const token = await authUtils.performGitHubDeviceOAuth(); + + expect(createDeviceCodeMock).toHaveBeenCalledWith({ + clientType: 'oauth-app', + clientId: 'FAKE_CLIENT_ID_123', + scopes: Constants.OAUTH_SCOPES.RECOMMENDED, + request: expect.any(Function), + }); + + expect(openExternalLinkSpy).toHaveBeenCalledWith( + 'https://github.com/login/device?user_code=user-code', + ); + + expect(exchangeDeviceCodeMock).toHaveBeenCalledWith({ + clientType: 'oauth-app', + clientId: 'FAKE_CLIENT_ID_123', + code: 'device-code', + interval: 5, + request: expect.any(Function), + }); + + expect(token).toBe('device-token'); + }); + + it('should call performGitHubWebOAuth using gitify oauth app - success auth flow', async () => { window.gitify.onAuthCallback = jest .fn() .mockImplementation((callback) => { callback('gitify://auth?code=123-456'); }); - const res = await authUtils.performGitHubOAuth(); + const res = await authUtils.performGitHubWebOAuth(webAuthOptions); expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); expect(openExternalLinkSpy).toHaveBeenCalledWith( @@ -73,14 +132,14 @@ describe('renderer/utils/auth/utils.ts', () => { expect(res.authCode).toBe('123-456'); }); - it('should call performGitHubOAuth using custom oauth app - success oauth flow', async () => { + it('should call performGitHubWebOAuth using custom oauth app - success oauth flow', async () => { window.gitify.onAuthCallback = jest .fn() .mockImplementation((callback) => { callback('gitify://oauth?code=123-456'); }); - const res = await authUtils.performGitHubOAuth({ + const res = await authUtils.performGitHubWebOAuth({ clientId: 'BYO_CLIENT_ID' as ClientID, clientSecret: 'BYO_CLIENT_SECRET' as ClientSecret, hostname: 'my.git.com' as Hostname, @@ -102,7 +161,7 @@ describe('renderer/utils/auth/utils.ts', () => { expect(res.authCode).toBe('123-456'); }); - it('should call performGitHubOAuth - failure', async () => { + it('should call performGitHubWebOAuth - failure', async () => { window.gitify.onAuthCallback = jest .fn() .mockImplementation((callback) => { @@ -112,7 +171,7 @@ describe('renderer/utils/auth/utils.ts', () => { }); await expect( - async () => await authUtils.performGitHubOAuth(), + async () => await authUtils.performGitHubWebOAuth(webAuthOptions), ).rejects.toEqual( new Error( "Oops! Something went wrong and we couldn't log you in using GitHub. Please try again. Reason: The redirect_uri is missing or invalid. Docs: https://docs.github.com/en/developers/apps/troubleshooting-oauth-errors", @@ -141,12 +200,11 @@ describe('renderer/utils/auth/utils.ts', () => { authentication: { token: 'this-is-a-token', }, - } as any); + } as unknown as Awaited>); - const res = await authUtils.exchangeAuthCodeForAccessToken( - authCode, - Constants.DEFAULT_AUTH_OPTIONS, - ); + const res = await authUtils.exchangeAuthCodeForAccessToken(authCode, { + ...webAuthOptions, + }); expect(exchangeWebFlowCodeMock).toHaveBeenCalledWith({ clientType: 'oauth-app', @@ -157,6 +215,16 @@ describe('renderer/utils/auth/utils.ts', () => { }); expect(res).toBe('this-is-a-token'); }); + + it('should throw when client secret is missing', async () => { + await expect( + async () => + await authUtils.exchangeAuthCodeForAccessToken(authCode, { + ...webAuthOptions, + clientSecret: undefined as unknown as ClientSecret, + }), + ).rejects.toThrow('clientSecret is required to exchange an auth code'); + }); }); describe('addAccount', () => { @@ -475,7 +543,7 @@ describe('renderer/utils/auth/utils.ts', () => { it('should use default hostname if no accounts', () => { expect(authUtils.getPrimaryAccountHostname({ accounts: [] })).toBe( - Constants.DEFAULT_AUTH_OPTIONS.hostname, + Constants.OAUTH_DEVICE_FLOW.hostname, ); }); }); diff --git a/src/renderer/utils/auth/utils.ts b/src/renderer/utils/auth/utils.ts index 6246a707e..96b5d9909 100644 --- a/src/renderer/utils/auth/utils.ts +++ b/src/renderer/utils/auth/utils.ts @@ -1,4 +1,6 @@ import { + createDeviceCode, + exchangeDeviceCode, exchangeWebFlowCode, getWebFlowAuthorizationUrl, } from '@octokit/oauth-methods'; @@ -19,7 +21,13 @@ import type { Link, Token, } from '../../types'; -import type { AuthMethod, AuthResponse, LoginOAuthAppOptions } from './types'; +import type { + AuthMethod, + AuthResponse, + DeviceFlowSession, + LoginOAuthDeviceOptions, + LoginOAuthWebOptions, +} from './types'; import { fetchAuthenticatedUserDetails } from '../api/client'; import { getGitHubAuthBaseUrl } from '../api/utils'; @@ -27,8 +35,8 @@ import { encryptValue, openExternalLink } from '../comms'; import { getPlatformFromHostname } from '../helpers'; import { rendererLogError, rendererLogInfo, rendererLogWarn } from '../logger'; -export function performGitHubOAuth( - authOptions: LoginOAuthAppOptions = Constants.DEFAULT_AUTH_OPTIONS, +export function performGitHubWebOAuth( + authOptions: LoginOAuthWebOptions, ): Promise { return new Promise((resolve, reject) => { const { url } = getWebFlowAuthorizationUrl({ @@ -80,10 +88,80 @@ export function performGitHubOAuth( }); } +export async function startGitHubDeviceFlow( + authOptions: LoginOAuthDeviceOptions = Constants.OAUTH_DEVICE_FLOW, +): Promise { + const deviceCode = await createDeviceCode({ + clientType: 'oauth-app' as const, + clientId: authOptions.clientId, + scopes: Constants.OAUTH_SCOPES.RECOMMENDED, + request: request.defaults({ + baseUrl: getGitHubAuthBaseUrl(authOptions.hostname).toString(), + }), + }); + + return { + hostname: authOptions.hostname, + clientId: authOptions.clientId, + deviceCode: deviceCode.data.device_code, + userCode: deviceCode.data.user_code, + verificationUri: deviceCode.data.verification_uri, + intervalSeconds: deviceCode.data.interval, + expiresAt: Date.now() + deviceCode.data.expires_in * 1000, + } as DeviceFlowSession; +} + +export async function pollGitHubDeviceFlow( + session: DeviceFlowSession, +): Promise { + try { + const { authentication } = await exchangeDeviceCode({ + clientType: 'oauth-app' as const, + clientId: session.clientId, + code: session.deviceCode, + request: request.defaults({ + baseUrl: getGitHubAuthBaseUrl(session.hostname).toString(), + }), + }); + + return authentication.token as Token; + } catch (err) { + const errorCode = (err as Record)?.response?.data?.error; + + if (errorCode === 'authorization_pending' || errorCode === 'slow_down') { + return null; + } + + throw err; + } +} + +export async function performGitHubDeviceOAuth(): Promise { + const session = await startGitHubDeviceFlow(); + + const intervalMs = Math.max(5000, session.intervalSeconds * 1000); + + while (Date.now() < session.expiresAt) { + const token = await pollGitHubDeviceFlow(session); + + if (token) { + return token; + } + + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + + throw new Error('Device code expired before authorization completed'); +} + export async function exchangeAuthCodeForAccessToken( authCode: AuthCode, - authOptions: LoginOAuthAppOptions = Constants.DEFAULT_AUTH_OPTIONS, + authOptions: LoginOAuthWebOptions, ): Promise { + if (!authOptions.clientSecret) { + throw new Error('clientSecret is required to exchange an auth code'); + } + const { authentication } = await exchangeWebFlowCode({ clientType: 'oauth-app', clientId: authOptions.clientId, @@ -278,7 +356,7 @@ export function getAccountUUID(account: Account): string { * Return the primary (first) account hostname */ export function getPrimaryAccountHostname(auth: AuthState) { - return auth.accounts[0]?.hostname ?? Constants.DEFAULT_AUTH_OPTIONS.hostname; + return auth.accounts[0]?.hostname ?? Constants.OAUTH_DEVICE_FLOW.hostname; } export function hasAccounts(auth: AuthState) { diff --git a/src/renderer/utils/comms.ts b/src/renderer/utils/comms.ts index 5ffa3b64c..e5b91d420 100644 --- a/src/renderer/utils/comms.ts +++ b/src/renderer/utils/comms.ts @@ -50,7 +50,9 @@ export function setAutoLaunch(value: boolean): void { export function setUseAlternateIdleIcon(value: boolean): void { window.gitify.tray.useAlternateIdleIcon(value); } - +export async function copyToClipboard(text: string): Promise { + await navigator.clipboard.writeText(text); +} export function setUseUnreadActiveIcon(value: boolean): void { window.gitify.tray.useUnreadActiveIcon(value); } diff --git a/src/renderer/utils/helpers.ts b/src/renderer/utils/helpers.ts index 04788b63a..334c6def6 100644 --- a/src/renderer/utils/helpers.ts +++ b/src/renderer/utils/helpers.ts @@ -14,13 +14,13 @@ import { rendererLogError } from './logger'; import { createNotificationHandler } from './notifications/handlers'; export function getPlatformFromHostname(hostname: string): PlatformType { - return hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname) + return hostname.endsWith(Constants.OAUTH_DEVICE_FLOW.hostname) ? 'GitHub Cloud' : 'GitHub Enterprise Server'; } export function isEnterpriseServerHost(hostname: Hostname): boolean { - return !hostname.endsWith(Constants.DEFAULT_AUTH_OPTIONS.hostname); + return !hostname.endsWith(Constants.OAUTH_DEVICE_FLOW.hostname); } export function generateNotificationReferrerId( diff --git a/src/renderer/utils/storage.test.ts b/src/renderer/utils/storage.test.ts index f4e69377b..6d38df7d8 100644 --- a/src/renderer/utils/storage.test.ts +++ b/src/renderer/utils/storage.test.ts @@ -13,7 +13,7 @@ describe('renderer/utils/storage.ts', () => { auth: { accounts: [ { - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', token: '123-456' as Token, @@ -28,7 +28,7 @@ describe('renderer/utils/storage.ts', () => { expect(result.auth.accounts).toEqual([ { - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', token: '123-456' as Token, @@ -55,7 +55,7 @@ describe('renderer/utils/storage.ts', () => { auth: { accounts: [ { - hostname: Constants.DEFAULT_AUTH_OPTIONS.hostname, + hostname: Constants.OAUTH_DEVICE_FLOW.hostname, platform: 'GitHub Cloud', method: 'Personal Access Token', token: '123-456' as Token,