Skip to content

Commit b9e43a4

Browse files
authored
chore(atlas-service): provide atlas service configuration through a preset (#4825)
1 parent 9650ec9 commit b9e43a4

File tree

9 files changed

+145
-154
lines changed

9 files changed

+145
-154
lines changed

package-lock.json

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/atlas-service/src/main.spec.ts

Lines changed: 12 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ describe('AtlasServiceMain', function () {
5050
destroy: sandbox.stub(),
5151
};
5252

53+
const defaultConfig = {
54+
atlasApiBaseUrl: 'http://example.com',
55+
atlasLogin: {
56+
issuer: 'http://example.com',
57+
clientId: '1234abcd',
58+
},
59+
authPortalUrl: 'http://example.com',
60+
};
61+
5362
const fetch = AtlasService['fetch'];
5463
const ipcMain = AtlasService['ipcMain'];
5564
const createPlugin = AtlasService['createMongoDBOIDCPlugin'];
5665
const userStore = AtlasService['atlasUserConfigStore'];
57-
const apiBaseUrl = process.env.COMPASS_ATLAS_SERVICE_BASE_URL;
58-
const issuer = process.env.COMPASS_OIDC_ISSUER;
59-
const clientId = process.env.COMPASS_CLIENT_ID;
6066

6167
beforeEach(function () {
6268
AtlasService['ipcMain'] = { handle: sandbox.stub() };
@@ -65,20 +71,13 @@ describe('AtlasServiceMain', function () {
6571
AtlasService['atlasUserConfigStore'] =
6672
mockUserConfigStore as unknown as AtlasUserConfigStore;
6773

68-
process.env.COMPASS_ATLAS_SERVICE_BASE_URL = 'http://example.com';
69-
process.env.COMPASS_OIDC_ISSUER = 'http://example.com';
70-
process.env.COMPASS_CLIENT_ID = '1234abcd';
71-
process.env.COMPASS_ATLAS_AUTH_PORTAL_URL = 'http://example.com';
74+
AtlasService['config'] = defaultConfig;
7275

7376
AtlasService['setupPlugin']();
7477
AtlasService['attachOidcPluginLoggerEvents']();
7578
});
7679

7780
afterEach(function () {
78-
process.env.COMPASS_ATLAS_SERVICE_BASE_URL = apiBaseUrl;
79-
process.env.COMPASS_OIDC_ISSUER = issuer;
80-
process.env.COMPASS_CLIENT_ID = clientId;
81-
8281
AtlasService['fetch'] = fetch;
8382
AtlasService['atlasUserConfigStore'] = userStore;
8483
AtlasService['ipcMain'] = ipcMain;
@@ -114,34 +113,6 @@ describe('AtlasServiceMain', function () {
114113
.REQUEST_TOKEN_CALLBACK
115114
).to.have.been.calledOnce;
116115
});
117-
118-
it('should throw if COMPASS_OIDC_ISSUER is not set', async function () {
119-
delete process.env.COMPASS_OIDC_ISSUER;
120-
121-
try {
122-
await AtlasService.signIn();
123-
expect.fail('Expected AtlasService.signIn() to throw');
124-
} catch (err) {
125-
expect(err).to.have.property(
126-
'message',
127-
'COMPASS_OIDC_ISSUER is required'
128-
);
129-
}
130-
});
131-
132-
it('should throw if COMPASS_CLIENT_ID is not set', async function () {
133-
delete process.env.COMPASS_CLIENT_ID;
134-
135-
try {
136-
await AtlasService.signIn();
137-
expect.fail('Expected AtlasService.signIn() to throw');
138-
} catch (err) {
139-
expect(err).to.have.property(
140-
'message',
141-
'COMPASS_CLIENT_ID is required'
142-
);
143-
}
144-
});
145116
});
146117

147118
describe('isAuthenticated', function () {
@@ -353,24 +324,6 @@ describe('AtlasServiceMain', function () {
353324
expect(err).to.have.property('message', '500 Internal Server Error');
354325
}
355326
});
356-
357-
it('should throw if COMPASS_ATLAS_SERVICE_BASE_URL is not set', async function () {
358-
delete process.env.COMPASS_ATLAS_SERVICE_BASE_URL;
359-
360-
try {
361-
await AtlasService[functionName]({
362-
userInput: 'test',
363-
collectionName: 'test.test',
364-
databaseName: 'peanut',
365-
});
366-
expect.fail('Expected AtlasService.signIn() to throw');
367-
} catch (err) {
368-
expect(err).to.have.property(
369-
'message',
370-
'No AI Query endpoint to fetch. Please set the environment variable `COMPASS_ATLAS_SERVICE_BASE_URL`'
371-
);
372-
}
373-
});
374327
});
375328
}
376329

@@ -427,7 +380,7 @@ describe('AtlasServiceMain', function () {
427380

428381
describe('init', function () {
429382
it('should try to restore service state by fetching user info', async function () {
430-
await AtlasService.init();
383+
await AtlasService.init(defaultConfig);
431384
expect(
432385
mockOidcPlugin.mongoClientOptions.authMechanismProperties
433386
.REQUEST_TOKEN_CALLBACK
@@ -476,7 +429,7 @@ describe('AtlasServiceMain', function () {
476429
const logger = new EventEmitter();
477430
AtlasService['openExternal'] = sandbox.stub().resolves();
478431
AtlasService['oidcPluginLogger'] = logger;
479-
await AtlasService.init();
432+
await AtlasService.init(defaultConfig);
480433
expect(getListenerCount(logger)).to.eq(25);
481434
// We did all preparations, reset sinon history for easier assertions
482435
sandbox.resetHistory();

packages/atlas-service/src/main.ts

Lines changed: 49 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,6 @@ function throwIfAINotEnabled(atlasService: typeof AtlasService) {
9797
}
9898
}
9999

100-
function throwIfRequiredEnvNotSet(atlasService: typeof AtlasService) {
101-
// These class properties will throw on access if missing
102-
return void (atlasService['clientId'] && atlasService['issuer']);
103-
}
104-
105100
const AI_MAX_REQUEST_SIZE = 10000;
106101

107102
const AI_MIN_SAMPLE_DOCUMENTS = 1;
@@ -119,6 +114,15 @@ export function getTrackingUserInfo(userInfo: AtlasUserInfo) {
119114
};
120115
}
121116

117+
export type AtlasServiceConfig = {
118+
atlasApiBaseUrl: string;
119+
atlasLogin: {
120+
clientId: string;
121+
issuer: string;
122+
};
123+
authPortalUrl: string;
124+
};
125+
122126
export class AtlasService {
123127
private constructor() {
124128
// singleton
@@ -153,46 +157,7 @@ export class AtlasService {
153157

154158
private static ipcMain: Pick<typeof ipcMain, 'handle'> = ipcMain;
155159

156-
private static get clientId() {
157-
const clientId =
158-
process.env.COMPASS_CLIENT_ID_OVERRIDE || process.env.COMPASS_CLIENT_ID;
159-
if (!clientId) {
160-
throw new Error('COMPASS_CLIENT_ID is required');
161-
}
162-
return clientId;
163-
}
164-
165-
private static get issuer() {
166-
const issuer =
167-
process.env.COMPASS_OIDC_ISSUER_OVERRIDE ||
168-
process.env.COMPASS_OIDC_ISSUER;
169-
if (!issuer) {
170-
throw new Error('COMPASS_OIDC_ISSUER is required');
171-
}
172-
return issuer;
173-
}
174-
175-
private static get apiBaseUrl() {
176-
const apiBaseUrl =
177-
process.env.COMPASS_ATLAS_SERVICE_BASE_URL_OVERRIDE ||
178-
process.env.COMPASS_ATLAS_SERVICE_BASE_URL;
179-
if (!apiBaseUrl) {
180-
throw new Error(
181-
'No AI Query endpoint to fetch. Please set the environment variable `COMPASS_ATLAS_SERVICE_BASE_URL`'
182-
);
183-
}
184-
return apiBaseUrl;
185-
}
186-
187-
private static get authPortalUrl() {
188-
const authPortalUrl =
189-
process.env.COMPASS_ATLAS_AUTH_PORTAL_URL_OVERRIDE ||
190-
process.env.COMPASS_ATLAS_AUTH_PORTAL_URL;
191-
if (!authPortalUrl) {
192-
throw new Error('COMPASS_ATLAS_AUTH_PORTAL_URL is required');
193-
}
194-
return authPortalUrl;
195-
}
160+
private static config: AtlasServiceConfig;
196161

197162
private static openExternal(...args: Parameters<typeof shell.openExternal>) {
198163
return shell?.openExternal(...args);
@@ -215,7 +180,7 @@ export class AtlasService {
215180
if (data.result === 'redirecting') {
216181
const { res, status, location } = data;
217182
res.statusCode = status;
218-
const redirectUrl = new URL(this.authPortalUrl);
183+
const redirectUrl = new URL(this.config.authPortalUrl);
219184
redirectUrl.searchParams.set('fromURI', location);
220185
res.setHeader('Location', redirectUrl.toString());
221186
res.end();
@@ -238,7 +203,8 @@ export class AtlasService {
238203
);
239204
}
240205

241-
static init(): Promise<void> {
206+
static init(config: AtlasServiceConfig): Promise<void> {
207+
this.config = config;
242208
return (this.initPromise ??= (async () => {
243209
ipcExpose(
244210
'AtlasService',
@@ -259,7 +225,8 @@ export class AtlasService {
259225
log.info(
260226
mongoLogId(1_001_000_210),
261227
'AtlasService',
262-
'Atlas service initialized'
228+
'Atlas service initialized',
229+
{ config: this.config }
263230
);
264231
const serializedState = await this.secretStore.getItem(SECRET_STORE_KEY);
265232
this.setupPlugin(serializedState);
@@ -286,7 +253,6 @@ export class AtlasService {
286253
}: { signal?: AbortSignal } = {}) {
287254
throwIfAborted(signal);
288255
throwIfNetworkTrafficDisabled();
289-
throwIfRequiredEnvNotSet(this);
290256

291257
if (!this.plugin) {
292258
throw new Error(
@@ -295,7 +261,10 @@ export class AtlasService {
295261
}
296262

297263
return this.plugin.mongoClientOptions.authMechanismProperties.REQUEST_TOKEN_CALLBACK(
298-
{ clientId: this.clientId, issuer: this.issuer },
264+
{
265+
clientId: this.config.atlasLogin.clientId,
266+
issuer: this.config.atlasLogin.issuer,
267+
},
299268
{
300269
// Required driver specific stuff
301270
version: 0,
@@ -348,7 +317,6 @@ export class AtlasService {
348317
this.signInPromise = (async () => {
349318
throwIfAborted(signal);
350319
throwIfNetworkTrafficDisabled();
351-
throwIfRequiredEnvNotSet(this);
352320

353321
log.info(mongoLogId(1_001_000_218), 'AtlasService', 'Starting sign in');
354322

@@ -411,9 +379,9 @@ export class AtlasService {
411379
this.currentUser = null;
412380
this.oidcPluginLogger.emit('atlas-service-signed-out');
413381
// Open Atlas sign out page to end the browser session created for sign in
414-
void this.openExternal(
415-
'https://account.mongodb.com/account/login?signedOut=true'
416-
);
382+
const signOutUrl = new URL(this.config.authPortalUrl);
383+
signOutUrl.searchParams.set('signedOut', 'true');
384+
void this.openExternal(signOutUrl.toString());
417385
track('Atlas Sign Out', getTrackingUserInfo(userInfo));
418386
}
419387

@@ -441,18 +409,20 @@ export class AtlasService {
441409
}: { signal?: AbortSignal } = {}): Promise<AtlasUserInfo> {
442410
throwIfAborted(signal);
443411
throwIfNetworkTrafficDisabled();
444-
throwIfRequiredEnvNotSet(this);
445412

446413
this.currentUser ??= await (async () => {
447414
const token = await this.maybeGetToken({ signal });
448415

449-
const res = await this.fetch(`${this.issuer}/v1/userinfo`, {
450-
headers: {
451-
Authorization: `Bearer ${token ?? ''}`,
452-
Accept: 'application/json',
453-
},
454-
signal: signal as NodeFetchAbortSignal | undefined,
455-
});
416+
const res = await this.fetch(
417+
`${this.config.atlasLogin.issuer}/v1/userinfo`,
418+
{
419+
headers: {
420+
Authorization: `Bearer ${token ?? ''}`,
421+
Accept: 'application/json',
422+
},
423+
signal: signal as NodeFetchAbortSignal | undefined,
424+
}
425+
);
456426

457427
await throwIfNotOk(res);
458428

@@ -495,10 +465,9 @@ export class AtlasService {
495465
} = {}) {
496466
throwIfAborted(signal);
497467
throwIfNetworkTrafficDisabled();
498-
throwIfRequiredEnvNotSet(this);
499468

500-
const url = new URL(`${this.issuer}/v1/introspect`);
501-
url.searchParams.set('client_id', this.clientId);
469+
const url = new URL(`${this.config.atlasLogin.issuer}/v1/introspect`);
470+
url.searchParams.set('client_id', this.config.atlasLogin.clientId);
502471

503472
tokenType ??= 'accessToken';
504473

@@ -530,10 +499,9 @@ export class AtlasService {
530499
} = {}): Promise<void> {
531500
throwIfAborted(signal);
532501
throwIfNetworkTrafficDisabled();
533-
throwIfRequiredEnvNotSet(this);
534502

535-
const url = new URL(`${this.issuer}/v1/revoke`);
536-
url.searchParams.set('client_id', this.clientId);
503+
const url = new URL(`${this.config.atlasLogin.issuer}/v1/revoke`);
504+
url.searchParams.set('client_id', this.config.atlasLogin.clientId);
537505

538506
tokenType ??= 'accessToken';
539507

@@ -574,7 +542,6 @@ export class AtlasService {
574542
throwIfAborted(signal);
575543
throwIfNetworkTrafficDisabled();
576544
throwIfAINotEnabled(this);
577-
throwIfRequiredEnvNotSet(this);
578545

579546
let msgBody = JSON.stringify({
580547
userInput,
@@ -605,7 +572,7 @@ export class AtlasService {
605572
const token = await this.maybeGetToken({ signal });
606573

607574
const res = await this.fetch(
608-
`${this.apiBaseUrl}/ai/api/v1/mql-aggregation`,
575+
`${this.config.atlasApiBaseUrl}/ai/api/v1/mql-aggregation`,
609576
{
610577
signal: signal as NodeFetchAbortSignal | undefined,
611578
method: 'POST',
@@ -644,7 +611,6 @@ export class AtlasService {
644611
throwIfAborted(signal);
645612
throwIfNetworkTrafficDisabled();
646613
throwIfAINotEnabled(this);
647-
throwIfRequiredEnvNotSet(this);
648614

649615
let msgBody = JSON.stringify({
650616
userInput,
@@ -674,15 +640,18 @@ export class AtlasService {
674640

675641
const token = await this.maybeGetToken({ signal });
676642

677-
const res = await this.fetch(`${this.apiBaseUrl}/ai/api/v1/mql-query`, {
678-
signal: signal as NodeFetchAbortSignal | undefined,
679-
method: 'POST',
680-
headers: {
681-
Authorization: `Bearer ${token ?? ''}`,
682-
'Content-Type': 'application/json',
683-
},
684-
body: msgBody,
685-
});
643+
const res = await this.fetch(
644+
`${this.config.atlasApiBaseUrl}/ai/api/v1/mql-query`,
645+
{
646+
signal: signal as NodeFetchAbortSignal | undefined,
647+
method: 'POST',
648+
headers: {
649+
Authorization: `Bearer ${token ?? ''}`,
650+
'Content-Type': 'application/json',
651+
},
652+
body: msgBody,
653+
}
654+
);
686655

687656
await throwIfNotOk(res);
688657

0 commit comments

Comments
 (0)