Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UUID convenience class #425

Merged
merged 21 commits into from
Apr 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"ts-node": "^9.0.0",
"typedoc": "^0.18.0",
"typescript": "^4.0.2",
"typescript-cached-transpile": "0.0.6"
"typescript-cached-transpile": "0.0.6",
"uuid": "^8.3.2"
},
"config": {
"native": false
Expand Down
16 changes: 14 additions & 2 deletions src/binary.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Buffer } from 'buffer';
import { ensureBuffer } from './ensure_buffer';
import { uuidHexStringToBuffer } from './uuid_utils';
import { UUID, UUIDExtended } from './uuid';
import type { EJSONOptions } from './extended_json';
import { parseUUID, UUIDExtended } from './uuid';

/** @public */
export type BinarySequence = Uint8Array | Buffer | number[];
Expand Down Expand Up @@ -228,6 +229,17 @@ export class Binary {
};
}

/** @internal */
toUUID(): UUID {
if (this.sub_type === Binary.SUBTYPE_UUID) {
return new UUID(this.buffer.slice(0, this.position));
}

throw new Error(
`Binary sub_type "${this.sub_type}" is not supported for converting to UUID. Only "${Binary.SUBTYPE_UUID}" is currently supported.`
);
}

/** @internal */
static fromExtendedJSON(
doc: BinaryExtendedLegacy | BinaryExtended | UUIDExtended,
Expand All @@ -248,7 +260,7 @@ export class Binary {
}
} else if ('$uuid' in doc) {
type = 4;
data = Buffer.from(parseUUID(doc.$uuid));
data = uuidHexStringToBuffer(doc.$uuid);
}
nbbeeken marked this conversation as resolved.
Show resolved Hide resolved
if (!data) {
throw new TypeError(`Unexpected Binary Extended JSON format ${JSON.stringify(doc)}`);
Expand Down
2 changes: 2 additions & 0 deletions src/bson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Map } from './map';
import { MaxKey } from './max_key';
import { MinKey } from './min_key';
import { ObjectId } from './objectid';
import { UUID } from './uuid';
import { calculateObjectSize as internalCalculateObjectSize } from './parser/calculate_size';
// Parts of the parser
import { deserialize as internalDeserialize, DeserializeOptions } from './parser/deserializer';
Expand Down Expand Up @@ -81,6 +82,7 @@ export {
DBRef,
Binary,
ObjectId,
UUID,
Long,
Timestamp,
Double,
Expand Down
4 changes: 2 additions & 2 deletions src/objectid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class ObjectId {
typeof otherId === 'string' &&
ObjectId.isValid(otherId) &&
otherId.length === 12 &&
this.id instanceof Buffer
Buffer.isBuffer(this.id)
) {
return otherId === this.id.toString('binary');
}
Expand Down Expand Up @@ -313,7 +313,7 @@ export class ObjectId {
return true;
}

if (id instanceof Buffer && id.length === 12) {
if (Buffer.isBuffer(id) && id.length === 12) {
return true;
}

Expand Down
45 changes: 34 additions & 11 deletions src/parser/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Buffer } from 'buffer';

type RandomBytesFunction = (size: number) => Uint8Array;

/**
* Normalizes our expected stringified form of a function across versions of node
* @param fn - The function to stringify
Expand All @@ -8,11 +10,20 @@ export function normalizedFunctionString(fn: Function): string {
return fn.toString().replace('function(', 'function (');
}

function insecureRandomBytes(size: number): Uint8Array {
const isReactNative =
typeof global.navigator === 'object' && global.navigator.product === 'ReactNative';

const insecureWarning = isReactNative
? 'BSON: For React Native please polyfill crypto.getRandomValues, e.g. using: https://www.npmjs.com/package/react-native-get-random-values.'
: 'BSON: No cryptographic implementation for random bytes present, falling back to a less secure implementation.';

const insecureRandomBytes: RandomBytesFunction = function insecureRandomBytes(size: number) {
console.warn(insecureWarning);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be better to use process.emitWarning here.

Suggested change
console.warn(insecureWarning);
process.emitWarning(insecureWarning);

Copy link
Contributor Author

@steffenagger steffenagger Mar 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, but wouldn't this throw in a browser context without crypto?


const result = Buffer.alloc(size);
for (let i = 0; i < size; ++i) result[i] = Math.floor(Math.random() * 256);
return result;
}
};

/* We do not want to have to include DOM types just for this check */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -22,22 +33,34 @@ declare let require: Function;
declare let global: any;
declare const self: unknown;

export let randomBytes = insecureRandomBytes;
if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) {
randomBytes = size => window.crypto.getRandomValues(Buffer.alloc(size));
} else {
const detectRandomBytes = (): RandomBytesFunction => {
if (typeof window !== 'undefined') {
// browser crypto implementation(s)
const target = window.crypto || window.msCrypto; // allow for IE11
if (target && target.getRandomValues) {
return size => target.getRandomValues(Buffer.alloc(size));
}
}

if (typeof global !== 'undefined' && global.crypto && global.crypto.getRandomValues) {
// allow for RN packages such as https://www.npmjs.com/package/react-native-get-random-values to populate global
return size => global.crypto.getRandomValues(Buffer.alloc(size));
}

let requiredRandomBytes: RandomBytesFunction | null | undefined;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
randomBytes = require('crypto').randomBytes;
requiredRandomBytes = require('crypto').randomBytes;
} catch (e) {
// keep the fallback
}

// NOTE: in transpiled cases the above require might return null/undefined
if (randomBytes == null) {
randomBytes = insecureRandomBytes;
}
}

return requiredRandomBytes || insecureRandomBytes;
};

export const randomBytes = detectRandomBytes();

export function isUint8Array(value: unknown): value is Uint8Array {
return Object.prototype.toString.call(value) === '[object Uint8Array]';
Expand Down
Loading