@bb-labs/pkce 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bb-labs/pkce
2
2
 
3
- A lightweight PKCE (Proof Key for Code Exchange) library using `@noble/hashes` for universal compatibility.
3
+ Zero-dependency PKCE library with pure JavaScript SHA-256 implementation. Bring your own crypto - provide a random bytes generator for maximum compatibility.
4
4
 
5
5
  ## Installation
6
6
 
@@ -13,43 +13,51 @@ npm install @bb-labs/pkce
13
13
  ```typescript
14
14
  import { generateCodeVerifier, createCodeChallenge } from "@bb-labs/pkce";
15
15
 
16
- // Generate a cryptographically secure code verifier
17
- const verifier = generateCodeVerifier();
16
+ // Node.js
17
+ import { randomBytes } from "crypto";
18
+ const verifier = generateCodeVerifier(randomBytes);
19
+ const challenge = createCodeChallenge(verifier);
18
20
 
19
- // Create the corresponding code challenge
21
+ // Browser
22
+ const verifier = generateCodeVerifier((length) => {
23
+ const bytes = new Uint8Array(length);
24
+ crypto.getRandomValues(bytes);
25
+ return bytes;
26
+ });
20
27
  const challenge = createCodeChallenge(verifier);
21
28
 
22
- // Use in OAuth flow...
29
+ // Expo
30
+ import * as Crypto from "expo-crypto";
31
+ const verifier = generateCodeVerifier(Crypto.getRandomBytes);
32
+ const challenge = createCodeChallenge(verifier);
23
33
  ```
24
34
 
25
35
  ## API
26
36
 
27
- ### `generateCodeVerifier(): string`
37
+ ### `generateCodeVerifier(getRandomBytes): string`
28
38
 
29
- Generates a cryptographically secure random code verifier (43 characters, base64url encoded).
39
+ Generates a cryptographically secure code verifier (43 chars, base64url encoded).
30
40
 
31
- ### `createCodeChallenge(verifier: string): string`
41
+ - `getRandomBytes`: Function returning random bytes (Uint8Array, ArrayBuffer, or Buffer)
32
42
 
33
- Creates a code challenge by SHA-256 hashing the verifier and base64url encoding the result.
43
+ ### `createCodeChallenge(verifier): string`
34
44
 
35
- ## Requirements
45
+ Creates SHA-256 code challenge from verifier (43 chars, base64url encoded).
36
46
 
37
- Depends on [`@noble/hashes`](https://github.com/paulmillr/noble-hashes) for crypto functions.
47
+ ## Platform Support
38
48
 
39
- **React Native Setup:**
49
+ Works in all JavaScript environments. Provide your platform's crypto random bytes function:
40
50
 
41
- ```typescript
42
- // In your app/_layout.tsx (Expo) or App.tsx
43
- import { polyfillWebCrypto } from "expo-standard-web-crypto";
44
- polyfillWebCrypto();
45
- ```
51
+ - **Node.js**: `crypto.randomBytes`
52
+ - **Browser**: `(length) => crypto.getRandomValues(new Uint8Array(length))`
53
+ - **Expo/React Native**: `expo-crypto.getRandomBytes`
46
54
 
47
- For bare React Native:
55
+ ## Implementation Details
48
56
 
49
- ```bash
50
- npm install react-native-get-random-values
51
- import 'react-native-get-random-values';
52
- ```
57
+ - **SHA-256**: Pure JavaScript FIPS 180-4 implementation
58
+ - **Base64URL**: RFC 4648 compliant
59
+ - **Bundle size**: ~3KB minified
60
+ - **TypeScript**: Full type definitions included
53
61
 
54
62
  ## License
55
63
 
package/dist/index.d.ts CHANGED
@@ -1,14 +1,36 @@
1
1
  /**
2
- * Universal PKCE implementation using @noble/hashes
3
- * Works in Node.js, React Native, Expo, and browsers
2
+ * Random bytes generator function type
3
+ * Can return Buffer, Uint8Array, or ArrayBuffer
4
4
  */
5
+ export type RandomBytesGenerator = (length: number) => Uint8Array | ArrayBuffer | Buffer;
5
6
  /**
6
- * Generates a cryptographically secure random code verifier for PKCE
7
- * Uses @noble/hashes randomBytes (works in Node.js, browsers, React Native)
7
+ * Generates a random code verifier for PKCE
8
+ * @param getRandomBytes - Function that generates cryptographically secure random bytes
9
+ * @returns Base64url-encoded code verifier (43 characters)
10
+ *
11
+ * @example
12
+ * // Node.js
13
+ * import { randomBytes } from 'crypto';
14
+ * const verifier = generateCodeVerifier(randomBytes);
15
+ *
16
+ * @example
17
+ * // Expo
18
+ * import * as Crypto from 'expo-crypto';
19
+ * const verifier = generateCodeVerifier(Crypto.getRandomBytes);
20
+ *
21
+ * @example
22
+ * // Browser
23
+ * const verifier = generateCodeVerifier((length) => {
24
+ * const bytes = new Uint8Array(length);
25
+ * crypto.getRandomValues(bytes);
26
+ * return bytes;
27
+ * });
8
28
  */
9
- export declare function generateCodeVerifier(): string;
29
+ export declare function generateCodeVerifier(getRandomBytes: RandomBytesGenerator): string;
10
30
  /**
11
31
  * Creates a code challenge from a code verifier using SHA-256
12
- * Uses @noble/hashes sha256 (works in Node.js, browsers, React Native)
32
+ * Uses pure JavaScript SHA-256 implementation
33
+ * @param verifier - The code verifier to hash
34
+ * @returns Base64url-encoded SHA-256 hash of the verifier (43 characters)
13
35
  */
14
36
  export declare function createCodeChallenge(verifier: string): string;
package/dist/index.js CHANGED
@@ -1,74 +1,45 @@
1
+ import { uint8ArrayToBase64Url } from "./utils/base64";
2
+ import { sha256 } from "./utils/sha256";
1
3
  /**
2
- * Universal PKCE implementation using @noble/hashes
3
- * Works in Node.js, React Native, Expo, and browsers
4
+ * Generates a random code verifier for PKCE
5
+ * @param getRandomBytes - Function that generates cryptographically secure random bytes
6
+ * @returns Base64url-encoded code verifier (43 characters)
7
+ *
8
+ * @example
9
+ * // Node.js
10
+ * import { randomBytes } from 'crypto';
11
+ * const verifier = generateCodeVerifier(randomBytes);
12
+ *
13
+ * @example
14
+ * // Expo
15
+ * import * as Crypto from 'expo-crypto';
16
+ * const verifier = generateCodeVerifier(Crypto.getRandomBytes);
17
+ *
18
+ * @example
19
+ * // Browser
20
+ * const verifier = generateCodeVerifier((length) => {
21
+ * const bytes = new Uint8Array(length);
22
+ * crypto.getRandomValues(bytes);
23
+ * return bytes;
24
+ * });
4
25
  */
5
- import { sha256 } from "@noble/hashes/sha2";
6
- import { randomBytes } from "@noble/hashes/utils";
7
- /**
8
- * Generates a cryptographically secure random code verifier for PKCE
9
- * Uses @noble/hashes randomBytes (works in Node.js, browsers, React Native)
10
- */
11
- export function generateCodeVerifier() {
26
+ export function generateCodeVerifier(getRandomBytes) {
12
27
  // Generate 32 bytes (256 bits) of random data
13
- const randomBytesData = randomBytes(32);
28
+ const randomBytes = getRandomBytes(32);
29
+ // Ensure we have a Uint8Array
30
+ const bytes = randomBytes instanceof Uint8Array ? randomBytes : new Uint8Array(randomBytes);
14
31
  // Convert to base64url encoding (RFC 4648)
15
- return base64UrlEncode(randomBytesData);
32
+ return uint8ArrayToBase64Url(bytes);
16
33
  }
17
34
  /**
18
35
  * Creates a code challenge from a code verifier using SHA-256
19
- * Uses @noble/hashes sha256 (works in Node.js, browsers, React Native)
36
+ * Uses pure JavaScript SHA-256 implementation
37
+ * @param verifier - The code verifier to hash
38
+ * @returns Base64url-encoded SHA-256 hash of the verifier (43 characters)
20
39
  */
21
40
  export function createCodeChallenge(verifier) {
22
- // Convert string to bytes
23
- const bytes = stringToBytes(verifier);
24
- // Hash using SHA-256 from @noble/hashes
25
- const hash = sha256(bytes);
41
+ // Hash using pure JavaScript SHA-256
42
+ const hash = sha256(verifier);
26
43
  // Convert to base64url encoding
27
- return base64UrlEncode(hash);
28
- }
29
- /**
30
- * Converts a string to UTF-8 bytes
31
- */
32
- function stringToBytes(str) {
33
- const bytes = [];
34
- for (let i = 0; i < str.length; i++) {
35
- const code = str.charCodeAt(i);
36
- if (code < 0x80) {
37
- bytes.push(code);
38
- }
39
- else if (code < 0x800) {
40
- bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
41
- }
42
- else if (code < 0xd800 || code >= 0xe000) {
43
- bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
44
- }
45
- else {
46
- // Surrogate pair
47
- i++;
48
- const cp = 0x10000 + (((code & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
49
- bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
50
- }
51
- }
52
- return new Uint8Array(bytes);
53
- }
54
- /**
55
- * Converts Uint8Array to base64url encoding (RFC 4648)
56
- */
57
- function base64UrlEncode(bytes) {
58
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
59
- let result = "";
60
- for (let i = 0; i < bytes.length; i += 3) {
61
- const b1 = bytes[i];
62
- const b2 = i + 1 < bytes.length ? bytes[i + 1] : 0;
63
- const b3 = i + 2 < bytes.length ? bytes[i + 2] : 0;
64
- result += chars[b1 >> 2];
65
- result += chars[((b1 & 0x03) << 4) | (b2 >> 4)];
66
- if (i + 1 < bytes.length) {
67
- result += chars[((b2 & 0x0f) << 2) | (b3 >> 6)];
68
- }
69
- if (i + 2 < bytes.length) {
70
- result += chars[b3 & 0x3f];
71
- }
72
- }
73
- return result;
44
+ return uint8ArrayToBase64Url(hash);
74
45
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Converts a Uint8Array to base64url encoding (RFC 4648)
3
+ * Works in all JavaScript environments
4
+ */
5
+ export declare function uint8ArrayToBase64Url(bytes: Uint8Array): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a Uint8Array to base64url encoding (RFC 4648)
3
+ * Works in all JavaScript environments
4
+ */
5
+ export function uint8ArrayToBase64Url(bytes) {
6
+ let binary = '';
7
+ for (let i = 0; i < bytes.length; i++) {
8
+ binary += String.fromCharCode(bytes[i]);
9
+ }
10
+ return btoa(binary)
11
+ .replace(/\+/g, '-')
12
+ .replace(/\//g, '_')
13
+ .replace(/=/g, '');
14
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Pure JavaScript SHA-256 implementation
3
+ * Based on FIPS 180-4 specification
4
+ * Works in all JavaScript environments (Node.js, browsers, React Native, Expo)
5
+ */
6
+ /**
7
+ * Computes SHA-256 hash of input data
8
+ * @param data - Input string or byte array
9
+ * @returns SHA-256 hash as Uint8Array (32 bytes)
10
+ */
11
+ export declare function sha256(data: string | Uint8Array): Uint8Array;
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Pure JavaScript SHA-256 implementation
3
+ * Based on FIPS 180-4 specification
4
+ * Works in all JavaScript environments (Node.js, browsers, React Native, Expo)
5
+ */
6
+ // SHA-256 constants (first 32 bits of the fractional parts of the cube roots of the first 64 primes)
7
+ const K = new Uint32Array([
8
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
9
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
10
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
11
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
12
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
13
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
14
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
15
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
16
+ ]);
17
+ // Right rotate
18
+ function rotr(n, x) {
19
+ return (x >>> n) | (x << (32 - n));
20
+ }
21
+ // Convert string to UTF-8 bytes
22
+ function stringToBytes(str) {
23
+ const bytes = new Uint8Array(str.length);
24
+ for (let i = 0; i < str.length; i++) {
25
+ const code = str.charCodeAt(i);
26
+ if (code > 127) {
27
+ // Handle UTF-8 encoding for non-ASCII characters
28
+ return new TextEncoder().encode(str);
29
+ }
30
+ bytes[i] = code;
31
+ }
32
+ return bytes;
33
+ }
34
+ /**
35
+ * Computes SHA-256 hash of input data
36
+ * @param data - Input string or byte array
37
+ * @returns SHA-256 hash as Uint8Array (32 bytes)
38
+ */
39
+ export function sha256(data) {
40
+ // Convert input to bytes
41
+ const message = typeof data === 'string' ? stringToBytes(data) : data;
42
+ // Pre-processing: adding padding bits
43
+ const msgLength = message.length;
44
+ const bitLength = msgLength * 8;
45
+ // Calculate padding length (message + 1 bit + zeros + 64-bit length = multiple of 512 bits)
46
+ const paddingLength = (msgLength + 9 + 63) & ~63; // Round up to nearest multiple of 64
47
+ const padded = new Uint8Array(paddingLength);
48
+ // Copy message
49
+ padded.set(message);
50
+ // Append '1' bit (0x80 = 10000000 in binary)
51
+ padded[msgLength] = 0x80;
52
+ // Append length in bits as 64-bit big-endian integer
53
+ const view = new DataView(padded.buffer);
54
+ view.setUint32(paddingLength - 4, bitLength >>> 0, false); // Low 32 bits
55
+ // Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes)
56
+ const h = new Uint32Array([
57
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
58
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
59
+ ]);
60
+ // Process message in 512-bit (64-byte) chunks
61
+ const w = new Uint32Array(64);
62
+ for (let chunkStart = 0; chunkStart < padded.length; chunkStart += 64) {
63
+ // Break chunk into sixteen 32-bit big-endian words
64
+ for (let i = 0; i < 16; i++) {
65
+ const offset = chunkStart + i * 4;
66
+ w[i] = (padded[offset] << 24) | (padded[offset + 1] << 16) |
67
+ (padded[offset + 2] << 8) | padded[offset + 3];
68
+ }
69
+ // Extend the sixteen 32-bit words into sixty-four 32-bit words
70
+ for (let i = 16; i < 64; i++) {
71
+ const s0 = rotr(7, w[i - 15]) ^ rotr(18, w[i - 15]) ^ (w[i - 15] >>> 3);
72
+ const s1 = rotr(17, w[i - 2]) ^ rotr(19, w[i - 2]) ^ (w[i - 2] >>> 10);
73
+ w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
74
+ }
75
+ // Initialize working variables
76
+ let a = h[0], b = h[1], c = h[2], d = h[3];
77
+ let e = h[4], f = h[5], g = h[6], hh = h[7];
78
+ // Main compression loop
79
+ for (let i = 0; i < 64; i++) {
80
+ const S1 = rotr(6, e) ^ rotr(11, e) ^ rotr(25, e);
81
+ const ch = (e & f) ^ (~e & g);
82
+ const temp1 = (hh + S1 + ch + K[i] + w[i]) >>> 0;
83
+ const S0 = rotr(2, a) ^ rotr(13, a) ^ rotr(22, a);
84
+ const maj = (a & b) ^ (a & c) ^ (b & c);
85
+ const temp2 = (S0 + maj) >>> 0;
86
+ hh = g;
87
+ g = f;
88
+ f = e;
89
+ e = (d + temp1) >>> 0;
90
+ d = c;
91
+ c = b;
92
+ b = a;
93
+ a = (temp1 + temp2) >>> 0;
94
+ }
95
+ // Add compressed chunk to current hash value
96
+ h[0] = (h[0] + a) >>> 0;
97
+ h[1] = (h[1] + b) >>> 0;
98
+ h[2] = (h[2] + c) >>> 0;
99
+ h[3] = (h[3] + d) >>> 0;
100
+ h[4] = (h[4] + e) >>> 0;
101
+ h[5] = (h[5] + f) >>> 0;
102
+ h[6] = (h[6] + g) >>> 0;
103
+ h[7] = (h[7] + hh) >>> 0;
104
+ }
105
+ // Produce the final hash value (big-endian)
106
+ const hash = new Uint8Array(32);
107
+ for (let i = 0; i < 8; i++) {
108
+ hash[i * 4 + 0] = (h[i] >>> 24) & 0xff;
109
+ hash[i * 4 + 1] = (h[i] >>> 16) & 0xff;
110
+ hash[i * 4 + 2] = (h[i] >>> 8) & 0xff;
111
+ hash[i * 4 + 3] = h[i] & 0xff;
112
+ }
113
+ return hash;
114
+ }
package/package.json CHANGED
@@ -1,39 +1,26 @@
1
1
  {
2
2
  "name": "@bb-labs/pkce",
3
- "version": "0.0.3",
4
- "description": "A library for PKCE",
5
- "homepage": "https://github.com/beepbop-labs/pkce",
6
- "keywords": [
7
- "pkce",
8
- "pkce-challenge",
9
- "pkce-verifier"
10
- ],
11
- "author": "Beepbop",
12
- "license": "MIT",
13
- "repository": {
14
- "type": "git",
15
- "url": "https://github.com/beepbop-labs/pkce.git"
16
- },
3
+ "version": "0.0.5",
17
4
  "main": "dist/index.js",
18
5
  "files": [
19
- "dist",
20
- "README.md"
6
+ "dist"
21
7
  ],
22
8
  "exports": {
23
- ".": "./dist/index.js",
24
- "./node": "./dist/node/index.js",
25
- "./expo": "./dist/expo/index.js"
9
+ ".": "./dist/index.js"
26
10
  },
27
11
  "scripts": {
28
- "clean": "rm -rf dist",
29
- "build": "npm run clean && tsc -p tsconfig.json",
30
- "pack": "npm run build && rm -rf archive && mkdir -p archive && bun pm pack --destination ./archive && find ./archive -name '*.tgz' -exec mv {} ./archive/archive2.tgz \\;"
12
+ "build": "bunx @bb-labs/bldr",
13
+ "dev": "bunx @bb-labs/bldr -w",
14
+ "clean": "rm -rf node_modules bun.lock dist"
31
15
  },
32
16
  "dependencies": {
33
- "@noble/hashes": "^1.3.0"
17
+ "@bb-labs/bldr": "0.0.0",
18
+ "@bb-labs/tsconfigs": "^0.0.2"
34
19
  },
35
20
  "devDependencies": {
36
- "@types/bun": "latest"
21
+ "@types/bun": "latest",
22
+ "@types/node": "^25.0.10",
23
+ "tsc-alias": "^1.8.16"
37
24
  },
38
25
  "peerDependencies": {
39
26
  "typescript": "^5"