55 lines
2 KiB
TypeScript
55 lines
2 KiB
TypeScript
import { sha256 } from '@noble/hashes/sha256'
|
|
|
|
type DigestAlgorithm = string | { readonly name?: unknown }
|
|
type DigestInput = ArrayBuffer | ArrayBufferView
|
|
|
|
interface PkceSubtleCrypto {
|
|
digest?: (algorithm: DigestAlgorithm, data: DigestInput) => Promise<ArrayBuffer>
|
|
}
|
|
|
|
export interface PkceCryptoTarget {
|
|
getRandomValues?: <T extends ArrayBufferView>(array: T) => T
|
|
subtle?: PkceSubtleCrypto
|
|
}
|
|
|
|
function normalizeAlgorithm(algorithm: DigestAlgorithm): string {
|
|
const name = typeof algorithm === 'string' ? algorithm : algorithm.name
|
|
return typeof name === 'string' ? name.trim().toUpperCase().replaceAll('_', '-') : ''
|
|
}
|
|
|
|
function inputBytes(data: DigestInput): Uint8Array {
|
|
if (data instanceof ArrayBuffer) return new Uint8Array(data)
|
|
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
}
|
|
|
|
/**
|
|
* Supabase Auth requires WebCrypto SHA-256 to generate an S256 PKCE challenge.
|
|
* React Native's native random-values shim supplies CSPRNG bytes but not
|
|
* `crypto.subtle`; without this narrow digest implementation auth-js silently
|
|
* downgrades to `code_challenge_method=plain`.
|
|
*/
|
|
export function installPkceS256(target: PkceCryptoTarget | undefined): void {
|
|
if (!target || typeof target.getRandomValues !== 'function') {
|
|
throw new Error('pkce_native_csprng_unavailable')
|
|
}
|
|
if (typeof target.subtle?.digest === 'function') return
|
|
|
|
const digest = async (algorithm: DigestAlgorithm, data: DigestInput): Promise<ArrayBuffer> => {
|
|
if (normalizeAlgorithm(algorithm) !== 'SHA-256') {
|
|
throw new Error('pkce_digest_algorithm_unsupported')
|
|
}
|
|
if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
|
|
throw new Error('pkce_digest_input_invalid')
|
|
}
|
|
return new Uint8Array(sha256(inputBytes(data))).buffer
|
|
}
|
|
|
|
Object.defineProperty(target, 'subtle', {
|
|
value: Object.freeze({ digest }),
|
|
enumerable: true,
|
|
configurable: false,
|
|
writable: false,
|
|
})
|
|
}
|
|
|
|
installPkceS256(globalThis.crypto as unknown as PkceCryptoTarget | undefined)
|