mongodb 6.6.2-dev.20240529.sha.d3031a5 → 6.7.0-dev.20240530.sha.f56938f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client-side-encryption/providers/azure.js +21 -6
- package/lib/client-side-encryption/providers/azure.js.map +1 -1
- package/lib/cmap/auth/mongo_credentials.js +24 -16
- package/lib/cmap/auth/mongo_credentials.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/automated_callback_workflow.js +78 -0
- package/lib/cmap/auth/mongodb_oidc/automated_callback_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +74 -0
- package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +74 -135
- package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/command_builders.js +45 -0
- package/lib/cmap/auth/mongodb_oidc/command_builders.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +46 -0
- package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/human_callback_workflow.js +122 -0
- package/lib/cmap/auth/mongodb_oidc/human_callback_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/machine_workflow.js +107 -0
- package/lib/cmap/auth/mongodb_oidc/machine_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/token_cache.js +52 -0
- package/lib/cmap/auth/mongodb_oidc/token_cache.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +34 -0
- package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc.js +26 -24
- package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
- package/lib/cmap/auth/providers.js +0 -1
- package/lib/cmap/auth/providers.js.map +1 -1
- package/lib/cmap/connect.js +4 -4
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +1 -1
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/connection_string.js +3 -0
- package/lib/connection_string.js.map +1 -1
- package/lib/error.js +57 -2
- package/lib/error.js.map +1 -1
- package/lib/index.js +5 -3
- package/lib/index.js.map +1 -1
- package/lib/mongo_client.js +1 -1
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_client_auth_providers.js +34 -4
- package/lib/mongo_client_auth_providers.js.map +1 -1
- package/lib/utils.js +32 -2
- package/lib/utils.js.map +1 -1
- package/mongodb.d.ts +105 -25
- package/package.json +5 -3
- package/src/client-side-encryption/providers/azure.ts +21 -10
- package/src/cmap/auth/mongo_credentials.ts +41 -34
- package/src/cmap/auth/mongodb_oidc/automated_callback_workflow.ts +82 -0
- package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +85 -0
- package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +96 -204
- package/src/cmap/auth/mongodb_oidc/command_builders.ts +54 -0
- package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +53 -0
- package/src/cmap/auth/mongodb_oidc/human_callback_workflow.ts +142 -0
- package/src/cmap/auth/mongodb_oidc/machine_workflow.ts +137 -0
- package/src/cmap/auth/mongodb_oidc/token_cache.ts +62 -0
- package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +34 -0
- package/src/cmap/auth/mongodb_oidc.ts +79 -49
- package/src/cmap/auth/providers.ts +0 -1
- package/src/cmap/connect.ts +14 -4
- package/src/cmap/connection.ts +1 -0
- package/src/cmap/connection_pool.ts +2 -1
- package/src/connection_string.ts +3 -0
- package/src/error.ts +58 -1
- package/src/index.ts +8 -4
- package/src/mongo_client.ts +4 -1
- package/src/mongo_client_auth_providers.ts +44 -6
- package/src/utils.ts +33 -0
- package/lib/client-side-encryption/providers/utils.js +0 -35
- package/lib/client-side-encryption/providers/utils.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +0 -30
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/azure_service_workflow.js +0 -73
- package/lib/cmap/auth/mongodb_oidc/azure_service_workflow.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/azure_token_cache.js +0 -49
- package/lib/cmap/auth/mongodb_oidc/azure_token_cache.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/cache.js +0 -55
- package/lib/cmap/auth/mongodb_oidc/cache.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js +0 -90
- package/lib/cmap/auth/mongodb_oidc/callback_lock_cache.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js +0 -43
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +0 -1
- package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +0 -62
- package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +0 -1
- package/src/client-side-encryption/providers/utils.ts +0 -37
- package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +0 -29
- package/src/cmap/auth/mongodb_oidc/azure_service_workflow.ts +0 -86
- package/src/cmap/auth/mongodb_oidc/azure_token_cache.ts +0 -51
- package/src/cmap/auth/mongodb_oidc/cache.ts +0 -63
- package/src/cmap/auth/mongodb_oidc/callback_lock_cache.ts +0 -115
- package/src/cmap/auth/mongodb_oidc/service_workflow.ts +0 -49
- package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +0 -77
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { type Document } from 'bson';
|
|
2
|
+
import { setTimeout } from 'timers/promises';
|
|
3
|
+
|
|
4
|
+
import { ns } from '../../../utils';
|
|
5
|
+
import type { Connection } from '../../connection';
|
|
6
|
+
import type { MongoCredentials } from '../mongo_credentials';
|
|
7
|
+
import type { Workflow } from '../mongodb_oidc';
|
|
8
|
+
import { finishCommandDocument } from './command_builders';
|
|
9
|
+
import { type TokenCache } from './token_cache';
|
|
10
|
+
|
|
11
|
+
/** The time to throttle callback calls. */
|
|
12
|
+
const THROTTLE_MS = 100;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The access token format.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export interface AccessToken {
|
|
19
|
+
access_token: string;
|
|
20
|
+
expires_in?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** @internal */
|
|
24
|
+
export type OIDCTokenFunction = (credentials: MongoCredentials) => Promise<AccessToken>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Common behaviour for OIDC machine workflows.
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export abstract class MachineWorkflow implements Workflow {
|
|
31
|
+
cache: TokenCache;
|
|
32
|
+
callback: OIDCTokenFunction;
|
|
33
|
+
lastExecutionTime: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Instantiate the machine workflow.
|
|
37
|
+
*/
|
|
38
|
+
constructor(cache: TokenCache) {
|
|
39
|
+
this.cache = cache;
|
|
40
|
+
this.callback = this.withLock(this.getToken.bind(this));
|
|
41
|
+
this.lastExecutionTime = Date.now() - THROTTLE_MS;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Execute the workflow. Gets the token from the subclass implementation.
|
|
46
|
+
*/
|
|
47
|
+
async execute(connection: Connection, credentials: MongoCredentials): Promise<void> {
|
|
48
|
+
const token = await this.getTokenFromCacheOrEnv(connection, credentials);
|
|
49
|
+
const command = finishCommandDocument(token);
|
|
50
|
+
await connection.command(ns(credentials.source), command, undefined);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Reauthenticate on a machine workflow just grabs the token again since the server
|
|
55
|
+
* has said the current access token is invalid or expired.
|
|
56
|
+
*/
|
|
57
|
+
async reauthenticate(connection: Connection, credentials: MongoCredentials): Promise<void> {
|
|
58
|
+
if (this.cache.hasAccessToken) {
|
|
59
|
+
// Reauthentication implies the token has expired.
|
|
60
|
+
if (connection.accessToken === this.cache.getAccessToken()) {
|
|
61
|
+
// If connection's access token is the same as the cache's, remove
|
|
62
|
+
// the token from the cache and connection.
|
|
63
|
+
this.cache.removeAccessToken();
|
|
64
|
+
delete connection.accessToken;
|
|
65
|
+
} else {
|
|
66
|
+
// If the connection's access token is different from the cache's, set
|
|
67
|
+
// the cache's token on the connection and do not remove from the
|
|
68
|
+
// cache.
|
|
69
|
+
connection.accessToken = this.cache.getAccessToken();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await this.execute(connection, credentials);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the document to add for speculative authentication.
|
|
77
|
+
*/
|
|
78
|
+
async speculativeAuth(connection: Connection, credentials: MongoCredentials): Promise<Document> {
|
|
79
|
+
// The spec states only cached access tokens can use speculative auth.
|
|
80
|
+
if (!this.cache.hasAccessToken) {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
const token = await this.getTokenFromCacheOrEnv(connection, credentials);
|
|
84
|
+
const document = finishCommandDocument(token);
|
|
85
|
+
document.db = credentials.source;
|
|
86
|
+
return { speculativeAuthenticate: document };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Get the token from the cache or environment.
|
|
91
|
+
*/
|
|
92
|
+
private async getTokenFromCacheOrEnv(
|
|
93
|
+
connection: Connection,
|
|
94
|
+
credentials: MongoCredentials
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
if (this.cache.hasAccessToken) {
|
|
97
|
+
return this.cache.getAccessToken();
|
|
98
|
+
} else {
|
|
99
|
+
const token = await this.callback(credentials);
|
|
100
|
+
this.cache.put({ accessToken: token.access_token, expiresInSeconds: token.expires_in });
|
|
101
|
+
// Put the access token on the connection as well.
|
|
102
|
+
connection.accessToken = token.access_token;
|
|
103
|
+
return token.access_token;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Ensure the callback is only executed one at a time, and throttled to
|
|
109
|
+
* only once per 100ms.
|
|
110
|
+
*/
|
|
111
|
+
private withLock(callback: OIDCTokenFunction): OIDCTokenFunction {
|
|
112
|
+
let lock: Promise<any> = Promise.resolve();
|
|
113
|
+
return async (credentials: MongoCredentials): Promise<AccessToken> => {
|
|
114
|
+
// We do this to ensure that we would never return the result of the
|
|
115
|
+
// previous lock, only the current callback's value would get returned.
|
|
116
|
+
await lock;
|
|
117
|
+
lock = lock
|
|
118
|
+
// eslint-disable-next-line github/no-then
|
|
119
|
+
.catch(() => null)
|
|
120
|
+
// eslint-disable-next-line github/no-then
|
|
121
|
+
.then(async () => {
|
|
122
|
+
const difference = Date.now() - this.lastExecutionTime;
|
|
123
|
+
if (difference <= THROTTLE_MS) {
|
|
124
|
+
await setTimeout(THROTTLE_MS - difference);
|
|
125
|
+
}
|
|
126
|
+
this.lastExecutionTime = Date.now();
|
|
127
|
+
return await callback(credentials);
|
|
128
|
+
});
|
|
129
|
+
return await lock;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get the token from the environment or endpoint.
|
|
135
|
+
*/
|
|
136
|
+
abstract getToken(credentials: MongoCredentials): Promise<AccessToken>;
|
|
137
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MongoDriverError } from '../../../error';
|
|
2
|
+
import type { IdPInfo, OIDCResponse } from '../mongodb_oidc';
|
|
3
|
+
|
|
4
|
+
class MongoOIDCError extends MongoDriverError {}
|
|
5
|
+
|
|
6
|
+
/** @internal */
|
|
7
|
+
export class TokenCache {
|
|
8
|
+
private accessToken?: string;
|
|
9
|
+
private refreshToken?: string;
|
|
10
|
+
private idpInfo?: IdPInfo;
|
|
11
|
+
private expiresInSeconds?: number;
|
|
12
|
+
|
|
13
|
+
get hasAccessToken(): boolean {
|
|
14
|
+
return !!this.accessToken;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get hasRefreshToken(): boolean {
|
|
18
|
+
return !!this.refreshToken;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get hasIdpInfo(): boolean {
|
|
22
|
+
return !!this.idpInfo;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
getAccessToken(): string {
|
|
26
|
+
if (!this.accessToken) {
|
|
27
|
+
throw new MongoOIDCError('Attempted to get an access token when none exists.');
|
|
28
|
+
}
|
|
29
|
+
return this.accessToken;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getRefreshToken(): string {
|
|
33
|
+
if (!this.refreshToken) {
|
|
34
|
+
throw new MongoOIDCError('Attempted to get a refresh token when none exists.');
|
|
35
|
+
}
|
|
36
|
+
return this.refreshToken;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getIdpInfo(): IdPInfo {
|
|
40
|
+
if (!this.idpInfo) {
|
|
41
|
+
throw new MongoOIDCError('Attempted to get IDP information when none exists.');
|
|
42
|
+
}
|
|
43
|
+
return this.idpInfo;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
put(response: OIDCResponse, idpInfo?: IdPInfo) {
|
|
47
|
+
this.accessToken = response.accessToken;
|
|
48
|
+
this.refreshToken = response.refreshToken;
|
|
49
|
+
this.expiresInSeconds = response.expiresInSeconds;
|
|
50
|
+
if (idpInfo) {
|
|
51
|
+
this.idpInfo = idpInfo;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
removeAccessToken() {
|
|
56
|
+
this.accessToken = undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
removeRefreshToken() {
|
|
60
|
+
this.refreshToken = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
import { MongoAWSError } from '../../../error';
|
|
4
|
+
import { type AccessToken, MachineWorkflow } from './machine_workflow';
|
|
5
|
+
import { type TokenCache } from './token_cache';
|
|
6
|
+
|
|
7
|
+
/** Error for when the token is missing in the environment. */
|
|
8
|
+
const TOKEN_MISSING_ERROR = 'OIDC_TOKEN_FILE must be set in the environment.';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Device workflow implementation for AWS.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export class TokenMachineWorkflow extends MachineWorkflow {
|
|
16
|
+
/**
|
|
17
|
+
* Instantiate the machine workflow.
|
|
18
|
+
*/
|
|
19
|
+
constructor(cache: TokenCache) {
|
|
20
|
+
super(cache);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get the token from the environment.
|
|
25
|
+
*/
|
|
26
|
+
async getToken(): Promise<AccessToken> {
|
|
27
|
+
const tokenFile = process.env.OIDC_TOKEN_FILE;
|
|
28
|
+
if (!tokenFile) {
|
|
29
|
+
throw new MongoAWSError(TOKEN_MISSING_ERROR);
|
|
30
|
+
}
|
|
31
|
+
const token = await fs.promises.readFile(tokenFile, 'utf8');
|
|
32
|
+
return { access_token: token };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -5,64 +5,93 @@ import type { HandshakeDocument } from '../connect';
|
|
|
5
5
|
import type { Connection } from '../connection';
|
|
6
6
|
import { type AuthContext, AuthProvider } from './auth_provider';
|
|
7
7
|
import type { MongoCredentials } from './mongo_credentials';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { AzureMachineWorkflow } from './mongodb_oidc/azure_machine_workflow';
|
|
9
|
+
import { GCPMachineWorkflow } from './mongodb_oidc/gcp_machine_workflow';
|
|
10
|
+
import { TokenCache } from './mongodb_oidc/token_cache';
|
|
11
|
+
import { TokenMachineWorkflow } from './mongodb_oidc/token_machine_workflow';
|
|
11
12
|
|
|
12
13
|
/** Error when credentials are missing. */
|
|
13
14
|
const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.';
|
|
14
15
|
|
|
15
16
|
/**
|
|
17
|
+
* The information returned by the server on the IDP server.
|
|
16
18
|
* @public
|
|
17
|
-
* @experimental
|
|
18
19
|
*/
|
|
19
|
-
export interface
|
|
20
|
+
export interface IdPInfo {
|
|
21
|
+
/**
|
|
22
|
+
* A URL which describes the Authentication Server. This identifier should
|
|
23
|
+
* be the iss of provided access tokens, and be viable for RFC8414 metadata
|
|
24
|
+
* discovery and RFC9207 identification.
|
|
25
|
+
*/
|
|
20
26
|
issuer: string;
|
|
27
|
+
/** A unique client ID for this OIDC client. */
|
|
21
28
|
clientId: string;
|
|
29
|
+
/** A list of additional scopes to request from IdP. */
|
|
22
30
|
requestScopes?: string[];
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
/**
|
|
34
|
+
* The response from the IdP server with the access token and
|
|
35
|
+
* optional expiration time and refresh token.
|
|
26
36
|
* @public
|
|
27
|
-
* @experimental
|
|
28
37
|
*/
|
|
29
38
|
export interface IdPServerResponse {
|
|
39
|
+
/** The OIDC access token. */
|
|
30
40
|
accessToken: string;
|
|
41
|
+
/** The time when the access token expires. For future use. */
|
|
31
42
|
expiresInSeconds?: number;
|
|
43
|
+
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
32
44
|
refreshToken?: string;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
/**
|
|
48
|
+
* The response required to be returned from the machine or
|
|
49
|
+
* human callback workflows' callback.
|
|
36
50
|
* @public
|
|
37
|
-
* @experimental
|
|
38
51
|
*/
|
|
39
|
-
export interface
|
|
52
|
+
export interface OIDCResponse {
|
|
53
|
+
/** The OIDC access token. */
|
|
54
|
+
accessToken: string;
|
|
55
|
+
/** The time when the access token expires. For future use. */
|
|
56
|
+
expiresInSeconds?: number;
|
|
57
|
+
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
40
58
|
refreshToken?: string;
|
|
41
|
-
timeoutSeconds?: number;
|
|
42
|
-
timeoutContext?: AbortSignal;
|
|
43
|
-
version: number;
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
/**
|
|
62
|
+
* The parameters that the driver provides to the user supplied
|
|
63
|
+
* human or machine callback.
|
|
64
|
+
*
|
|
65
|
+
* The version number is used to communicate callback API changes that are not breaking but that
|
|
66
|
+
* users may want to know about and review their implementation. Users may wish to check the version
|
|
67
|
+
* number and throw an error if their expected version number and the one provided do not match.
|
|
47
68
|
* @public
|
|
48
|
-
* @experimental
|
|
49
69
|
*/
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
70
|
+
export interface OIDCCallbackParams {
|
|
71
|
+
/** Optional username. */
|
|
72
|
+
username?: string;
|
|
73
|
+
/** The context in which to timeout the OIDC callback. */
|
|
74
|
+
timeoutContext: AbortSignal;
|
|
75
|
+
/** The current OIDC API version. */
|
|
76
|
+
version: 1;
|
|
77
|
+
/** The IdP information returned from the server. */
|
|
78
|
+
idpInfo?: IdPInfo;
|
|
79
|
+
/** The refresh token, if applicable, to be used by the callback to request a new token from the issuer. */
|
|
80
|
+
refreshToken?: string;
|
|
81
|
+
}
|
|
54
82
|
|
|
55
83
|
/**
|
|
84
|
+
* The signature of the human or machine callback functions.
|
|
56
85
|
* @public
|
|
57
|
-
* @experimental
|
|
58
86
|
*/
|
|
59
|
-
export type
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
export type OIDCCallbackFunction = (params: OIDCCallbackParams) => Promise<OIDCResponse>;
|
|
88
|
+
|
|
89
|
+
/** The current version of OIDC implementation. */
|
|
90
|
+
export const OIDC_VERSION = 1;
|
|
63
91
|
|
|
64
|
-
type
|
|
92
|
+
type EnvironmentName = 'test' | 'azure' | 'gcp' | undefined;
|
|
65
93
|
|
|
94
|
+
/** @internal */
|
|
66
95
|
export interface Workflow {
|
|
67
96
|
/**
|
|
68
97
|
* All device workflows must implement this method in order to get the access
|
|
@@ -71,32 +100,41 @@ export interface Workflow {
|
|
|
71
100
|
execute(
|
|
72
101
|
connection: Connection,
|
|
73
102
|
credentials: MongoCredentials,
|
|
74
|
-
reauthenticating: boolean,
|
|
75
103
|
response?: Document
|
|
76
|
-
): Promise<
|
|
104
|
+
): Promise<void>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Each workflow should specify the correct custom behaviour for reauthentication.
|
|
108
|
+
*/
|
|
109
|
+
reauthenticate(connection: Connection, credentials: MongoCredentials): Promise<void>;
|
|
77
110
|
|
|
78
111
|
/**
|
|
79
112
|
* Get the document to add for speculative authentication.
|
|
80
113
|
*/
|
|
81
|
-
speculativeAuth(credentials: MongoCredentials): Promise<Document>;
|
|
114
|
+
speculativeAuth(connection: Connection, credentials: MongoCredentials): Promise<Document>;
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
/** @internal */
|
|
85
|
-
export const OIDC_WORKFLOWS: Map<
|
|
86
|
-
OIDC_WORKFLOWS.set('
|
|
87
|
-
OIDC_WORKFLOWS.set('
|
|
88
|
-
OIDC_WORKFLOWS.set('
|
|
118
|
+
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()));
|
|
89
122
|
|
|
90
123
|
/**
|
|
91
124
|
* OIDC auth provider.
|
|
92
|
-
* @experimental
|
|
93
125
|
*/
|
|
94
126
|
export class MongoDBOIDC extends AuthProvider {
|
|
127
|
+
workflow: Workflow;
|
|
128
|
+
|
|
95
129
|
/**
|
|
96
130
|
* Instantiate the auth provider.
|
|
97
131
|
*/
|
|
98
|
-
constructor() {
|
|
132
|
+
constructor(workflow?: Workflow) {
|
|
99
133
|
super();
|
|
134
|
+
if (!workflow) {
|
|
135
|
+
throw new MongoInvalidArgumentError('No workflow provided to the OIDC auth provider.');
|
|
136
|
+
}
|
|
137
|
+
this.workflow = workflow;
|
|
100
138
|
}
|
|
101
139
|
|
|
102
140
|
/**
|
|
@@ -104,9 +142,15 @@ export class MongoDBOIDC extends AuthProvider {
|
|
|
104
142
|
*/
|
|
105
143
|
override async auth(authContext: AuthContext): Promise<void> {
|
|
106
144
|
const { connection, reauthenticating, response } = authContext;
|
|
145
|
+
if (response?.speculativeAuthenticate?.done) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
107
148
|
const credentials = getCredentials(authContext);
|
|
108
|
-
|
|
109
|
-
|
|
149
|
+
if (reauthenticating) {
|
|
150
|
+
await this.workflow.reauthenticate(connection, credentials);
|
|
151
|
+
} else {
|
|
152
|
+
await this.workflow.execute(connection, credentials, response);
|
|
153
|
+
}
|
|
110
154
|
}
|
|
111
155
|
|
|
112
156
|
/**
|
|
@@ -116,9 +160,9 @@ export class MongoDBOIDC extends AuthProvider {
|
|
|
116
160
|
handshakeDoc: HandshakeDocument,
|
|
117
161
|
authContext: AuthContext
|
|
118
162
|
): Promise<HandshakeDocument> {
|
|
163
|
+
const { connection } = authContext;
|
|
119
164
|
const credentials = getCredentials(authContext);
|
|
120
|
-
const
|
|
121
|
-
const result = await workflow.speculativeAuth(credentials);
|
|
165
|
+
const result = await this.workflow.speculativeAuth(connection, credentials);
|
|
122
166
|
return { ...handshakeDoc, ...result };
|
|
123
167
|
}
|
|
124
168
|
}
|
|
@@ -133,17 +177,3 @@ function getCredentials(authContext: AuthContext): MongoCredentials {
|
|
|
133
177
|
}
|
|
134
178
|
return credentials;
|
|
135
179
|
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Gets either a device workflow or callback workflow.
|
|
139
|
-
*/
|
|
140
|
-
function getWorkflow(credentials: MongoCredentials): Workflow {
|
|
141
|
-
const providerName = credentials.mechanismProperties.PROVIDER_NAME;
|
|
142
|
-
const workflow = OIDC_WORKFLOWS.get(providerName || 'callback');
|
|
143
|
-
if (!workflow) {
|
|
144
|
-
throw new MongoInvalidArgumentError(
|
|
145
|
-
`Could not load workflow for provider ${credentials.mechanismProperties.PROVIDER_NAME}`
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
return workflow;
|
|
149
|
-
}
|
package/src/cmap/connect.ts
CHANGED
|
@@ -91,7 +91,10 @@ export async function performInitialHandshake(
|
|
|
91
91
|
if (credentials) {
|
|
92
92
|
if (
|
|
93
93
|
!(credentials.mechanism === AuthMechanism.MONGODB_DEFAULT) &&
|
|
94
|
-
!options.authProviders.getOrCreateProvider(
|
|
94
|
+
!options.authProviders.getOrCreateProvider(
|
|
95
|
+
credentials.mechanism,
|
|
96
|
+
credentials.mechanismProperties
|
|
97
|
+
)
|
|
95
98
|
) {
|
|
96
99
|
throw new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`);
|
|
97
100
|
}
|
|
@@ -146,7 +149,10 @@ export async function performInitialHandshake(
|
|
|
146
149
|
authContext.response = response;
|
|
147
150
|
|
|
148
151
|
const resolvedCredentials = credentials.resolveAuthMechanism(response);
|
|
149
|
-
const provider = options.authProviders.getOrCreateProvider(
|
|
152
|
+
const provider = options.authProviders.getOrCreateProvider(
|
|
153
|
+
resolvedCredentials.mechanism,
|
|
154
|
+
resolvedCredentials.mechanismProperties
|
|
155
|
+
);
|
|
150
156
|
if (!provider) {
|
|
151
157
|
throw new MongoInvalidArgumentError(
|
|
152
158
|
`No AuthProvider for ${resolvedCredentials.mechanism} defined.`
|
|
@@ -218,7 +224,8 @@ export async function prepareHandshakeDocument(
|
|
|
218
224
|
handshakeDoc.saslSupportedMechs = `${credentials.source}.${credentials.username}`;
|
|
219
225
|
|
|
220
226
|
const provider = authContext.options.authProviders.getOrCreateProvider(
|
|
221
|
-
AuthMechanism.MONGODB_SCRAM_SHA256
|
|
227
|
+
AuthMechanism.MONGODB_SCRAM_SHA256,
|
|
228
|
+
credentials.mechanismProperties
|
|
222
229
|
);
|
|
223
230
|
if (!provider) {
|
|
224
231
|
// This auth mechanism is always present.
|
|
@@ -228,7 +235,10 @@ export async function prepareHandshakeDocument(
|
|
|
228
235
|
}
|
|
229
236
|
return await provider.prepare(handshakeDoc, authContext);
|
|
230
237
|
}
|
|
231
|
-
const provider = authContext.options.authProviders.getOrCreateProvider(
|
|
238
|
+
const provider = authContext.options.authProviders.getOrCreateProvider(
|
|
239
|
+
credentials.mechanism,
|
|
240
|
+
credentials.mechanismProperties
|
|
241
|
+
);
|
|
232
242
|
if (!provider) {
|
|
233
243
|
throw new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`);
|
|
234
244
|
}
|
package/src/cmap/connection.ts
CHANGED
|
@@ -174,6 +174,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
|
|
|
174
174
|
public authContext?: AuthContext;
|
|
175
175
|
public delayedTimeoutId: NodeJS.Timeout | null = null;
|
|
176
176
|
public generation: number;
|
|
177
|
+
public accessToken?: string;
|
|
177
178
|
public readonly description: Readonly<StreamDescription>;
|
|
178
179
|
/**
|
|
179
180
|
* Represents if the connection has been established:
|
|
@@ -551,7 +551,8 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
|
|
|
551
551
|
|
|
552
552
|
const resolvedCredentials = credentials.resolveAuthMechanism(connection.hello);
|
|
553
553
|
const provider = this[kServer].topology.client.s.authProviders.getOrCreateProvider(
|
|
554
|
-
resolvedCredentials.mechanism
|
|
554
|
+
resolvedCredentials.mechanism,
|
|
555
|
+
resolvedCredentials.mechanismProperties
|
|
555
556
|
);
|
|
556
557
|
|
|
557
558
|
if (!provider) {
|
package/src/connection_string.ts
CHANGED
|
@@ -698,6 +698,9 @@ export const OPTIONS = {
|
|
|
698
698
|
});
|
|
699
699
|
}
|
|
700
700
|
},
|
|
701
|
+
// Note that if the authMechanismProperties contain a TOKEN_RESOURCE that has a
|
|
702
|
+
// comma in it, it MUST be supplied as a MongoClient option instead of in the
|
|
703
|
+
// connection string.
|
|
701
704
|
authMechanismProperties: {
|
|
702
705
|
target: 'credentials',
|
|
703
706
|
transform({ options, values }): MongoCredentials {
|
package/src/error.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const NODE_IS_RECOVERING_ERROR_MESSAGE = new RegExp('node is recovering',
|
|
|
36
36
|
export const MONGODB_ERROR_CODES = Object.freeze({
|
|
37
37
|
HostUnreachable: 6,
|
|
38
38
|
HostNotFound: 7,
|
|
39
|
+
AuthenticationFailed: 18,
|
|
39
40
|
NetworkTimeout: 89,
|
|
40
41
|
ShutdownInProgress: 91,
|
|
41
42
|
PrimarySteppedDown: 189,
|
|
@@ -529,6 +530,34 @@ export class MongoAWSError extends MongoRuntimeError {
|
|
|
529
530
|
}
|
|
530
531
|
}
|
|
531
532
|
|
|
533
|
+
/**
|
|
534
|
+
* A error generated when the user attempts to authenticate
|
|
535
|
+
* via OIDC callbacks, but fails.
|
|
536
|
+
*
|
|
537
|
+
* @public
|
|
538
|
+
* @category Error
|
|
539
|
+
*/
|
|
540
|
+
export class MongoOIDCError extends MongoRuntimeError {
|
|
541
|
+
/**
|
|
542
|
+
* **Do not use this constructor!**
|
|
543
|
+
*
|
|
544
|
+
* Meant for internal use only.
|
|
545
|
+
*
|
|
546
|
+
* @remarks
|
|
547
|
+
* This class is only meant to be constructed within the driver. This constructor is
|
|
548
|
+
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
|
549
|
+
*
|
|
550
|
+
* @public
|
|
551
|
+
**/
|
|
552
|
+
constructor(message: string) {
|
|
553
|
+
super(message);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
override get name(): string {
|
|
557
|
+
return 'MongoOIDCError';
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
532
561
|
/**
|
|
533
562
|
* A error generated when the user attempts to authenticate
|
|
534
563
|
* via Azure, but fails.
|
|
@@ -536,7 +565,7 @@ export class MongoAWSError extends MongoRuntimeError {
|
|
|
536
565
|
* @public
|
|
537
566
|
* @category Error
|
|
538
567
|
*/
|
|
539
|
-
export class MongoAzureError extends
|
|
568
|
+
export class MongoAzureError extends MongoOIDCError {
|
|
540
569
|
/**
|
|
541
570
|
* **Do not use this constructor!**
|
|
542
571
|
*
|
|
@@ -557,6 +586,34 @@ export class MongoAzureError extends MongoRuntimeError {
|
|
|
557
586
|
}
|
|
558
587
|
}
|
|
559
588
|
|
|
589
|
+
/**
|
|
590
|
+
* A error generated when the user attempts to authenticate
|
|
591
|
+
* via GCP, but fails.
|
|
592
|
+
*
|
|
593
|
+
* @public
|
|
594
|
+
* @category Error
|
|
595
|
+
*/
|
|
596
|
+
export class MongoGCPError extends MongoOIDCError {
|
|
597
|
+
/**
|
|
598
|
+
* **Do not use this constructor!**
|
|
599
|
+
*
|
|
600
|
+
* Meant for internal use only.
|
|
601
|
+
*
|
|
602
|
+
* @remarks
|
|
603
|
+
* This class is only meant to be constructed within the driver. This constructor is
|
|
604
|
+
* not subject to semantic versioning compatibility guarantees and may change at any time.
|
|
605
|
+
*
|
|
606
|
+
* @public
|
|
607
|
+
**/
|
|
608
|
+
constructor(message: string) {
|
|
609
|
+
super(message);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
override get name(): string {
|
|
613
|
+
return 'MongoGCPError';
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
560
617
|
/**
|
|
561
618
|
* An error generated when a ChangeStream operation fails to execute.
|
|
562
619
|
*
|
package/src/index.ts
CHANGED
|
@@ -52,6 +52,7 @@ export {
|
|
|
52
52
|
MongoDriverError,
|
|
53
53
|
MongoError,
|
|
54
54
|
MongoExpiredSessionError,
|
|
55
|
+
MongoGCPError,
|
|
55
56
|
MongoGridFSChunkError,
|
|
56
57
|
MongoGridFSStreamError,
|
|
57
58
|
MongoInvalidArgumentError,
|
|
@@ -61,6 +62,7 @@ export {
|
|
|
61
62
|
MongoNetworkError,
|
|
62
63
|
MongoNetworkTimeoutError,
|
|
63
64
|
MongoNotConnectedError,
|
|
65
|
+
MongoOIDCError,
|
|
64
66
|
MongoParseError,
|
|
65
67
|
MongoRuntimeError,
|
|
66
68
|
MongoServerClosedError,
|
|
@@ -250,12 +252,14 @@ export type {
|
|
|
250
252
|
MongoCredentialsOptions
|
|
251
253
|
} from './cmap/auth/mongo_credentials';
|
|
252
254
|
export type {
|
|
253
|
-
|
|
255
|
+
IdPInfo,
|
|
254
256
|
IdPServerResponse,
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
257
|
+
OIDCCallbackFunction,
|
|
258
|
+
OIDCCallbackParams,
|
|
259
|
+
OIDCResponse
|
|
258
260
|
} from './cmap/auth/mongodb_oidc';
|
|
261
|
+
export type { Workflow } from './cmap/auth/mongodb_oidc';
|
|
262
|
+
export type { TokenCache } from './cmap/auth/mongodb_oidc/token_cache';
|
|
259
263
|
export type {
|
|
260
264
|
MessageHeader,
|
|
261
265
|
OpCompressedRequest,
|
package/src/mongo_client.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
DEFAULT_ALLOWED_HOSTS,
|
|
11
11
|
type MongoCredentials
|
|
12
12
|
} from './cmap/auth/mongo_credentials';
|
|
13
|
+
import { type TokenCache } from './cmap/auth/mongodb_oidc/token_cache';
|
|
13
14
|
import { AuthMechanism } from './cmap/auth/providers';
|
|
14
15
|
import type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS } from './cmap/connect';
|
|
15
16
|
import type { Connection } from './cmap/connection';
|
|
@@ -524,7 +525,7 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
|
|
|
524
525
|
if (options.credentials?.mechanism === AuthMechanism.MONGODB_OIDC) {
|
|
525
526
|
const allowedHosts =
|
|
526
527
|
options.credentials?.mechanismProperties?.ALLOWED_HOSTS || DEFAULT_ALLOWED_HOSTS;
|
|
527
|
-
const isServiceAuth = !!options.credentials?.mechanismProperties?.
|
|
528
|
+
const isServiceAuth = !!options.credentials?.mechanismProperties?.ENVIRONMENT;
|
|
528
529
|
if (!isServiceAuth) {
|
|
529
530
|
for (const host of options.hosts) {
|
|
530
531
|
if (!hostMatchesWildcards(host.toHostPort().host, allowedHosts)) {
|
|
@@ -828,6 +829,8 @@ export interface MongoOptions
|
|
|
828
829
|
extendedMetadata: Promise<Document>;
|
|
829
830
|
/** @internal */
|
|
830
831
|
autoEncrypter?: AutoEncrypter;
|
|
832
|
+
/** @internal */
|
|
833
|
+
tokenCache?: TokenCache;
|
|
831
834
|
proxyHost?: string;
|
|
832
835
|
proxyPort?: number;
|
|
833
836
|
proxyUsername?: string;
|