Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements #6

Merged
merged 7 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/sessionManager/stores/expoSecureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import { splitString } from "../utils.js";

let expoSecureStore: typeof import("expo-secure-store") | undefined = undefined;

async function waitForExpoSecureStore() {
let tries = 0;
while (!expoSecureStore && tries < 20) {
await new Promise((resolve) => setTimeout(resolve, 100));
tries++;
}
}

/**
* Provides a expo local store based session manager implementation for the browser.
* @class ExpoSecureStore
Expand Down Expand Up @@ -42,6 +50,7 @@ export class ExpoSecureStore<V = StorageKeys> implements SessionManager<V> {
itemKey: V | StorageKeys,
itemValue: unknown,
): Promise<void> {
await waitForExpoSecureStore();
// clear items first
await this.removeSessionItem(itemKey);

Expand All @@ -66,6 +75,8 @@ export class ExpoSecureStore<V = StorageKeys> implements SessionManager<V> {
* @returns {unknown | null}
*/
async getSessionItem(itemKey: V | StorageKeys): Promise<unknown | null> {
await waitForExpoSecureStore();

const chunks = [];
let index = 0;

Expand All @@ -82,7 +93,7 @@ export class ExpoSecureStore<V = StorageKeys> implements SessionManager<V> {
);
}

return chunks.join("");
return chunks.join("") || null;
}

/**
Expand All @@ -91,6 +102,8 @@ export class ExpoSecureStore<V = StorageKeys> implements SessionManager<V> {
* @returns {void}
*/
async removeSessionItem(itemKey: V | StorageKeys): Promise<void> {
await waitForExpoSecureStore();

let index = 0;

let chunk = await expoSecureStore!.getItemAsync(
Expand Down
4 changes: 4 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ export type LoginOptions = {
* Whether to show the success screen at the end of the flow, this is most useful when the callback is not a webpage.
*/
hasSuccessPage?: boolean;
/**
* Single use code to prevent replay attacks
*/
nonce?: string;
};

export enum IssuerRouteTypes {
Expand Down
18 changes: 13 additions & 5 deletions lib/utils/generateAuthUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ export const generateAuthUrl = (
...mapLoginMethodParamsForUrl(options),
};

const generatedState = generateRandomString(32);
const generatedNonce = generateRandomString(16);
if (!options.state) {
options.state = generateRandomString(32);
}
searchParams["state"] = options.state;

searchParams["state"] = options.state || generatedState;
searchParams["nonce"] = generatedNonce;
if (!options.nonce) {
options.nonce = generateRandomString(16);
}
searchParams["nonce"] = options.nonce;

if (options.codeChallenge) {
searchParams["code_challenge"] = options.codeChallenge;
Expand All @@ -38,5 +42,9 @@ export const generateAuthUrl = (
}

authUrl.search = new URLSearchParams(searchParams).toString();
return { url: authUrl, state: generatedState, nonce: generatedNonce };
return {
url: authUrl,
state: searchParams["state"],
nonce: searchParams["nonce"],
};
};
23 changes: 20 additions & 3 deletions lib/utils/generateRandomString.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,28 @@
* @returns {string} required secret
*/
export const generateRandomString = (length: number = 28): string => {
const arr = new Uint8Array(length / 2);
crypto.getRandomValues(arr);
return Array.from(arr, dec2hex).join("");
if (crypto) {
const arr = new Uint8Array(length / 2);
crypto.getRandomValues(arr);
return Array.from(arr, dec2hex).join("");
} else {
return generateRandomStringNonCrypto(length);
}
};

function dec2hex(dec: number) {
return dec.toString(16).padStart(2, "0");
}

function generateRandomStringNonCrypto(length: number = 28) {
const characters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
const charactersLength = characters.length;

for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}

return result;
}
Loading