Skip to content

Commit bc7aa64

Browse files
authored
implement X509 auth (#166)
1 parent 4855e1b commit bc7aa64

7 files changed

Lines changed: 73 additions & 6 deletions

File tree

src/auth/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./base.ts";
22
export * from "./scram.ts";
3+
export * from "./x509.ts";

src/auth/scram.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export class ScramAuthPlugin extends AuthPlugin {
4747
return request;
4848
}
4949

50-
async auth(authContext: AuthContext) {
50+
async auth(authContext: AuthContext): Promise<Document> {
5151
const response = authContext.response;
5252
if (response && response.speculativeAuthenticate) {
5353
return await continueScramConversation(

src/auth/x509.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Credential, Document } from "../types.ts";
2+
import { AuthContext, AuthPlugin } from "./base.ts";
3+
import { HandshakeDocument } from "../protocol/handshake.ts";
4+
import { driverMetadata } from "../protocol/mod.ts";
5+
6+
export interface X509Command extends Document {
7+
authenticate: number;
8+
mechanism: string;
9+
user?: string;
10+
}
11+
12+
export class X509AuthPlugin extends AuthPlugin {
13+
constructor() {
14+
super();
15+
}
16+
prepare(authContext: AuthContext): Document {
17+
const handshakeDoc = <HandshakeDocument> {
18+
ismaster: true,
19+
client: driverMetadata,
20+
compression: authContext.options.compression,
21+
speculativeAuthenticate: x509AuthenticateCommand(authContext.credentials),
22+
};
23+
return handshakeDoc;
24+
}
25+
26+
async auth(authContext: AuthContext): Promise<Document> {
27+
if (authContext.response!.speculativeAuthenticate) {
28+
return authContext.response!;
29+
}
30+
return await authContext.protocol.commandSingle(
31+
"$external",
32+
x509AuthenticateCommand(authContext.credentials),
33+
);
34+
}
35+
}
36+
37+
function x509AuthenticateCommand(credentials?: Credential): Document {
38+
const command: X509Command = { authenticate: 1, mechanism: "MONGODB-X509" };
39+
if (credentials) {
40+
command.user = credentials!.username;
41+
}
42+
return command;
43+
}

src/client.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ListDatabaseInfo,
99
} from "./types.ts";
1010
import { parse } from "./utils/uri.ts";
11-
import { AuthContext, ScramAuthPlugin } from "./auth/mod.ts";
11+
import { AuthContext, ScramAuthPlugin, X509AuthPlugin } from "./auth/mod.ts";
1212
import { MongoError } from "./error.ts";
1313

1414
const DENO_DRIVER_VERSION = "0.0.1";
@@ -36,6 +36,16 @@ export class MongoClient {
3636
if (options.certFile) {
3737
denoConnectOps.certFile = options.certFile;
3838
}
39+
if (options.keyFile) {
40+
if (options.keyFilePassword) {
41+
throw new MongoError(
42+
`Tls keyFilePassword not implemented in Deno driver`,
43+
);
44+
//TODO, need something like const key = decrypt(options.keyFile) ...
45+
}
46+
throw new MongoError(`Tls keyFile not implemented in Deno driver`);
47+
//TODO, need Deno.connectTls with something like key or keyFile option.
48+
}
3949
conn = await Deno.connectTls(denoConnectOps);
4050
} else {
4151
conn = await Deno.connect(denoConnectOps);
@@ -56,8 +66,12 @@ export class MongoClient {
5666
authPlugin = new ScramAuthPlugin("sha256"); //TODO AJUST sha256
5767
} else if (mechanism === "SCRAM-SHA-1") {
5868
authPlugin = new ScramAuthPlugin("sha1");
69+
} else if (mechanism === "MONGODB-X509") {
70+
authPlugin = new X509AuthPlugin();
5971
} else {
60-
throw new MongoError(`Auth mechanism not implemented: ${mechanism}`);
72+
throw new MongoError(
73+
`Auth mechanism not implemented in Deno driver: ${mechanism}`,
74+
);
6175
}
6276
const request = authPlugin.prepare(authContext);
6377
authContext.response = await this.#protocol.commandSingle(

src/protocol/handshake.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface HandshakeDocument extends Document {
1818
client: any;
1919
compression: string[];
2020
saslSupportedMechs?: string;
21+
speculativeAuthenticate?: Document;
2122
}
2223

2324
interface HandshakeResponse {

src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ export type Document = Bson.Document;
55
export interface ConnectOptions {
66
compression?: string[];
77
certFile?: string;
8+
keyFile?: string;
9+
keyFilePassword?: string;
810
tls?: boolean;
911
safe?: boolean;
1012
credential?: Credential;
@@ -341,7 +343,7 @@ export interface Credential {
341343
/**
342344
* Which authentication mechanism to use. If not provided, one will be negotiated with the server.
343345
*/
344-
mechanism?: "SCRAM-SHA-1" | "SCRAM-SHA-256";
346+
mechanism?: "SCRAM-SHA-1" | "SCRAM-SHA-256" | "MONGODB-X509";
345347
}
346348

347349
export interface IndexOptions {

src/utils/uri.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { ConnectOptions, Credential } from "../types.ts";
33

44
interface Parts {
5-
auth?: { user: string; password: string };
5+
auth?: { user: string; password?: string };
66
hash?: any;
77
hostname?: string;
88
href?: string;
@@ -25,7 +25,7 @@ export function parse_url(url: string): Parts {
2525
"hash",
2626
];
2727
const pattern =
28-
/([^:/?#]+:)?(?:(?:\/\/)(?:([^/?#]*:[^@/]+)@)?([^/:?#]+)(?:(?::)(\d+))?)?(\/?[^?#]*)?(\?[^#]*)?(#[^\s]*)?/;
28+
/([^:/?#]+:)?(?:(?:\/\/)(?:([^/?#]*:?[^@/]+)@)?([^/:?#]+)(?:(?::)(\d+))?)?(\/?[^?#]*)?(\?[^#]*)?(#[^\s]*)?/;
2929

3030
function parse_simple(url: string): any {
3131
const parts: any = {};
@@ -118,6 +118,12 @@ export function parse(url: string, optOverride: any = {}): ConnectOptions {
118118
if (data.search.tlsCAFile) {
119119
connectOptions.certFile = data.search.tlsCAFile;
120120
}
121+
if (data.search.tlsCertificateKeyFile) {
122+
connectOptions.keyFile = data.search.tlsCertificateKeyFile;
123+
}
124+
if (data.search.tlsCertificateKeyFilePassword) {
125+
connectOptions.keyFilePassword = data.search.tlsCertificateKeyFilePassword;
126+
}
121127
if (data.search.safe) {
122128
connectOptions.safe = data.search.safe === "true";
123129
}

0 commit comments

Comments
 (0)