Skip to content

Commit 115a8a5

Browse files
authored
fix login provider logic in electron apps (#2958)
### Fixes # <!-- Mention the issues this PR addresses --> #2955 ### Checks - [ ] Ran `yarn test-build` - [ ] Updated relevant documentations - [ ] Updated matching config options in altair-static ### Changes proposed in this pull request: <!-- Describe the changes being introduced in this PR --> ## Summary by Sourcery Unify login provider handling across web and Electron flows by centralizing identity provider constants and popup URL generation, and wiring the selected provider through the Electron IPC authentication path. New Features: - Introduce a shared identity provider module with helpers to construct login popup URLs. Bug Fixes: - Ensure Electron authentication passes the selected identity provider through IPC and uses the correct login popup URL format. Enhancements: - Refactor login redirect and API client code to use the shared identity provider utilities instead of local implementations. - Loosen the Electron IPC handler argument typing to support provider parameters. Build: - Add api-utils as a dependency of the altair-electron package. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for multiple identity providers (Google, GitHub) for user authentication. * **Chores** * Updated dependencies to support identity provider functionality. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent c05977e commit 115a8a5

File tree

9 files changed

+62
-40
lines changed

9 files changed

+62
-40
lines changed

packages/altair-api-utils/src/client.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { ConfigEnvironment } from 'altair-graphql-core/build/config/environment'
3030
import { UrlConfig } from 'altair-graphql-core/build/config/urls';
3131
import { IRateMessageDto, ISendMessageDto } from './ai';
3232
import { IAvailableCredits } from 'altair-graphql-core/build/types/state/account.interfaces';
33+
import { getPopupUrl } from 'altair-graphql-core/build/identity/providers';
3334
export type FullQueryCollection = QueryCollection & {
3435
queries: QueryItem[];
3536
};
@@ -130,12 +131,7 @@ export class APIClient {
130131
nonce: string,
131132
provider: IdentityProvider = IdentityProvider.GOOGLE
132133
) {
133-
const url = new URL(this.urlConfig.loginClient);
134-
url.searchParams.append('nonce', nonce);
135-
url.searchParams.append('sc', location.origin);
136-
url.searchParams.append('provider', provider);
137-
138-
return url.href;
134+
return getPopupUrl(this.urlConfig.loginClient, nonce, location.origin, provider);
139135
}
140136

141137
observeUser() {

packages/altair-app/src/app/modules/altair/services/account/account.service.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import { IdentityProvider } from '@altairgraphql/db';
1313
export class AccountService {
1414
private electronApp = inject(ElectronAppService);
1515

16-
1716
private async signin(provider: IdentityProvider = IdentityProvider.GOOGLE) {
1817
if (isElectronApp()) {
19-
const authToken = await this.electronApp.getAuthToken();
18+
const authToken = await this.electronApp.getAuthToken(provider);
2019
return apiClient.signInWithCustomToken(authToken);
2120
}
2221

packages/altair-app/src/app/modules/altair/services/electron-app/electron-app.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
} from '@altairgraphql/electron-interop/build/renderer';
2929
import { environment } from 'environments/environment';
3030
import { SettingsState } from 'altair-graphql-core/build/types/state/settings.interfaces';
31+
import { IdentityProvider } from '@altairgraphql/db';
3132

3233
interface ConnectOptions {
3334
importFileContent: (content: string) => void;
@@ -225,8 +226,8 @@ export class ElectronAppService {
225226
});
226227
}
227228

228-
getAuthToken() {
229-
return this.api?.actions.getAuthToken();
229+
getAuthToken(provider: IdentityProvider) {
230+
return this.api?.actions.getAuthToken(provider);
230231
}
231232

232233
getAutobackupData() {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// TODO: For some reason, importing from altair-db in login-redirect package causes issues. Strangely this only seems to affect login-redirect package.
2+
// Getting src/login-redirect.ts (4:2): "IDENTITY_PROVIDERS" is not exported by "../altair-db/build/client.js", imported by "src/login-redirect.ts".
3+
// Will investigate later.
4+
const IDENTITY_PROVIDERS = {
5+
GOOGLE: 'GOOGLE',
6+
GITHUB: 'GITHUB',
7+
} as const;
8+
type IdentityProvider = (typeof IDENTITY_PROVIDERS)[keyof typeof IDENTITY_PROVIDERS];
9+
10+
export { IDENTITY_PROVIDERS, IdentityProvider };
11+
12+
export function getPopupUrl(
13+
url: string,
14+
nonce: string,
15+
source: string,
16+
provider: IdentityProvider
17+
) {
18+
const urlObj = new URL(url);
19+
urlObj.searchParams.append('nonce', nonce);
20+
urlObj.searchParams.append('sc', source);
21+
urlObj.searchParams.append('provider', provider);
22+
23+
return urlObj.href;
24+
}

packages/altair-electron-interop/src/api.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { SettingsState } from 'altair-graphql-core/build/types/state/settings.interfaces';
2+
import { IdentityProvider } from 'altair-graphql-core/build/identity/providers';
23
import { ipcRenderer } from 'electron';
34
import { IPC_EVENT_NAMES, STORE_EVENTS } from './constants';
45
import { SETTINGS_STORE_EVENTS } from './settings';
@@ -117,8 +118,11 @@ export const electronApi = {
117118
restartApp() {
118119
return ipcRenderer.send(IPC_EVENT_NAMES.RENDERER_RESTART_APP);
119120
},
120-
getAuthToken() {
121-
return invokeWithCustomErrors(IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN);
121+
getAuthToken(provider: IdentityProvider) {
122+
return invokeWithCustomErrors(
123+
IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN,
124+
provider
125+
);
122126
},
123127
getAutobackupData() {
124128
return invokeWithCustomErrors(IPC_EVENT_NAMES.RENDERER_GET_AUTOBACKUP_DATA);

packages/altair-electron/src/app/window.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
renderAltair,
1212
renderInitSnippet,
1313
} from 'altair-static';
14+
import { IdentityProvider } from 'altair-graphql-core/build/identity/providers';
1415

1516
import { checkMultipleDataVersions } from '../utils/check-multi-data-versions';
1617
import { createSha256CspHash } from '../utils/csp-hash';
@@ -185,14 +186,17 @@ export class WindowManager {
185186
}
186187
);
187188

188-
handleWithCustomErrors(IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN, async (e) => {
189-
if (!e.sender || e.sender !== this.instance?.webContents) {
190-
throw new Error('untrusted source trying to get auth token');
191-
}
189+
handleWithCustomErrors(
190+
IPC_EVENT_NAMES.RENDERER_GET_AUTH_TOKEN,
191+
async (e, provider: IdentityProvider) => {
192+
if (!e.sender || e.sender !== this.instance?.webContents) {
193+
throw new Error('untrusted source trying to get auth token');
194+
}
192195

193-
const authServer = new AuthServer();
194-
return authServer.getCustomToken();
195-
});
196+
const authServer = new AuthServer();
197+
return authServer.getCustomToken(provider);
198+
}
199+
);
196200

197201
handleWithCustomErrors(
198202
IPC_EVENT_NAMES.RENDERER_GET_AUTOBACKUP_DATA,

packages/altair-electron/src/auth/server/index.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,20 @@ import bodyParser from 'body-parser';
44
import { EventEmitter } from 'events';
55
import path from 'path';
66
import { randomBytes } from 'crypto';
7-
import { getStaticDirectory } from '../../utils';
87
import { session, shell } from 'electron';
98
import { getCSP, INLINE, SELF } from 'csp-header';
109
import getPort from 'get-port';
10+
import {
11+
IdentityProvider,
12+
getPopupUrl,
13+
} from 'altair-graphql-core/build/identity/providers';
1114

1215
export const IPC_SET_CUSTOM_TOKEN_EVENT = 'auth:set-custom-token';
1316

1417
const newNonce = () => {
1518
const validChars =
1619
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
17-
const array = randomBytes(40).map(x =>
20+
const array = randomBytes(40).map((x) =>
1821
validChars.charCodeAt(x % validChars.length)
1922
);
2023
return String.fromCharCode(...array);
@@ -36,16 +39,12 @@ export class AuthServer {
3639
app.use(bodyParser.json());
3740

3841
app.use('/login', (req, res) => {
39-
return res.sendFile(
40-
path.resolve(authClientStaticDirectory(), 'index.html')
41-
);
42+
return res.sendFile(path.resolve(authClientStaticDirectory(), 'index.html'));
4243
});
4344
app.use('/callback', (req, res) => {
4445
// TODO: Verify ttl
4546
if (req.body.nonce !== this.nonce) {
46-
return res
47-
.status(400)
48-
.send({ status: 'error', message: 'invalid request' });
47+
return res.status(400).send({ status: 'error', message: 'invalid request' });
4948
}
5049

5150
this.emitter.emit('token', req.body.token);
@@ -65,13 +64,13 @@ export class AuthServer {
6564
}
6665
}
6766

68-
async getCustomToken() {
67+
async getCustomToken(provider: IdentityProvider) {
6968
if (!this.server) {
7069
await this.start();
7170
}
7271
// TODO: Use a hosted domain instead
7372
await shell.openExternal(
74-
`http://localhost:${this.port}/login?nonce=${this.nonce}`
73+
getPopupUrl(`http://localhost:${this.port}/login`, this.nonce, '', provider)
7574
);
7675

7776
const token = await this.listenForToken();

packages/altair-electron/src/utils/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* eslint-disable no-sync */
2-
import { readdir } from 'fs';
3-
import fs from 'fs';
2+
import fs, { readdir } from 'fs';
43
import { join } from 'path';
54
import { App, ipcMain } from 'electron';
65
import { ALTAIR_CUSTOM_PROTOCOL } from '@altairgraphql/electron-interop';
@@ -45,7 +44,7 @@ const encodeError = (e: Error) => {
4544

4645
export const handleWithCustomErrors = (
4746
channel: string,
48-
handler: (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown
47+
handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => unknown
4948
) => {
5049
ipcMain.handle(channel, async (...args) => {
5150
try {

packages/login-redirect/src/login-redirect.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
import { OAUTH_POPUP_CALLBACK_MESSAGE_TYPE } from '@altairgraphql/api-utils/build/constants';
22
import { closeWindow, getValidSource, isValidOpener } from './helpers';
33
import { getAltairConfig } from 'altair-graphql-core/build/config';
4+
import {
5+
IdentityProvider,
6+
IDENTITY_PROVIDERS,
7+
} from 'altair-graphql-core/build/identity/providers';
48

5-
// TODO: For some reason, importing from altair-db causes issues. Strangely this only seems to affect login-redirect package.
6-
// Getting src/login-redirect.ts (4:2): "IDENTITY_PROVIDERS" is not exported by "../altair-db/build/client.js", imported by "src/login-redirect.ts".
7-
// Will investigate later.
8-
const IDENTITY_PROVIDERS = {
9-
GOOGLE: 'GOOGLE',
10-
GITHUB: 'GITHUB',
11-
} as const;
12-
type IdentityProvider = (typeof IDENTITY_PROVIDERS)[keyof typeof IDENTITY_PROVIDERS];
139
const OAUTH_NONCE_KEY = 'altairgql:oauth:nonce:key';
1410

1511
const getNonce = () => {

0 commit comments

Comments
 (0)