Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions packages/atlas-service/src/atlas-admin-api-auth-endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Whitelist of Atlas Admin API endpoints that require authentication (and that Compass actively uses).

// Source: https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/

const groupId = /^([a-f0-9]{24})$/;
const clusterName = /^[a-zA-Z0-9][a-zA-Z0-9-]*$/;

export const ATLAS_ADMIN_API_AUTH_ENDPOINTS = [
'/api/atlas/v2/clusters',
new RegExp(`/api/atlas/v2/groups/${groupId.source}/clusters`),
new RegExp(
`/api/atlas/v2/groups/${groupId.source}/clusters/${clusterName.source}`
),
new RegExp(`/api/atlas/v2/groups/${groupId.source}/accessList`),
];
Comment on lines +5 to +15
3 changes: 0 additions & 3 deletions packages/atlas-service/src/atlas-auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ export abstract class AtlasAuthService extends EventEmitter {
): Promise<AtlasUserInfo>;
abstract signOut(): Promise<void>;
abstract isAuthenticated(opts?: ArgsWithSignal): Promise<boolean>;
abstract getAuthHeaders(
opts?: ArgsWithSignal
): Promise<Record<string, string>>;

abstract getUserInfo(opts?: ArgsWithSignal): Promise<AtlasUserInfo>;

Expand Down
3 changes: 1 addition & 2 deletions packages/atlas-service/src/atlas-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,11 @@ export class AtlasService {
url: RequestInfo | URL,
init?: RequestInit
): Promise<Response> {
const authHeaders = await this.authService.getAuthHeaders();
return this.fetch(url, {
...init,
headers: {
...init?.headers,
...authHeaders,
['X-Compass-Auth']: 'true',
},
credentials: 'include',
});
Expand Down
16 changes: 2 additions & 14 deletions packages/atlas-service/src/compass-atlas-auth-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,8 @@ import type { ArgsWithSignal } from './atlas-auth-service';
export class CompassAtlasAuthService extends AtlasAuthService {
private _ipc = ipcRenderer?.createInvoke<
typeof AtlasServiceMain,
'getUserInfo' | 'isAuthenticated' | 'signIn' | 'signOut' | 'maybeGetToken'
>('AtlasService', [
'getUserInfo',
'isAuthenticated',
'signIn',
'signOut',
'maybeGetToken',
]);
'getUserInfo' | 'isAuthenticated' | 'signIn' | 'signOut'
>('AtlasService', ['getUserInfo', 'isAuthenticated', 'signIn', 'signOut']);

private get ipc() {
if (!this._ipc) {
Expand All @@ -24,12 +18,6 @@ export class CompassAtlasAuthService extends AtlasAuthService {
return this._ipc;
}

async getAuthHeaders(opts: ArgsWithSignal = {}) {
return {
Authorization: `Bearer ${await this.ipc.maybeGetToken(opts)}`,
};
}

isAuthenticated(opts?: ArgsWithSignal) {
return this.ipc.isAuthenticated(opts);
}
Expand Down
29 changes: 27 additions & 2 deletions packages/atlas-service/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { spawn } from 'child_process';
import { getAtlasConfig } from './util';
import { createIpcTrack } from '@mongodb-js/compass-telemetry';
import type { RequestInit, Response } from '@mongodb-js/devtools-proxy-support';
import { ATLAS_ADMIN_API_AUTH_ENDPOINTS } from './atlas-admin-api-auth-endpoints';

const { log } = createLogger('COMPASS-ATLAS-SERVICE');
const track = createIpcTrack();
Expand Down Expand Up @@ -182,7 +183,6 @@ export class CompassAuthService {
'isAuthenticated',
'signIn',
'signOut',
'maybeGetToken',
]);
}
this.attachOidcPluginLoggerEvents();
Expand Down Expand Up @@ -464,8 +464,33 @@ export class CompassAuthService {
mongoLogId(1_001_000_221),
'AtlasService',
'Failed to save auth state',
{ erroe: (err as Error).stack }
{ error: (err as Error).stack }
);
}
}

static isAuthenticatedAtlasAdminAPIRequest(req: Request): boolean {
const url = new URL(req.url);
return (
url.origin === this.config.atlasLogin.issuer &&
ATLAS_ADMIN_API_AUTH_ENDPOINTS.some((endpoint) => {
if (typeof endpoint === 'string') {
return url.pathname === endpoint;
}
return endpoint.test(url.pathname);
Comment on lines +472 to +480
})
);
}

static async maybeGetAuthHeaders(
req: Request
): Promise<Record<string, string> | undefined> {
if (this.isAuthenticatedAtlasAdminAPIRequest(req)) {
return {
Authorization: `Bearer ${await this.maybeGetToken({
tokenType: 'accessToken',
})}`,
};
}
}
Comment on lines +485 to +495
}
3 changes: 0 additions & 3 deletions packages/compass-settings/test/configure-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ class MockAtlasAuthService extends AtlasAuthService {
async signOut() {
return Promise.resolve();
}
async getAuthHeaders() {
return Promise.resolve({});
}
}

export default function configureStore(
Expand Down
3 changes: 0 additions & 3 deletions packages/compass-web/src/atlas-auth-service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ class AtlasCloudAuthService extends AtlasAuthService {
getUserInfo(): Promise<AtlasUserInfo> {
throw new Error('AtlasCloudAuthService.getUserInfo not implemented');
}
getAuthHeaders() {
return Promise.resolve({});
}
}

const atlasAuthService = new AtlasCloudAuthService();
Expand Down
17 changes: 17 additions & 0 deletions packages/compass/src/main/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import './disable-node-deprecations'; // Separate module so it runs first
import path from 'path';
import { EventEmitter } from 'events';
import type { BrowserWindow, Event, ProxyConfig } from 'electron';
import { protocol, net } from 'electron';
import { app, safeStorage, session } from 'electron';
import { ipcMain } from 'hadron-ipc';
import type { AutoUpdateManagerState } from './auto-update-manager';
Expand Down Expand Up @@ -197,6 +198,21 @@ class CompassApplication {
this.addExitHandler(() => {
return CompassAuthService.onExit();
});

protocol.handle('https', async (req) => {
const authHeaders =
req.headers.get('X-Compass-Auth') === 'true'
? await CompassAuthService.maybeGetAuthHeaders(req)
: {};
return net.fetch(req, {
headers: {
...req.headers,
['X-Compass-Auth']: undefined,
...authHeaders,
},
bypassCustomProtocolHandlers: true,
});
});
}

private static setupJavaScriptArguments(): void {
Expand Down Expand Up @@ -280,6 +296,7 @@ class CompassApplication {
// Accessing isEncryptionAvailable is not allowed when app is not ready on Windows
// https://github.com/electron/electron/issues/33640
await app.whenReady();
return false;
return safeStorage.isEncryptionAvailable();
},
});
Expand Down
Loading