-
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(js): generate state, code verifier and code challenge (#125)
- Loading branch information
Showing
6 changed files
with
122 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1 @@ | ||
// Temporary content, for `pnpm build` to pass CI, will be deleted later | ||
import { Optional } from '@silverhand/essentials'; | ||
|
||
export type TODO = Optional<string>; | ||
export * from './utils/generators'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/** | ||
* @jest-environment node | ||
*/ | ||
import { toUint8Array } from 'js-base64'; | ||
|
||
import { generateCodeChallenge, generateCodeVerifier, generateState } from './generators'; | ||
|
||
const isUrlSafe = (input: string) => /^[\w-]*$/.test(input); | ||
|
||
describe('generateState', () => { | ||
test('should be random value', () => { | ||
const state1 = generateState(); | ||
const state2 = generateState(); | ||
expect(state1).not.toEqual(state2); | ||
}); | ||
|
||
test('should be url-safe', () => { | ||
const state = generateState(); | ||
expect(isUrlSafe(state)).toBeTruthy(); | ||
}); | ||
|
||
test('raw random data length should be length 64', () => { | ||
const state = generateState(); | ||
expect(toUint8Array(state).length).toEqual(64); | ||
}); | ||
}); | ||
|
||
describe('generateCodeVerifier', () => { | ||
test('should be random value', () => { | ||
const codeVerifier1 = generateCodeVerifier(); | ||
const codeVerifier2 = generateCodeVerifier(); | ||
expect(codeVerifier1).not.toEqual(codeVerifier2); | ||
}); | ||
|
||
test('should be url-safe', () => { | ||
const codeVerifier = generateCodeVerifier(); | ||
expect(isUrlSafe(codeVerifier)).toBeTruthy(); | ||
}); | ||
|
||
test('raw random data length should be length 64', () => { | ||
const codeVerifier = generateCodeVerifier(); | ||
expect(toUint8Array(codeVerifier).length).toEqual(64); | ||
}); | ||
}); | ||
|
||
describe('generateCodeChallenge', () => { | ||
test('dealing with different code verifiers should not be equal', async () => { | ||
const codeVerifier1 = generateCodeVerifier(); | ||
const codeChallenge1 = await generateCodeChallenge(codeVerifier1); | ||
const codeVerifier2 = generateCodeVerifier(); | ||
const codeChallenge2 = await generateCodeChallenge(codeVerifier2); | ||
expect(codeChallenge1).not.toEqual(codeChallenge2); | ||
}); | ||
|
||
test('dealing with same code verifier should be equal', async () => { | ||
const codeVerifier = generateCodeVerifier(); | ||
const codeChallenge1 = await generateCodeChallenge(codeVerifier); | ||
const codeChallenge2 = await generateCodeChallenge(codeVerifier); | ||
expect(codeChallenge1).toEqual(codeChallenge2); | ||
}); | ||
|
||
describe('dealing with static code verifier should not throw', () => { | ||
test('dealing with url-safe code verifier should not throw', async () => { | ||
const codeVerifier = | ||
'tO6MabnMFRAatnlMa1DdSstypzzkgalL1-k8Hr_GdfTj-VXGiEACqAkSkDhFuAuD8FOU8lMishaXjt29Xt2Oww'; | ||
const codeChallenge = await generateCodeChallenge(codeVerifier); | ||
expect(codeChallenge).toEqual('0K3SLeGlNNzFswYJjcVzcN4C76m_8NZORxFJLBJWGwg'); | ||
}); | ||
|
||
describe('dealing with non-url-safe code verifier should not throw', () => { | ||
test('latin1 character', async () => { | ||
const codeVerifier = 'Á'; | ||
const codeChallenge = await generateCodeChallenge(codeVerifier); | ||
expect(codeChallenge).toEqual('p3yvZiKYauPicLIDZ0W1peDz4Z9KFC-9uxtDfoO1KOQ'); | ||
}); | ||
|
||
test('emoji character', async () => { | ||
const codeVerifier = '🚀'; | ||
const codeChallenge = await generateCodeChallenge(codeVerifier); | ||
expect(codeChallenge).toEqual('67wLKHDrMj8rbP-lxJPO74GufrNq_HPU4DZzAWMdrsU'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
/** @link [Proof Key for Code Exchange by OAuth Public Clients](https://datatracker.ietf.org/doc/html/rfc7636) */ | ||
|
||
import { fromUint8Array } from 'js-base64'; | ||
|
||
/** | ||
* @param length The length of the raw random data. | ||
*/ | ||
const generateRandomString = (length = 64) => | ||
fromUint8Array(crypto.getRandomValues(new Uint8Array(length)), true); | ||
|
||
/** | ||
* Generates random string for state and encodes them in url safe base64 | ||
*/ | ||
export const generateState = () => generateRandomString(); | ||
|
||
/** | ||
* Generates code verifier | ||
* | ||
* @link [Client Creates a Code Verifier](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1) | ||
*/ | ||
export const generateCodeVerifier = () => generateRandomString(); | ||
|
||
/** | ||
* Calculates the S256 PKCE code challenge for an arbitrary code verifier and encodes it in url safe base64 | ||
* | ||
* @param {String} codeVerifier Code verifier to calculate the S256 code challenge for | ||
* @link [Client Creates the Code Challenge](https://datatracker.ietf.org/doc/html/rfc7636#section-4.2) | ||
*/ | ||
export const generateCodeChallenge = async (codeVerifier: string): Promise<string> => { | ||
const encodedCodeVerifier = new TextEncoder().encode(codeVerifier); | ||
const codeChallenge = new Uint8Array(await crypto.subtle.digest('SHA-256', encodedCodeVerifier)); | ||
return fromUint8Array(codeChallenge, true); | ||
}; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.