Skip to content

Commit

Permalink
Merge pull request #139 from hackers267/auth
Browse files Browse the repository at this point in the history
Auth add some function to prepare the auth
  • Loading branch information
manyuanrong committed Dec 2, 2020
2 parents 4fab8d3 + a64c166 commit b76a153
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 10 deletions.
6 changes: 6 additions & 0 deletions deps.ts
@@ -1,4 +1,10 @@
export * as Bson from "./bson/mod.ts";
export { createHash } from "https://deno.land/std@0.77.0/hash/mod.ts";
export { pbkdf2Sync } from "https://deno.land/std@0.78.0/node/_crypto/pbkdf2.ts";
export { randomBytes } from "https://deno.land/std@0.78.0/node/crypto.ts";

// todo: 替换为 deno.land/std的库
export { HmacSha1 } from "https://raw.githubusercontent.com/denoland/deno/master/std/hash/sha1.ts";
export * from "https://deno.land/x/bytes_formater/mod.ts";
export { BufReader } from "https://deno.land/std@0.77.0/io/mod.ts";
export { deferred } from "https://deno.land/std@0.77.0/async/deferred.ts";
Expand Down
6 changes: 6 additions & 0 deletions src/auth/base.ts
@@ -0,0 +1,6 @@
import { Credential, Document } from "../types.ts";

export abstract class AuthPlugin {
abstract prepare(credential: Credential): Document;
auth() {}
}
77 changes: 77 additions & 0 deletions src/auth/mod.ts
@@ -0,0 +1,77 @@
import { createHash, HmacSha1, pbkdf2Sync, randomBytes } from "../../deps.ts";
import { Binary } from "../../bson/mod.ts";

export function passwordDigest(username: string, password: string): string {
const hash = createHash("md5");
hash.update(`${username}:mongo:${password}`);
return hash.toString();
}
export function HI(data: string, salt: string, iterations: number) {
return pbkdf2Sync(data, salt, iterations, 20, "sha1");
}
export function clientKeyFor(key: string) {
return keyFor(key, "Client Key");
}

/**
* @param serverKey
* @param key
* @return string
*/
function keyFor(key: string, serverKey: string): string {
return new HmacSha1(key).update(serverKey).hex();
}

export function serverKeyFor(key: string) {
return keyFor(key, "Server Key");
}

export function storedKeyFor(data: string) {
return createHash("sha1").update(data).toString();
}

export function nonceFor() {
return randomBytes(24);
}
export function clientFirstMessageBare(username: string, nonce: Uint8Array) {
return Uint8Array.from(
[
...getUint8Array("n="),
...getUint8Array(username),
...getUint8Array(",r="),
...nonce,
],
);
}

/**
* @param input
*/
function getUint8Array(input: string) {
return new TextEncoder().encode(input);
}

export function cleanUsername(username: string) {
return username.replace("=", "=3D").replace(",", "=2C");
}

export function makeFirstMessage(
credentials: { username: string },
nonce: Uint8Array,
) {
const username = cleanUsername(credentials.username);
const mechanism = "SCRAM-SHA-1";
return {
saslStart: 1,
mechanism,
autoAuthorize: 1,
payload: new Binary(
Uint8Array.from(
[...getUint8Array("n,,"), ...clientFirstMessageBare(username, nonce)],
),
),
options: {
skipEmptyExchange: true,
},
};
}
11 changes: 11 additions & 0 deletions src/auth/scram.ts
@@ -0,0 +1,11 @@
import { Credential, Document } from "../types.ts";
import { AuthPlugin } from "./base.ts";

export class ScramAuthPlugin extends AuthPlugin {
prepare(credential: Credential): Document {
const nonce = window.crypto.getRandomValues(new Uint8Array(24));
return {
nonce,
};
}
}
22 changes: 22 additions & 0 deletions src/types.ts
Expand Up @@ -319,3 +319,25 @@ export interface CreateUserOptions {
*/
comment?: Document;
}

export interface Credential {
/**
* The username to authenticate with. This applies to all mechanisms but may be omitted when authenticating via MONGODB-X509.
*/
username?: string;

/**
* The password to authenticate with. This does not apply to all mechanisms.
*/
password?: string;

/**
* The database used to authenticate. This applies to all mechanisms and defaults to "admin" in SCRAM authentication mechanisms and "$external" for GSSAPI, MONGODB-X509 and PLAIN.
*/
source?: string;

/**
* Which authentication mechanism to use. If not provided, one will be negotiated with the server.
*/
mechanism?: "SCRAM-SHA-1" | "SCRAM-SHA-256";
}
16 changes: 8 additions & 8 deletions tests/auth.test.ts
@@ -1,11 +1,11 @@
import { testWithClient } from "./common.ts";

testWithClient("createUser", async (client) => {
const db = client.database("test");
await db.createUser("testUser", "testPassword");
});
// testWithClient("createUser", async (client) => {
// const db = client.database("test");
// await db.createUser("testUser", "testPassword");
// });

testWithClient("dropUser", async (client) => {
const db = client.database("test");
await db.dropUser("testUser");
});
// testWithClient("dropUser", async (client) => {
// const db = client.database("test");
// await db.dropUser("testUser");
// });
12 changes: 12 additions & 0 deletions tests/auth/HI.test.ts
@@ -0,0 +1,12 @@
import { assertEquals } from "../test.deps.ts";
import { HI } from "../../src/auth/mod.ts";
const salt = "rQ9ZY3MntBeuP3E1TDVC4w";
const inter = 10000;
const data = "1c33006ec1ffd90f9cadcbcc0e118200";
Deno.test({
name: "HI",
fn() {
const saltedPassword = HI(data, salt, inter).toString("hex");
assertEquals(saltedPassword, "48549cb611401e7456e9072741898ea4006e4ee6");
},
});
145 changes: 145 additions & 0 deletions tests/auth/auth.test.ts
@@ -0,0 +1,145 @@
import {
cleanUsername,
clientFirstMessageBare,
clientKeyFor,
makeFirstMessage,
nonceFor,
passwordDigest,
serverKeyFor,
storedKeyFor,
} from "../../src/auth/mod.ts";
import { assertEquals } from "../test.deps.ts";
import { Binary } from "../../bson/mod.ts";

interface PasswordValid {
username: string;
password: string;
digest: string;
}

const passwordValdis: PasswordValid[] = [
{
username: "user",
password: "pencil",
digest: "1c33006ec1ffd90f9cadcbcc0e118200",
},
{
username: "test",
password: "test",
digest: "a6de521abefc2fed4f5876855a3484f5",
},
];
passwordValdis.forEach(({ username, password, digest }) => {
Deno.test({
name: `passwordDigest:${username}:${password}`,
fn() {
const digestRes: string = passwordDigest(username, password);
assertEquals(digestRes, digest);
},
});
});

Deno.test({
name: "clientKey",
fn() {
const key: string = "48549cb611401e7456e9072741898ea4006e4ee6";
const expected = `6f7ca2af959e3b7823886dca969c04b166ed3b7d`;
const result: string = clientKeyFor(key);
assertEquals(expected, result);
},
});

Deno.test({
name: "serverKey",
fn() {
const key: string = "48549cb611401e7456e9072741898ea4006e4ee6";
const expected: string = `550083f8dbe04acbb85a0081a6eacf1607b4835e`;
const result: string = serverKeyFor(key);
assertEquals(expected, result);
},
});

Deno.test({
name: "storedKey",
fn() {
const data: string = "48549cb611401e7456e9072741898ea4006e4ee6";
const expected: string = "504ce7448255f3996b66ca99c09a6c5463d18dd7";
const result: string = storedKeyFor(data);
assertEquals(expected, result);
},
});

Deno.test({
name: "nonce",
fn() {
const buf = nonceFor();
assertEquals(buf.length, 24);
},
});

Deno.test({
name: "clientFirstMessageBare",
fn() {
const username = "1234";
const nonce = new TextEncoder().encode("qwer");
const result: Uint8Array = clientFirstMessageBare(username, nonce);
const expected: Uint8Array = Uint8Array.from(
[110, 61, 49, 50, 51, 52, 44, 114, 61, 113, 119, 101, 114],
);
assertEquals(expected, result);
},
});

Deno.test({
name: "cleanUsername",
fn() {
const username: string = "first=12,last=34";
const expected: string = "first=3D12=2Clast=34";
const result: string = cleanUsername(username);
assertEquals(expected, result);
},
});

function getUint8Array(str: string) {
return new TextEncoder().encode(str);
}

Deno.test({
name: "makeFirstMessage",
fn: function () {
const username = "admin";
const credentials = { username };
const nonce = getUint8Array(username);
const payload = new Binary(
[
110,
44,
44,
110,
61,
97,
100,
109,
105,
110,
44,
114,
61,
97,
100,
109,
105,
110,
],
);
const expected = {
saslStart: 1,
mechanism: "SCRAM-SHA-1",
payload,
autoAuthorize: 1,
options: { skipEmptyExchange: true },
};
const result = makeFirstMessage(credentials, nonce);
assertEquals(expected, result);
},
});
2 changes: 1 addition & 1 deletion tests/common.ts
@@ -1,6 +1,6 @@
import { MongoClient } from "../mod.ts";

const hostname = "localhost";
const hostname = "127.0.0.1";

export async function testWithClient(
name: string,
Expand Down
2 changes: 1 addition & 1 deletion tests/connect.test.ts
@@ -1,7 +1,7 @@
import { assert } from "./test.deps.ts";
import { MongoClient } from "../src/client.ts";

const hostname = "localhost";
const hostname = "127.0.0.1";

Deno.test("testConnectWithUri", async () => {
const client = new MongoClient();
Expand Down

0 comments on commit b76a153

Please sign in to comment.