-
Notifications
You must be signed in to change notification settings - Fork 4
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
feat: Expo Web support (utilising localStorage) #8
base: main
Are you sure you want to change the base?
Changes from 4 commits
15aabd9
2a5147b
528af19
d242522
7e52fe9
1e65459
ec9f0e8
473cda7
8be0868
c843b7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
export const DEFAULT_TOKEN_SCOPES: string = "openid profile email offline"; | ||
export const DEFAULT_PLATFORM = "native"; | ||
|
||
export enum StorageKeys { | ||
accessToken, | ||
idToken, | ||
state, | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { DEFAULT_PLATFORM } from "../constants"; | ||
import { NativeStorageProvider } from "./nativeProvider"; | ||
import { IStorageProvider } from "./storageProvider.interface"; | ||
import { WebStorageProvider } from "./webProvider"; | ||
|
||
/** | ||
* Storage provider factory | ||
* @param {StorageKeys} platform Key to switch the storage provider | ||
* @returns {Promise<void>} | ||
*/ | ||
export default function StorageProvider( | ||
platform: "web" | "native" = DEFAULT_PLATFORM | ||
): IStorageProvider { | ||
switch (platform) { | ||
case "web": | ||
return new WebStorageProvider(); | ||
default: | ||
return new NativeStorageProvider(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { deleteItemAsync, getItemAsync, setItemAsync } from "expo-secure-store"; | ||
import { StorageKeys } from "../constants"; | ||
import { IStorageProvider } from "./storageProvider.interface"; | ||
|
||
/** | ||
* Native storage provider (uses expo-secure-store) | ||
*/ | ||
export class NativeStorageProvider implements IStorageProvider { | ||
async setStorage(key: StorageKeys, value: string | null): Promise<void> { | ||
if (!value) { | ||
let index = 0; | ||
let chunk = await getItemAsync(`${key}-${index}`); | ||
while (chunk) { | ||
await deleteItemAsync(`${key}-${index}`); | ||
index++; | ||
chunk = await getItemAsync(`${key}-${index}`); | ||
} | ||
return; | ||
} | ||
if (value.length > 2048) { | ||
const chunks = value.match(/.{1,2048}/g); | ||
if (chunks) { | ||
chunks.forEach(async (chunk, index) => { | ||
await setItemAsync(`${key}-${index}`, chunk); | ||
}); | ||
} | ||
} else { | ||
await setItemAsync(`${key}-0`, value); | ||
} | ||
} | ||
|
||
async getStorage(key: StorageKeys): Promise<string | null> { | ||
const chunks = []; | ||
let index = 0; | ||
let chunk = await getItemAsync(`${key}-${index}`); | ||
while (chunk) { | ||
chunks.push(chunk); | ||
index++; | ||
chunk = await getItemAsync(`${key}-${index}`); | ||
} | ||
return chunks.join(""); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { StorageKeys } from "../constants"; | ||
|
||
export interface IStorageProvider { | ||
/** | ||
* Sets item in the storage | ||
* @param {StorageKeys} key Key to store the value | ||
* @param {string} value value to store in the storage | ||
* @returns {Promise<void>} | ||
*/ | ||
setStorage(key: StorageKeys, value: string | null): Promise<void>; | ||
|
||
/** | ||
* Get item from the storage | ||
* @param {StorageKeys} key Key to retrieve | ||
* @returns {Promise<string | null>} | ||
*/ | ||
getStorage(key: StorageKeys): Promise<string | null>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { StorageKeys } from "../constants"; | ||
import { IStorageProvider } from "./storageProvider.interface"; | ||
|
||
/** | ||
* Web storage provider (uses localStorage) | ||
*/ | ||
export class WebStorageProvider implements IStorageProvider { | ||
setStorage(key: StorageKeys, value: string | null): Promise<void> { | ||
if (!value || value.length === 0) { | ||
localStorage.removeItem(`${key}`); | ||
return Promise.resolve(); | ||
} | ||
|
||
localStorage.setItem(`${key}`, value); | ||
return Promise.resolve(); | ||
} | ||
|
||
getStorage(key: StorageKeys): Promise<string | null> { | ||
const item = localStorage.getItem(`${key}`); | ||
return Promise.resolve(item); | ||
} | ||
} | ||
Comment on lines
+7
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Security concern confirmed: Authentication tokens are stored in localStorage The verification confirms that sensitive authentication data (access tokens and ID tokens) from Kinde authentication are being stored directly in localStorage:
Recommendations:
🔗 Analysis chainBe mindful of storing tokens in localStorage. Let me gather more information about how this storage is being used. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Searching project files for usage of WebStorageProvider to confirm in which contexts we rely on localStorage
rg -A 5 "WebStorageProvider"
Length of output: 959 Script: #!/bin/bash
# Check what types of data are being stored using StorageKeys enum
rg -A 5 "enum StorageKeys"
# Search for actual usage of setStorage to understand what values are being stored
rg "setStorage\(" -A 3
Length of output: 1817 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { useContext } from "react"; | ||
|
||
import { | ||
LoginResponse, | ||
LogoutResult, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid using
forEach
with async callbacks.Inside the
forEach(async (chunk, index) => {...})
, the calls toawait setItemAsync
won't resolve in a predictable sequence sinceArray.forEach
doesn't await each iteration. If the order of execution matters, consider using a traditionalfor
loop orPromise.all
withmap
.📝 Committable suggestion