diff options
author | Matthew Miller <matthew@millerti.me> | 2024-02-26 16:39:22 -0800 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2024-02-26 16:39:22 -0800 |
commit | 4cdeb5ef9c3617e64647ac72b19500fbbfd19b7a (patch) | |
tree | 9b44c3a72b88954ef1e328c61760279a6755cf6f /packages/server/src | |
parent | 634ceabdb05f4b5e56132fff7c57598caa2401a8 (diff) |
Update getWebCrypto for Node 20 globalThis.crypto
Diffstat (limited to 'packages/server/src')
-rw-r--r-- | packages/server/src/helpers/iso/isoCrypto/getWebCrypto.test.ts | 104 | ||||
-rw-r--r-- | packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts | 67 |
2 files changed, 26 insertions, 145 deletions
diff --git a/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.test.ts b/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.test.ts index 24b37aa..9ab0ad2 100644 --- a/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.test.ts +++ b/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.test.ts @@ -23,101 +23,6 @@ Deno.test('should return globalThis.crypto when present', async () => { mockGlobalThisCrypto.restore(); }); -Deno.test('should return node:crypto.webcrypto when globalThis.crypto is missing', async () => { - // Clear whatever version of crypto might have been set - _getWebCryptoInternals.setCachedCrypto(undefined); - - // Pretend globalThis.crypto doesn't exist - const mockGlobalThisCrypto = stub( - _getWebCryptoInternals, - 'stubThisGlobalThisCrypto', - // @ts-ignore: globalThis.crypto - returnsNext([undefined]), - ); - - // Mock out just enough of the 'node:crypto' module - const fakeNodeCrypto = { webcrypto: {} }; - const mockImportNodeCrypto = stub( - _getWebCryptoInternals, - 'stubThisImportNodeCrypto', - // @ts-ignore: node:crypto - returnsNext([Promise.resolve(fakeNodeCrypto)]), - ); - - const returnedCrypto = await getWebCrypto(); - - assertEquals(returnedCrypto, fakeNodeCrypto.webcrypto); - - mockGlobalThisCrypto.restore(); - mockImportNodeCrypto.restore(); -}); - -Deno.test( - 'should return globalThis.crypto when present, while node:crypto.webcrypto is present', - async () => { - // Clear whatever version of crypto might have been set - _getWebCryptoInternals.setCachedCrypto(undefined); - - // Pretend globalThis.crypto exists - const fakeGlobalThisCrypto = {}; - const mockGlobalThisCrypto = stub( - _getWebCryptoInternals, - 'stubThisGlobalThisCrypto', - // @ts-ignore: globalThis.crypto - returnsNext([fakeGlobalThisCrypto]), - ); - - // Mock out just enough of the 'node:crypto' module, but like we're in Node v14 - const fakeNodeCrypto = { webcrypto: {} }; - const mockImportNodeCrypto = stub( - _getWebCryptoInternals, - 'stubThisImportNodeCrypto', - // @ts-ignore: node:crypto - returnsNext([Promise.resolve(fakeNodeCrypto)]), - ); - - const returnedCrypto = await getWebCrypto(); - - assertEquals(returnedCrypto, fakeGlobalThisCrypto); - - mockGlobalThisCrypto.restore(); - mockImportNodeCrypto.restore(); - }, -); - -Deno.test( - 'should return globalThis.crypto when present, while node:crypto is present but missing webcrypto', - async () => { - // Clear whatever version of crypto might have been set - _getWebCryptoInternals.setCachedCrypto(undefined); - - // Pretend globalThis.crypto exists - const fakeGlobalThisCrypto = {}; - const mockGlobalThisCrypto = stub( - _getWebCryptoInternals, - 'stubThisGlobalThisCrypto', - // @ts-ignore: globalThis.crypto - returnsNext([fakeGlobalThisCrypto]), - ); - - // Mock out just enough of the 'node:crypto' module, but like we're in Node v14 - const fakeNodeCrypto = { webcrypto: undefined }; - const mockImportNodeCrypto = stub( - _getWebCryptoInternals, - 'stubThisImportNodeCrypto', - // @ts-ignore: node:crypto - returnsNext([Promise.resolve(fakeNodeCrypto)]), - ); - - const returnedCrypto = await getWebCrypto(); - - assertEquals(returnedCrypto, fakeGlobalThisCrypto); - - mockGlobalThisCrypto.restore(); - mockImportNodeCrypto.restore(); - }, -); - Deno.test('should raise MissingWebCrypto error when nothing is available', async () => { // Clear whatever version of crypto might have been set _getWebCryptoInternals.setCachedCrypto(undefined); @@ -130,14 +35,6 @@ Deno.test('should raise MissingWebCrypto error when nothing is available', async returnsNext([undefined]), ); - // Pretend node:crypto doesn't exist - const mockImportNodeCrypto = stub( - _getWebCryptoInternals, - 'stubThisImportNodeCrypto', - // @ts-ignore: node:crypto - returnsNext([Promise.resolve({ webcrypto: undefined })]), - ); - await assertRejects( () => getWebCrypto(), MissingWebCrypto, @@ -145,5 +42,4 @@ Deno.test('should raise MissingWebCrypto error when nothing is available', async ); mockGlobalThisCrypto.restore(); - mockImportNodeCrypto.restore(); }); diff --git a/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts b/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts index f07d4a2..59ba876 100644 --- a/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts +++ b/packages/server/src/helpers/iso/isoCrypto/getWebCrypto.ts @@ -6,34 +6,38 @@ let webCrypto: Crypto | undefined = undefined; * Try to get an instance of the Crypto API from the current runtime. Should support Node, * as well as others, like Deno, that implement Web APIs. */ -export async function getWebCrypto(): Promise<Crypto> { - if (webCrypto) { - return webCrypto; - } - +export function getWebCrypto(): Promise<Crypto> { /** - * Naively attempt to access Crypto as a global object, which popular alternative run-times - * support. + * Hello there! If you came here wondering why this method is asynchronous when use of + * `globalThis.crypto` is not, it's to minimize a bunch of refactor related to make this + * synchronous. For example, `generateRegistrationOptions()` and `generateAuthenticationOptions()` + * become synchronous if we make this synchronous (since nothing else in that method is async) + * which represents a breaking API change in this library's core API. + * + * TODO: If it's after February 2025 when you read this then consider whether it still makes sense + * to keep this method asynchronous. */ - const _globalThisCrypto = _getWebCryptoInternals.stubThisGlobalThisCrypto(); + const toResolve = new Promise<Crypto>((resolve, reject) => { + if (webCrypto) { + return resolve(webCrypto); + } - if (_globalThisCrypto) { - webCrypto = _globalThisCrypto; - return webCrypto; - } + /** + * Naively attempt to access Crypto as a global object, which popular ESM-centric run-times + * support (and Node v20+) + */ + const _globalThisCrypto = _getWebCryptoInternals.stubThisGlobalThisCrypto(); - /** - * `globalThis.crypto` isn't available, so attempt a Node import... - */ - const _nodeCrypto = await _getWebCryptoInternals.stubThisImportNodeCrypto(); + if (_globalThisCrypto) { + webCrypto = _globalThisCrypto; + return resolve(webCrypto); + } - if (_nodeCrypto?.webcrypto) { - webCrypto = _nodeCrypto.webcrypto as Crypto; - return webCrypto; - } + // We tried to access it both in Node and globally, so bail out + return reject(new MissingWebCrypto()); + }); - // We tried to access it both in Node and globally, so bail out - throw new MissingWebCrypto(); + return toResolve; } export class MissingWebCrypto extends Error { @@ -46,25 +50,6 @@ export class MissingWebCrypto extends Error { // Make it possible to stub return values during testing export const _getWebCryptoInternals = { - stubThisImportNodeCrypto: async () => { - try { - // dnt-shim-ignore - /** - * The `webpackIgnore` here is to help support Next.js' Edge runtime. - * See https://github.com/MasterKale/SimpleWebAuthn/issues/517 for more info. - */ - const _nodeCrypto = await import(/* webpackIgnore: true */ 'node:crypto'); - return _nodeCrypto; - } catch (_err) { - /** - * Intentionally declaring webcrypto as undefined because we're assuming the Node import - * failed due to either: - * - `import()` isn't supported - * - `node:crypto` is unavailable. - */ - return { webcrypto: undefined }; - } - }, stubThisGlobalThisCrypto: () => globalThis.crypto, // Make it possible to reset the `webCrypto` at the top of the file setCachedCrypto: (newCrypto: Crypto | undefined) => { |