Skip to content

Commit

Permalink
feat(NODE-5034): support proper cb interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
durran committed Feb 6, 2023
1 parent 6e9fa5c commit 80cc54d
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 8 deletions.
43 changes: 41 additions & 2 deletions src/cmap/auth/mongo_credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { Document } from '../../bson';
import { MongoAPIError, MongoMissingCredentialsError } from '../../error';
import { GSSAPICanonicalizationValue } from './gssapi';
import type { OIDCRefreshFunction, OIDCRequestFunction } from './mongodb_oidc';
import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './providers';

// https://github.com/mongodb/specifications/blob/master/source/auth/auth.rst
Expand Down Expand Up @@ -34,8 +35,8 @@ export interface AuthMechanismProperties extends Document {
AWS_SESSION_TOKEN?: string;
DEVICE_NAME?: string;
PRINCIPAL_NAME?: string;
REQUEST_TOKEN_CALLBACK?: string;
REFRESH_TOKEN_CALLBACK?: string;
REQUEST_TOKEN_CALLBACK?: OIDCRequestFunction;
REFRESH_TOKEN_CALLBACK?: OIDCRefreshFunction;
}

/** @public */
Expand Down Expand Up @@ -141,6 +142,44 @@ export class MongoCredentials {
throw new MongoMissingCredentialsError(`Username required for mechanism '${this.mechanism}'`);
}

if (this.mechanism === AuthMechanism.MONGODB_OIDC) {
if (this.username) {
throw new MongoAPIError(
`Username not permitted for mechanism '${this.mechanism}'. Use PRINCIPAL_NAME instead.`
);
}

if (this.mechanismProperties.PRINCIPAL_NAME && this.mechanismProperties.DEVICE_NAME) {
throw new MongoAPIError(
`PRINCIPAL_NAME and DEVICE_NAME may not be used together for mechanism '${this.mechanism}'.`
);
}

if (this.mechanismProperties.DEVICE_NAME && this.mechanismProperties.DEVICE_NAME !== 'aws') {
throw new MongoAPIError(
`Currently only a DEVICE_NAME of 'aws' is supported for mechanism '${this.mechanism}'.`
);
}

if (
this.mechanismProperties.REFRESH_TOKEN_CALLBACK &&
!this.mechanismProperties.REQUEST_TOKEN_CALLBACK
) {
throw new MongoAPIError(
`A REQUEST_TOKEN_CALLBACK must be provided when using a REFRESH_TOKEN_CALLBACK for mechanism '${this.mechanism}'`
);
}

if (
!this.mechanismProperties.DEVICE_NAME &&
!this.mechanismProperties.REQUEST_TOKEN_CALLBACK
) {
throw new MongoAPIError(
`Either a DEVICE_NAME or a REQUEST_TOKEN_CALLBACK must be specified for mechanism '${this.mechanism}'.`
);
}
}

if (AUTH_MECHS_AUTH_SRC_EXTERNAL.has(this.mechanism)) {
if (this.source != null && this.source !== '$external') {
// TODO(NODE-3485): Replace this with a MongoAuthValidationError
Expand Down
26 changes: 26 additions & 0 deletions src/cmap/auth/mongodb_oidc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @public */
export interface OIDCMechanismServerStep1 {
authorizeEndpoint?: string;
tokenEndpoint?: string;
deviceAuthorizeEndpoint?: string;
clientId: string;
clientSecret?: string;
requestScopes?: string[];
}

/** @public */
export interface OIDCRequestTokenResult {
accessToken: string;
expiresInSeconds?: number;
refreshToken?: string;
}

/** @public */
export interface OIDCRequestFunction {
(idl: OIDCMechanismServerStep1): Promise<OIDCRequestTokenResult>;
}

/** @public */
export interface OIDCRefreshFunction {
(idl: OIDCMechanismServerStep1, result: OIDCRequestTokenResult): Promise<OIDCRequestTokenResult>;
}
6 changes: 5 additions & 1 deletion src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,11 @@ export function parseOptions(
);
}

if (!(isGssapi || isX509 || isAws || isOidc) && mongoOptions.dbName && !allOptions.has('authSource')) {
if (
!(isGssapi || isX509 || isAws || isOidc) &&
mongoOptions.dbName &&
!allOptions.has('authSource')
) {
// inherit the dbName unless GSSAPI or X509, then silently ignore dbName
// and there was no specific authSource given
mongoOptions.credentials = MongoCredentials.merge(mongoOptions.credentials, {
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ export type {
MongoCredentials,
MongoCredentialsOptions
} from './cmap/auth/mongo_credentials';
export type {
OIDCMechanismServerStep1,
OIDCRefreshFunction,
OIDCRequestFunction,
OIDCRequestTokenResult
} from './cmap/auth/mongodb_oidc';
export type {
BinMsg,
MessageHeader,
Expand Down
14 changes: 10 additions & 4 deletions test/tools/uri_spec_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ export function executeUriValidationTest(
// in the actual options provided to the MongoClient. This is because OIDC does not allow
// functions for callbacks in the URI itself but needs to validate they are passed.
const CALLBACKS = {
oidcRequest: () => {},
oidcRefresh: () => {}
oidcRequest: async () => {
return { accessToken: '<test>' };
},
oidcRefresh: async () => {
return { accessToken: '<test>' };
}
};

const CALLBACK_MAPPINGS = {
Expand Down Expand Up @@ -223,8 +227,10 @@ export function executeUriValidationTest(
expectedMechProp === 'REQUEST_TOKEN_CALLBACK' ||
expectedMechProp === 'REFRESH_TOKEN_CALLBACK'
) {
expect(options, `${errorMessage} credentials.mechanismProperties.${expectedMechProp}`)
.to.have.nested.property(`credentials.mechanismProperties.${expectedMechProp}`);
expect(
options,
`${errorMessage} credentials.mechanismProperties.${expectedMechProp}`
).to.have.nested.property(`credentials.mechanismProperties.${expectedMechProp}`);
} else {
expect(options, `${errorMessage} credentials.mechanismProperties.${expectedMechProp}`)
.to.have.nested.property(`credentials.mechanismProperties.${expectedMechProp}`)
Expand Down
2 changes: 1 addition & 1 deletion test/unit/assorted/auth.spec.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { loadSpecTests } from '../../spec';
import { executeUriValidationTest } from '../../tools/uri_spec_runner';

describe.only('Auth option spec tests (legacy)', function () {
describe('Auth option spec tests (legacy)', function () {
const suites = loadSpecTests('auth', 'legacy');

for (const suite of suites) {
Expand Down

0 comments on commit 80cc54d

Please sign in to comment.