Skip to content

Commit bd6030f

Browse files
authored
fix(NODE-6962): OIDC machine workflows use OIDCCallbacks internally (#4546)
1 parent 7ef6edd commit bd6030f

11 files changed

+107
-314
lines changed

src/cmap/auth/mongodb_oidc.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import type { HandshakeDocument } from '../connect';
44
import type { Connection } from '../connection';
55
import { type AuthContext, AuthProvider } from './auth_provider';
66
import type { MongoCredentials } from './mongo_credentials';
7-
import { AzureMachineWorkflow } from './mongodb_oidc/azure_machine_workflow';
8-
import { GCPMachineWorkflow } from './mongodb_oidc/gcp_machine_workflow';
9-
import { K8SMachineWorkflow } from './mongodb_oidc/k8s_machine_workflow';
7+
import { AutomatedCallbackWorkflow } from './mongodb_oidc/automated_callback_workflow';
8+
import { callback as azureCallback } from './mongodb_oidc/azure_machine_workflow';
9+
import { callback as gcpCallback } from './mongodb_oidc/gcp_machine_workflow';
10+
import { callback as k8sCallback } from './mongodb_oidc/k8s_machine_workflow';
1011
import { TokenCache } from './mongodb_oidc/token_cache';
11-
import { TokenMachineWorkflow } from './mongodb_oidc/token_machine_workflow';
12+
import { callback as testCallback } from './mongodb_oidc/token_machine_workflow';
1213

1314
/** Error when credentials are missing. */
1415
const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.';
@@ -78,6 +79,8 @@ export interface OIDCCallbackParams {
7879
idpInfo?: IdPInfo;
7980
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
8081
refreshToken?: string;
82+
/** The token audience for GCP and Azure. */
83+
tokenAudience?: string;
8184
}
8285

8386
/**
@@ -93,6 +96,8 @@ type EnvironmentName = 'test' | 'azure' | 'gcp' | 'k8s' | undefined;
9396

9497
/** @internal */
9598
export interface Workflow {
99+
cache: TokenCache;
100+
96101
/**
97102
* All device workflows must implement this method in order to get the access
98103
* token and then call authenticate with it.
@@ -116,10 +121,10 @@ export interface Workflow {
116121

117122
/** @internal */
118123
export const OIDC_WORKFLOWS: Map<EnvironmentName, () => Workflow> = new Map();
119-
OIDC_WORKFLOWS.set('test', () => new TokenMachineWorkflow(new TokenCache()));
120-
OIDC_WORKFLOWS.set('azure', () => new AzureMachineWorkflow(new TokenCache()));
121-
OIDC_WORKFLOWS.set('gcp', () => new GCPMachineWorkflow(new TokenCache()));
122-
OIDC_WORKFLOWS.set('k8s', () => new K8SMachineWorkflow(new TokenCache()));
124+
OIDC_WORKFLOWS.set('test', () => new AutomatedCallbackWorkflow(new TokenCache(), testCallback));
125+
OIDC_WORKFLOWS.set('azure', () => new AutomatedCallbackWorkflow(new TokenCache(), azureCallback));
126+
OIDC_WORKFLOWS.set('gcp', () => new AutomatedCallbackWorkflow(new TokenCache(), gcpCallback));
127+
OIDC_WORKFLOWS.set('k8s', () => new AutomatedCallbackWorkflow(new TokenCache(), k8sCallback));
123128

124129
/**
125130
* OIDC auth provider.

src/cmap/auth/mongodb_oidc/automated_callback_workflow.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export class AutomatedCallbackWorkflow extends CallbackWorkflow {
6666
if (credentials.username) {
6767
params.username = credentials.username;
6868
}
69+
if (credentials.mechanismProperties.TOKEN_RESOURCE) {
70+
params.tokenAudience = credentials.mechanismProperties.TOKEN_RESOURCE;
71+
}
6972
const timeout = Timeout.expires(AUTOMATED_TIMEOUT_MS);
7073
try {
7174
return await Promise.race([this.executeAndValidateCallback(params), timeout]);
Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { addAzureParams, AZURE_BASE_URL } from '../../../client-side-encryption/providers/azure';
22
import { MongoAzureError } from '../../../error';
33
import { get } from '../../../utils';
4-
import type { MongoCredentials } from '../mongo_credentials';
5-
import { type AccessToken, MachineWorkflow } from './machine_workflow';
6-
import { type TokenCache } from './token_cache';
4+
import type { OIDCCallbackFunction, OIDCCallbackParams, OIDCResponse } from '../mongodb_oidc';
75

86
/** Azure request headers. */
97
const AZURE_HEADERS = Object.freeze({ Metadata: 'true', Accept: 'application/json' });
@@ -17,39 +15,29 @@ const TOKEN_RESOURCE_MISSING_ERROR =
1715
'TOKEN_RESOURCE must be set in the auth mechanism properties when ENVIRONMENT is azure.';
1816

1917
/**
20-
* Device workflow implementation for Azure.
21-
*
22-
* @internal
18+
* The callback function to be used in the automated callback workflow.
19+
* @param params - The OIDC callback parameters.
20+
* @returns The OIDC response.
2321
*/
24-
export class AzureMachineWorkflow extends MachineWorkflow {
25-
/**
26-
* Instantiate the machine workflow.
27-
*/
28-
constructor(cache: TokenCache) {
29-
super(cache);
22+
export const callback: OIDCCallbackFunction = async (
23+
params: OIDCCallbackParams
24+
): Promise<OIDCResponse> => {
25+
const tokenAudience = params.tokenAudience;
26+
const username = params.username;
27+
if (!tokenAudience) {
28+
throw new MongoAzureError(TOKEN_RESOURCE_MISSING_ERROR);
3029
}
31-
32-
/**
33-
* Get the token from the environment.
34-
*/
35-
async getToken(credentials?: MongoCredentials): Promise<AccessToken> {
36-
const tokenAudience = credentials?.mechanismProperties.TOKEN_RESOURCE;
37-
const username = credentials?.username;
38-
if (!tokenAudience) {
39-
throw new MongoAzureError(TOKEN_RESOURCE_MISSING_ERROR);
40-
}
41-
const response = await getAzureTokenData(tokenAudience, username);
42-
if (!isEndpointResultValid(response)) {
43-
throw new MongoAzureError(ENDPOINT_RESULT_ERROR);
44-
}
45-
return response;
30+
const response = await getAzureTokenData(tokenAudience, username);
31+
if (!isEndpointResultValid(response)) {
32+
throw new MongoAzureError(ENDPOINT_RESULT_ERROR);
4633
}
47-
}
34+
return response;
35+
};
4836

4937
/**
5038
* Hit the Azure endpoint to get the token data.
5139
*/
52-
async function getAzureTokenData(tokenAudience: string, username?: string): Promise<AccessToken> {
40+
async function getAzureTokenData(tokenAudience: string, username?: string): Promise<OIDCResponse> {
5341
const url = new URL(AZURE_BASE_URL);
5442
addAzureParams(url, tokenAudience, username);
5543
const response = await get(url, {
@@ -62,8 +50,8 @@ async function getAzureTokenData(tokenAudience: string, username?: string): Prom
6250
}
6351
const result = JSON.parse(response.body);
6452
return {
65-
access_token: result.access_token,
66-
expires_in: Number(result.expires_in)
53+
accessToken: result.access_token,
54+
expiresInSeconds: Number(result.expires_in)
6755
};
6856
}
6957

@@ -77,9 +65,9 @@ function isEndpointResultValid(
7765
): token is { access_token: unknown; expires_in: unknown } {
7866
if (token == null || typeof token !== 'object') return false;
7967
return (
80-
'access_token' in token &&
81-
typeof token.access_token === 'string' &&
82-
'expires_in' in token &&
83-
typeof token.expires_in === 'number'
68+
'accessToken' in token &&
69+
typeof token.accessToken === 'string' &&
70+
'expiresInSeconds' in token &&
71+
typeof token.expiresInSeconds === 'number'
8472
);
8573
}
Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { MongoGCPError } from '../../../error';
22
import { get } from '../../../utils';
3-
import { type MongoCredentials } from '../mongo_credentials';
4-
import { type AccessToken, MachineWorkflow } from './machine_workflow';
5-
import { type TokenCache } from './token_cache';
3+
import type { OIDCCallbackFunction, OIDCCallbackParams, OIDCResponse } from '../mongodb_oidc';
64

75
/** GCP base URL. */
86
const GCP_BASE_URL =
@@ -15,30 +13,25 @@ const GCP_HEADERS = Object.freeze({ 'Metadata-Flavor': 'Google' });
1513
const TOKEN_RESOURCE_MISSING_ERROR =
1614
'TOKEN_RESOURCE must be set in the auth mechanism properties when ENVIRONMENT is gcp.';
1715

18-
export class GCPMachineWorkflow extends MachineWorkflow {
19-
/**
20-
* Instantiate the machine workflow.
21-
*/
22-
constructor(cache: TokenCache) {
23-
super(cache);
24-
}
25-
26-
/**
27-
* Get the token from the environment.
28-
*/
29-
async getToken(credentials?: MongoCredentials): Promise<AccessToken> {
30-
const tokenAudience = credentials?.mechanismProperties.TOKEN_RESOURCE;
31-
if (!tokenAudience) {
32-
throw new MongoGCPError(TOKEN_RESOURCE_MISSING_ERROR);
33-
}
34-
return await getGcpTokenData(tokenAudience);
16+
/**
17+
* The callback function to be used in the automated callback workflow.
18+
* @param params - The OIDC callback parameters.
19+
* @returns The OIDC response.
20+
*/
21+
export const callback: OIDCCallbackFunction = async (
22+
params: OIDCCallbackParams
23+
): Promise<OIDCResponse> => {
24+
const tokenAudience = params.tokenAudience;
25+
if (!tokenAudience) {
26+
throw new MongoGCPError(TOKEN_RESOURCE_MISSING_ERROR);
3527
}
36-
}
28+
return await getGcpTokenData(tokenAudience);
29+
};
3730

3831
/**
3932
* Hit the GCP endpoint to get the token data.
4033
*/
41-
async function getGcpTokenData(tokenAudience: string): Promise<AccessToken> {
34+
async function getGcpTokenData(tokenAudience: string): Promise<OIDCResponse> {
4235
const url = new URL(GCP_BASE_URL);
4336
url.searchParams.append('audience', tokenAudience);
4437
const response = await get(url, {
@@ -49,5 +42,5 @@ async function getGcpTokenData(tokenAudience: string): Promise<AccessToken> {
4942
`Status code ${response.status} returned from the GCP endpoint. Response body: ${response.body}`
5043
);
5144
}
52-
return { access_token: response.body };
45+
return { accessToken: response.body };
5346
}
Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { readFile } from 'fs/promises';
22

3-
import { type AccessToken, MachineWorkflow } from './machine_workflow';
4-
import { type TokenCache } from './token_cache';
3+
import type { OIDCCallbackFunction, OIDCResponse } from '../mongodb_oidc';
54

65
/** The fallback file name */
76
const FALLBACK_FILENAME = '/var/run/secrets/kubernetes.io/serviceaccount/token';
@@ -12,27 +11,20 @@ const AZURE_FILENAME = 'AZURE_FEDERATED_TOKEN_FILE';
1211
/** The AWS environment variable for the file name. */
1312
const AWS_FILENAME = 'AWS_WEB_IDENTITY_TOKEN_FILE';
1413

15-
export class K8SMachineWorkflow extends MachineWorkflow {
16-
/**
17-
* Instantiate the machine workflow.
18-
*/
19-
constructor(cache: TokenCache) {
20-
super(cache);
14+
/**
15+
* The callback function to be used in the automated callback workflow.
16+
* @param params - The OIDC callback parameters.
17+
* @returns The OIDC response.
18+
*/
19+
export const callback: OIDCCallbackFunction = async (): Promise<OIDCResponse> => {
20+
let filename: string;
21+
if (process.env[AZURE_FILENAME]) {
22+
filename = process.env[AZURE_FILENAME];
23+
} else if (process.env[AWS_FILENAME]) {
24+
filename = process.env[AWS_FILENAME];
25+
} else {
26+
filename = FALLBACK_FILENAME;
2127
}
22-
23-
/**
24-
* Get the token from the environment.
25-
*/
26-
async getToken(): Promise<AccessToken> {
27-
let filename: string;
28-
if (process.env[AZURE_FILENAME]) {
29-
filename = process.env[AZURE_FILENAME];
30-
} else if (process.env[AWS_FILENAME]) {
31-
filename = process.env[AWS_FILENAME];
32-
} else {
33-
filename = FALLBACK_FILENAME;
34-
}
35-
const token = await readFile(filename, 'utf8');
36-
return { access_token: token };
37-
}
38-
}
28+
const token = await readFile(filename, 'utf8');
29+
return { accessToken: token };
30+
};

0 commit comments

Comments
 (0)