Skip to content

Commit

Permalink
add pkce lib
Browse files Browse the repository at this point in the history
  • Loading branch information
alvinsj committed Jun 8, 2024
1 parent 781bfff commit 647b8f6
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 0 deletions.
70 changes: 70 additions & 0 deletions src/lib/__tests__/pkce.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, test } from 'vitest'
import {
toSha256,
toBase64Url,
createRandomString,
createPKCECodeVerifier,
} from '../pkce'

describe('createRandomString', () => {
test('throws error on 0 length', () => {
expect(() => createRandomString(0))
.toThrow('length must be greater than 0')
})

test('creates string with length', () => {
const randomString43 = createRandomString(43)
const randomString128 = createRandomString(128)

expect(randomString43).toHaveLength(43)
expect(randomString128).toHaveLength(128)
})

test('creates unique strings', () => {
const randomString1 = createRandomString(43)
const randomString2 = createRandomString(43)

expect(randomString1).not.toEqual(randomString2)
console.log(randomString1)
})
})

describe('toSha256', () => {
test('throws error on empty string', async () => {
await expect(toSha256('')).rejects.toThrow('data is required')
})

test('hashes correctly', async () => {
const hash = await toSha256('hello')
const base64url = toBase64Url(hash)

// LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=
// but without + and =
expect(base64url).toEqual('LPJNul-wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ')
})
})

describe('toBase64Url', () => {
test('converts to base64url', () => {
const base64url = toBase64Url([1, 2, 3, 4, 5])

expect(base64url).toEqual('AQIDBAU')
})
})

describe('createPKCECodeVerifier', () => {
test('creates code challenge', async () => {
const codeVerifier = createRandomString(43)
const codeChallenge = await createPKCECodeVerifier(codeVerifier)

expect(codeChallenge).toHaveLength(43)
})

test('creates unique code challenge', async () => {
const codeVerifier = createRandomString(43)
const codeChallenge1 = await createPKCECodeVerifier(codeVerifier)
const codeChallenge2 = await createPKCECodeVerifier(codeVerifier)

expect(codeChallenge1).toEqual(codeChallenge2)
})
})
43 changes: 43 additions & 0 deletions src/lib/pkce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Sha256 } from '@aws-crypto/sha256-js';

export const toSha256 = async (data: string): Promise<Uint8Array> => {
if (!data) throw new Error('data is required');

const hash = new Sha256();
hash.update(data);

const hashed = await hash.digest();
return hashed;
}

// refer to base64url-encoding in RFC 7636
// https://datatracker.ietf.org/doc/html/rfc7636#appendix-A
export const toBase64Url = (bytes: Uint8Array) => {
const charCodes = Array.from(bytes);
let str = btoa(String.fromCharCode.apply(null, charCodes));
str = str.split('=')[0];
str = str.replace(/\+/g, '-');
str = str.replace(/\//g, '_');
return str;
}

// refer to random string generation in RFC 7636
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
export const createRandomString = (length: number = 34): string => {
if (length === 0) throw new Error('length must be greater than 0');

const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
let randomString = '';
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * charset.length);
randomString += charset[randomIndex];
}
return randomString;
}

export const createPKCECodeVerifier = async (str: string): string => {
const hashed: Uint8Array = await toSha256(str)
const codeVerifier = toBase64Url(hashed)
return codeVerifier;
}

0 comments on commit 647b8f6

Please sign in to comment.