index.node.cjs 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.generateChallenge = generateChallenge;
  4. exports.default = pkceChallenge;
  5. exports.verifyChallenge = verifyChallenge;
  6. let crypto;
  7. crypto =
  8. globalThis.crypto?.webcrypto ?? // Node.js [18-16] REPL
  9. globalThis.crypto ?? // Node.js >18
  10. import("node:crypto").then(m => m.webcrypto); // Node.js <18 Non-REPL
  11. /**
  12. * Creates an array of length `size` of random bytes
  13. * @param size
  14. * @returns Array of random ints (0 to 255)
  15. */
  16. async function getRandomValues(size) {
  17. return (await crypto).getRandomValues(new Uint8Array(size));
  18. }
  19. /** Generate cryptographically strong random string
  20. * @param size The desired length of the string
  21. * @returns The random string
  22. */
  23. async function random(size) {
  24. const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
  25. const evenDistCutoff = Math.pow(2, 8) - Math.pow(2, 8) % mask.length;
  26. let result = "";
  27. while (result.length < size) {
  28. const randomBytes = await getRandomValues(size - result.length);
  29. for (const randomByte of randomBytes) {
  30. if (randomByte < evenDistCutoff) {
  31. result += mask[randomByte % mask.length];
  32. }
  33. }
  34. }
  35. return result;
  36. }
  37. /** Generate a PKCE challenge verifier
  38. * @param length Length of the verifier
  39. * @returns A random verifier `length` characters long
  40. */
  41. async function generateVerifier(length) {
  42. return await random(length);
  43. }
  44. /** Generate a PKCE code challenge from a code verifier
  45. * @param code_verifier
  46. * @returns The base64 url encoded code challenge
  47. */
  48. async function generateChallenge(code_verifier) {
  49. const buffer = await (await crypto).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
  50. // Generate base64url string
  51. // btoa is deprecated in Node.js but is used here for web browser compatibility
  52. // (which has no good replacement yet, see also https://github.com/whatwg/html/issues/6811)
  53. return btoa(String.fromCharCode(...new Uint8Array(buffer)))
  54. .replace(/\//g, '_')
  55. .replace(/\+/g, '-')
  56. .replace(/=/g, '');
  57. }
  58. /** Generate a PKCE challenge pair
  59. * @param length Length of the verifer (between 43-128). Defaults to 43.
  60. * @returns PKCE challenge pair
  61. */
  62. async function pkceChallenge(length) {
  63. if (!length)
  64. length = 43;
  65. if (length < 43 || length > 128) {
  66. throw `Expected a length between 43 and 128. Received ${length}.`;
  67. }
  68. const verifier = await generateVerifier(length);
  69. const challenge = await generateChallenge(verifier);
  70. return {
  71. code_verifier: verifier,
  72. code_challenge: challenge,
  73. };
  74. }
  75. /** Verify that a code_verifier produces the expected code challenge
  76. * @param code_verifier
  77. * @param expectedChallenge The code challenge to verify
  78. * @returns True if challenges are equal. False otherwise.
  79. */
  80. async function verifyChallenge(code_verifier, expectedChallenge) {
  81. const actualChallenge = await generateChallenge(code_verifier);
  82. return actualChallenge === expectedChallenge;
  83. }