Skip to content
Open
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
3 changes: 2 additions & 1 deletion src/lib/ai.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { decryptMessage, encryptMessage } from "./encryption";
import { getAttestation } from "./getAttestation";
import * as api from "./api";
import { getStorage } from "./storage";

export interface CustomFetchOptions {
apiKey?: string; // Optional API key to use instead of JWT token
Expand All @@ -18,7 +19,7 @@ export function createCustomFetch(
}

// Otherwise, use the standard JWT token
const currentAccessToken = window.localStorage.getItem("access_token");
const currentAccessToken = getStorage().persistent.getItem("access_token");
if (!currentAccessToken) {
throw new Error("No access token or API key available");
}
Expand Down
49 changes: 25 additions & 24 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { encode } from "@stablelib/base64";
import { authenticatedApiCall, encryptedApiCall, openAiAuthenticatedApiCall } from "./encryptedApi";
import type { Model } from "openai/resources/models.js";
import { getConfig } from "./config";
import { getStorage } from "./storage";

export function getApiUrl(): string {
return getConfig().apiUrl;
Expand Down Expand Up @@ -50,8 +51,8 @@ export async function fetchLogin(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
}
Expand All @@ -68,8 +69,8 @@ export async function fetchGuestLogin(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
}
Expand Down Expand Up @@ -99,8 +100,8 @@ export async function fetchSignUp(
});

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
}
Expand All @@ -120,14 +121,14 @@ export async function fetchGuestSignUp(
});

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
}

export async function refreshToken(): Promise<RefreshResponse> {
const refresh_token = window.localStorage.getItem("refresh_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");
if (!refresh_token) throw new Error("No refresh token available");

const refreshData = { refresh_token };
Expand All @@ -141,8 +142,8 @@ export async function refreshToken(): Promise<RefreshResponse> {
"Failed to refresh token"
);

window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);
return response;
} catch (error) {
console.error("Error refreshing token:", error);
Expand Down Expand Up @@ -211,7 +212,7 @@ export async function fetchList(): Promise<KVListItem[]> {
}

export async function fetchLogout(): Promise<void> {
const refresh_token = window.localStorage.getItem("refresh_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");

if (refresh_token) {
try {
Expand All @@ -222,10 +223,10 @@ export async function fetchLogout(): Promise<void> {
}
}

localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
sessionStorage.removeItem("sessionKey");
sessionStorage.removeItem("sessionId");
getStorage().persistent.removeItem("access_token");
getStorage().persistent.removeItem("refresh_token");
getStorage().session.removeItem("sessionKey");
getStorage().session.removeItem("sessionId");
}

export async function verifyEmail(code: string): Promise<void> {
Expand Down Expand Up @@ -374,8 +375,8 @@ export async function handleGitHubCallback(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
} catch (error) {
Expand Down Expand Up @@ -460,8 +461,8 @@ export async function handleGoogleCallback(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
} catch (error) {
Expand Down Expand Up @@ -555,8 +556,8 @@ export async function handleAppleCallback(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
} catch (error) {
Expand Down Expand Up @@ -646,8 +647,8 @@ export async function handleAppleNativeSignIn(
);

// Store tokens automatically
window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);

return response;
} catch (error) {
Expand Down
18 changes: 17 additions & 1 deletion src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@
* Global configuration for OpenSecret SDK
*/

import { setStorageProvider, resetStorage, type StorageProvider } from "./storage";

export interface OpenSecretConfig {
apiUrl: string;
clientId: string;
/**
* Storage provider backing access tokens and session state.
* Browser apps can pass the exported `browserStorage` helper;
* non-browser consumers implement the interface against their own store.
*/
storage: StorageProvider;
}

let config: OpenSecretConfig | null = null;
Expand Down Expand Up @@ -35,9 +43,16 @@ export function configure(options: OpenSecretConfig): void {
throw new Error('OpenSecret SDK requires a non-empty clientId');
}

if (!options.storage) {
throw new Error('OpenSecret SDK requires a storage provider on configure({ storage })');
}

setStorageProvider(options.storage);

config = {
apiUrl: options.apiUrl.replace(/\/$/, ''), // Remove trailing slash
clientId: options.clientId
clientId: options.clientId,
storage: options.storage
};
}

Expand Down Expand Up @@ -66,4 +81,5 @@ export function isConfigured(): boolean {
*/
export function resetConfig(): void {
config = null;
resetStorage();
}
21 changes: 11 additions & 10 deletions src/lib/developer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from "./attestationForView";
import type { AttestationDocument } from "./attestation";
import { PcrConfig } from "./pcr";
import { getStorage } from "./storage";
import type {
Organization,
Project,
Expand Down Expand Up @@ -523,8 +524,8 @@ export function OpenSecretDeveloper({
}, [apiUrl]);

async function fetchDeveloper() {
const access_token = window.localStorage.getItem("access_token");
const refresh_token = window.localStorage.getItem("refresh_token");
const access_token = getStorage().persistent.getItem("access_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");
if (!access_token || !refresh_token) {
setAuth({
loading: false,
Expand Down Expand Up @@ -552,7 +553,7 @@ export function OpenSecretDeveloper({
}

const getAttestationDocument = async () => {
const nonce = window.crypto.randomUUID();
const nonce = globalThis.crypto.randomUUID();
const response = await fetch(`${apiUrl}/attestation/${nonce}`);
if (!response.ok) {
throw new Error("Failed to fetch attestation document");
Expand All @@ -574,8 +575,8 @@ export function OpenSecretDeveloper({
async function signIn(email: string, password: string) {
try {
const { access_token, refresh_token } = await platformApi.platformLogin(email, password);
window.localStorage.setItem("access_token", access_token);
window.localStorage.setItem("refresh_token", refresh_token);
getStorage().persistent.setItem("access_token", access_token);
getStorage().persistent.setItem("refresh_token", refresh_token);
await fetchDeveloper();
return { access_token, refresh_token, id: "", email };
} catch (error) {
Expand All @@ -592,8 +593,8 @@ export function OpenSecretDeveloper({
invite_code,
name
);
window.localStorage.setItem("access_token", access_token);
window.localStorage.setItem("refresh_token", refresh_token);
getStorage().persistent.setItem("access_token", access_token);
getStorage().persistent.setItem("refresh_token", refresh_token);
await fetchDeveloper();
return { access_token, refresh_token, id: "", email, name };
} catch (error) {
Expand All @@ -608,16 +609,16 @@ export function OpenSecretDeveloper({
signUp,
refetchDeveloper: fetchDeveloper,
signOut: async () => {
const refresh_token = window.localStorage.getItem("refresh_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");
if (refresh_token) {
try {
await platformApi.platformLogout(refresh_token);
} catch (error) {
console.error("Error during logout:", error);
}
}
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
getStorage().persistent.removeItem("access_token");
getStorage().persistent.removeItem("refresh_token");
setAuth({
loading: false,
developer: undefined
Expand Down
3 changes: 2 additions & 1 deletion src/lib/encryptedApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { getAttestation } from "./getAttestation";
import { refreshToken } from "./api";
import { platformRefreshToken } from "./platformApi";
import { apiConfig } from "./apiConfig";
import { getStorage } from "./storage";

interface EncryptedResponse {
encrypted: string;
Expand Down Expand Up @@ -37,7 +38,7 @@ export async function authenticatedApiCall<T, U>(
}

// Always get the latest token from localStorage
const accessToken = window.localStorage.getItem("access_token");
const accessToken = getStorage().persistent.getItem("access_token");
if (!accessToken) {
throw new Error("No access token available");
}
Expand Down
11 changes: 6 additions & 5 deletions src/lib/getAttestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { keyExchange } from "./api";
import nacl from "tweetnacl";
import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305";
import { encode, decode } from "@stablelib/base64";
import { getStorage } from "./storage";

export interface Attestation {
sessionKey: Uint8Array | null;
Expand Down Expand Up @@ -30,8 +31,8 @@ export async function getAttestation(
explicitApiUrl?: string
): Promise<Attestation> {
// Check if we already have a sessionKey and sessionId in sessionstorage
const sessionKey = sessionStorage.getItem("sessionKey");
const sessionId = sessionStorage.getItem("sessionId");
const sessionKey = getStorage().session.getItem("sessionKey");
const sessionId = getStorage().session.getItem("sessionId");

console.groupCollapsed("Attestation");

Expand All @@ -45,7 +46,7 @@ export async function getAttestation(

// Need to get a new attestation
// (Will use test nonce if provided)
const attestationNonce = window.crypto.randomUUID();
const attestationNonce = globalThis.crypto.randomUUID();

console.log("Generated attestation nonce:", attestationNonce);
const document = await verifyAttestation(attestationNonce, explicitApiUrl);
Expand Down Expand Up @@ -76,8 +77,8 @@ export async function getAttestation(

if (decryptedSessionKey) {
console.log("Session key decrypted successfully");
window.sessionStorage.setItem("sessionKey", encode(decryptedSessionKey));
window.sessionStorage.setItem("sessionId", session_id);
getStorage().session.setItem("sessionKey", encode(decryptedSessionKey));
getStorage().session.setItem("sessionId", session_id);
return { sessionKey: decryptedSessionKey, sessionId: session_id };
} else {
throw new Error("Failed to decrypt session key");
Expand Down
4 changes: 4 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ export type { Model } from "openai/resources/models.js";
export { configure, getConfig, isConfigured, resetConfig } from "./config";
export type { OpenSecretConfig } from "./config";

// Export storage abstraction
export { getStorage, setStorageProvider, resetStorage, browserStorage } from "./storage";
export type { StorageProvider } from "./storage";

// Export API configuration
export { apiConfig, type ApiContext, type ApiEndpoint } from "./apiConfig";

Expand Down
12 changes: 7 additions & 5 deletions src/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { AttestationDocument } from "./attestation";
import type { LoginResponse, ThirdPartyTokenResponse, DocumentResponse } from "./api";
import { PcrConfig } from "./pcr";
import { configure } from "./config";
import { browserStorage, getStorage } from "./storage";

export type OpenSecretAuthState = {
loading: boolean;
Expand Down Expand Up @@ -1027,8 +1028,9 @@ export function OpenSecretProvider({
);
}

// Configure the SDK with the provided values
configure({ apiUrl, clientId });
// Configure the SDK with the provided values. OpenSecretProvider is a React DOM
// component, so browserStorage is always the correct backing store here.
configure({ apiUrl, clientId, storage: browserStorage });
}, [apiUrl, clientId]);

// Create aiCustomFetch when API is configured (supports JWT or API key internally)
Expand All @@ -1042,8 +1044,8 @@ export function OpenSecretProvider({
}, [apiUrl, apiKey]);

async function fetchUser() {
const access_token = window.localStorage.getItem("access_token");
const refresh_token = window.localStorage.getItem("refresh_token");
const access_token = getStorage().persistent.getItem("access_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");
if (!access_token || !refresh_token) {
setAuth({
loading: false,
Expand Down Expand Up @@ -1232,7 +1234,7 @@ export function OpenSecretProvider({
};

const getAttestationDocument = async () => {
const nonce = window.crypto.randomUUID();
const nonce = globalThis.crypto.randomUUID();
const response = await fetch(`${apiUrl}/attestation/${nonce}`);
if (!response.ok) {
throw new Error("Failed to fetch attestation document");
Expand Down
7 changes: 4 additions & 3 deletions src/lib/platformApi.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { encryptedApiCall, authenticatedApiCall } from "./encryptedApi";
import { getStorage } from "./storage";

// Platform Auth Types
export type PlatformLoginResponse = {
Expand Down Expand Up @@ -182,7 +183,7 @@ export async function platformLogout(refresh_token: string): Promise<void> {
* It returns new access and refresh tokens if validation succeeds.
*/
export async function platformRefreshToken(): Promise<PlatformRefreshResponse> {
const refresh_token = window.localStorage.getItem("refresh_token");
const refresh_token = getStorage().persistent.getItem("refresh_token");
if (!refresh_token) throw new Error("No refresh token available");

const refreshData = { refresh_token };
Expand All @@ -196,8 +197,8 @@ export async function platformRefreshToken(): Promise<PlatformRefreshResponse> {
"Failed to refresh platform token"
);

window.localStorage.setItem("access_token", response.access_token);
window.localStorage.setItem("refresh_token", response.refresh_token);
getStorage().persistent.setItem("access_token", response.access_token);
getStorage().persistent.setItem("refresh_token", response.refresh_token);
return response;
} catch (error) {
console.error("Error refreshing platform token:", error);
Expand Down
Loading