Skip to content

Commit f0eec7c

Browse files
committed
feat: use system browser for oauth flow
Signed-off-by: Adam Setch <[email protected]>
1 parent 94b8b01 commit f0eec7c

File tree

4 files changed

+67
-39
lines changed

4 files changed

+67
-39
lines changed

src/main/main.ts

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -194,24 +194,5 @@ app.whenReady().then(async () => {
194194
// Handle gitify:// custom protocol URL events for OAuth 2.0 callback
195195
app.on('open-url', (event, url) => {
196196
event.preventDefault();
197-
198-
const link = new URL(url);
199-
200-
const type = link.hostname;
201-
const code = link.searchParams.get('code');
202-
203-
if (code && (type === 'auth' || type === 'oauth')) {
204-
mb.window.webContents.send(namespacedEvent('auth-code'), type, code);
205-
}
206-
207-
const error = link.searchParams.get('error');
208-
const errorDescription = link.searchParams.get('error_description');
209-
210-
if (error) {
211-
logError(
212-
'main:open-url',
213-
`Error during OAuth 2.0 callback ${error}`,
214-
new Error(errorDescription),
215-
);
216-
}
197+
mb.window.webContents.send(namespacedEvent('auth-callback'), url);
217198
});

src/renderer/utils/auth/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface LoginPersonalAccessTokenOptions {
2222
}
2323

2424
export interface AuthResponse {
25-
authType: AuthMethod;
25+
authMethod: AuthMethod;
2626
authCode: AuthCode;
2727
authOptions: LoginOAuthAppOptions;
2828
}

src/renderer/utils/auth/utils.test.ts

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ describe('renderer/utils/auth/utils.ts', () => {
3232
jest.clearAllMocks();
3333
});
3434

35-
it('should call authGitHub - auth flow', async () => {
35+
it('should call authGitHub - success auth flow', async () => {
3636
const mockIpcRendererOn = (
3737
jest.spyOn(ipcRenderer, 'on') as jest.Mock
3838
).mockImplementation((event, callback) => {
39-
if (event === 'gitify:auth-code') {
40-
callback(null, 'auth', '123-456');
39+
if (event === 'gitify:auth-callback') {
40+
callback(null, 'gitify://auth?code=123-456');
4141
}
4242
});
4343

@@ -50,20 +50,20 @@ describe('renderer/utils/auth/utils.ts', () => {
5050

5151
expect(mockIpcRendererOn).toHaveBeenCalledTimes(1);
5252
expect(mockIpcRendererOn).toHaveBeenCalledWith(
53-
'gitify:auth-code',
53+
'gitify:auth-callback',
5454
expect.any(Function),
5555
);
5656

57-
expect(res.authType).toBe('GitHub App');
57+
expect(res.authMethod).toBe('GitHub App');
5858
expect(res.authCode).toBe('123-456');
5959
});
6060

61-
it('should call authGitHub - oauth flow', async () => {
61+
it('should call authGitHub - success oauth flow', async () => {
6262
const mockIpcRendererOn = (
6363
jest.spyOn(ipcRenderer, 'on') as jest.Mock
6464
).mockImplementation((event, callback) => {
65-
if (event === 'gitify:auth-code') {
66-
callback(null, 'oauth', '123-456');
65+
if (event === 'gitify:auth-callback') {
66+
callback(null, 'gitify://oauth?code=123-456');
6767
}
6868
});
6969

@@ -80,13 +80,41 @@ describe('renderer/utils/auth/utils.ts', () => {
8080

8181
expect(mockIpcRendererOn).toHaveBeenCalledTimes(1);
8282
expect(mockIpcRendererOn).toHaveBeenCalledWith(
83-
'gitify:auth-code',
83+
'gitify:auth-callback',
8484
expect.any(Function),
8585
);
8686

87-
expect(res.authType).toBe('OAuth App');
87+
expect(res.authMethod).toBe('OAuth App');
8888
expect(res.authCode).toBe('123-456');
8989
});
90+
91+
it('should call authGitHub - failure', async () => {
92+
const mockIpcRendererOn = (
93+
jest.spyOn(ipcRenderer, 'on') as jest.Mock
94+
).mockImplementation((event, callback) => {
95+
if (event === 'gitify:auth-callback') {
96+
callback(
97+
null,
98+
'gitify://auth?error=invalid_request&error_description=The+redirect_uri+is+missing+or+invalid.&error_uri=https://docs.github.com/en/developers/apps/troubleshooting-oauth-errors',
99+
);
100+
}
101+
});
102+
103+
await expect(async () => await auth.authGitHub()).rejects.toEqual(
104+
"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",
105+
);
106+
107+
expect(openExternalLinkMock).toHaveBeenCalledTimes(1);
108+
expect(openExternalLinkMock).toHaveBeenCalledWith(
109+
'https://github.com/login/oauth/authorize?client_id=FAKE_CLIENT_ID_123&scope=read%3Auser%2Cnotifications%2Crepo',
110+
);
111+
112+
expect(mockIpcRendererOn).toHaveBeenCalledTimes(1);
113+
expect(mockIpcRendererOn).toHaveBeenCalledWith(
114+
'gitify:auth-callback',
115+
expect.any(Function),
116+
);
117+
});
90118
});
91119

92120
describe('getToken', () => {

src/renderer/utils/auth/utils.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,43 @@ import type { AuthMethod, AuthResponse, AuthTokenResponse } from './types';
2626
export function authGitHub(
2727
authOptions = Constants.DEFAULT_AUTH_OPTIONS,
2828
): Promise<AuthResponse> {
29-
return new Promise((resolve) => {
29+
return new Promise((resolve, reject) => {
3030
const authUrl = new URL(`https://${authOptions.hostname}`);
3131
authUrl.pathname = '/login/oauth/authorize';
3232
authUrl.searchParams.append('client_id', authOptions.clientId);
3333
authUrl.searchParams.append('scope', Constants.AUTH_SCOPE.toString());
3434

3535
openExternalLink(authUrl.toString() as Link);
3636

37-
const handleCallback = (authType: AuthMethod, authCode: AuthCode) => {
38-
resolve({ authType, authCode, authOptions });
37+
const handleCallback = (callbackUrl: string) => {
38+
const url = new URL(callbackUrl);
39+
40+
const type = url.hostname;
41+
const code = url.searchParams.get('code');
42+
const error = url.searchParams.get('error');
43+
const errorDescription = url.searchParams.get('error_description');
44+
const errorUri = url.searchParams.get('error_uri');
45+
46+
if (code && (type === 'auth' || type === 'oauth')) {
47+
const authMethod: AuthMethod =
48+
type === 'auth' ? 'GitHub App' : 'OAuth App';
49+
50+
resolve({
51+
authMethod: authMethod,
52+
authCode: code as AuthCode,
53+
authOptions: authOptions,
54+
});
55+
} else if (error) {
56+
reject(
57+
`Oops! Something went wrong and we couldn't log you in using GitHub. Please try again. Reason: ${errorDescription} Docs: ${errorUri}`,
58+
);
59+
}
3960
};
4061

4162
ipcRenderer.on(
42-
namespacedEvent('auth-code'),
43-
(_, authType: 'auth' | 'oauth', authCode: AuthCode) => {
44-
const type: AuthMethod =
45-
authType === 'auth' ? 'GitHub App' : 'OAuth App';
46-
handleCallback(type, authCode);
63+
namespacedEvent('auth-callback'),
64+
(_, callbackUrl: string) => {
65+
handleCallback(callbackUrl);
4766
},
4867
);
4968
});

0 commit comments

Comments
 (0)