expo-passkey
Version:
Passkey authentication for Expo apps with Better Auth integration
220 lines • 7.9 kB
JavaScript
/**
* @file Storage utilities
* @module expo-passkey/utils/storage
*/
import { loadExpoModules } from "./modules";
// Helper function to get modules only when needed
function getModules() {
return loadExpoModules();
}
/**
* Gets storage keys based on the client options
* @param options Client options with optional storage prefix
* @returns Storage keys with the configured prefix
*/
export function getStorageKeys(options = {}) {
const prefix = options.storagePrefix || "_better-auth";
return {
DEVICE_ID: `${prefix}.device_id`,
STATE: `${prefix}.passkey_state`,
USER_ID: `${prefix}.user_id`,
CREDENTIAL_IDS: `${prefix}.credential_ids`,
};
}
/**
* Stores a credential ID with metadata in secure storage
* @param credentialId The credential ID to store
* @param userId The user ID associated with the credential
* @param options Client options with optional storage prefix
* @param additionalMetadata Additional metadata to store with the credential
* @returns Promise resolving when credential is stored
*/
export async function storeCredentialId(credentialId, userId, options = {}, additionalMetadata = {}) {
try {
const { SecureStore } = getModules();
const KEYS = getStorageKeys(options);
// Store the user ID
await SecureStore.setItemAsync(KEYS.USER_ID, userId);
// Get existing credential IDs
const existingIdsStr = await SecureStore.getItemAsync(KEYS.CREDENTIAL_IDS);
let credentials = {};
if (existingIdsStr) {
try {
credentials = JSON.parse(existingIdsStr);
}
catch (e) {
console.warn("Failed to parse stored credential IDs:", e);
}
}
const now = new Date().toISOString();
// Add the new credential ID with its metadata
credentials[credentialId] = {
userId,
credentialId,
registeredAt: credentials[credentialId]?.registeredAt || now,
lastUsedAt: now,
...additionalMetadata,
};
// Store the updated credential IDs
await SecureStore.setItemAsync(KEYS.CREDENTIAL_IDS, JSON.stringify(credentials));
}
catch (error) {
console.error("[ExpoPasskey] Error storing credential ID:", error);
throw error;
}
}
/**
* Gets all stored credential metadata
* @param options Client options with optional storage prefix
* @returns Promise resolving to an object mapping credential IDs to their metadata
*/
export async function getCredentialMetadata(options = {}) {
try {
const { SecureStore } = getModules();
const KEYS = getStorageKeys(options);
const existingIdsStr = await SecureStore.getItemAsync(KEYS.CREDENTIAL_IDS);
if (!existingIdsStr) {
return {};
}
try {
return JSON.parse(existingIdsStr);
}
catch (e) {
console.warn("[ExpoPasskey] Failed to parse stored credential IDs:", e);
return {};
}
}
catch (error) {
console.error("[ExpoPasskey] Error getting credential metadata:", error);
return {};
}
}
/**
* Gets credential IDs for a specific user
* @param userId User ID to filter credentials by
* @param options Client options with optional storage prefix
* @returns Promise resolving to array of credential IDs for the user
*/
export async function getUserCredentialIds(userId, options = {}) {
try {
const credentials = await getCredentialMetadata(options);
return Object.values(credentials)
.filter((cred) => cred.userId === userId)
.map((cred) => cred.credentialId);
}
catch (error) {
console.error("[ExpoPasskey] Error getting user credential IDs:", error);
return [];
}
}
/**
* Updates the last used timestamp for a credential
* @param credentialId The credential ID to update
* @param options Client options with optional storage prefix
* @returns Promise resolving when credential is updated
*/
export async function updateCredentialLastUsed(credentialId, options = {}) {
try {
const { SecureStore } = getModules();
const KEYS = getStorageKeys(options);
// Get existing credential IDs
const existingIdsStr = await SecureStore.getItemAsync(KEYS.CREDENTIAL_IDS);
if (!existingIdsStr) {
return;
}
let credentials = {};
try {
credentials = JSON.parse(existingIdsStr);
}
catch (e) {
console.warn("[ExpoPasskey] Failed to parse stored credential IDs:", e);
return;
}
// Check if credential exists
if (!credentials[credentialId]) {
return;
}
// Update last used
credentials[credentialId].lastUsedAt = new Date().toISOString();
// Store updated credentials
await SecureStore.setItemAsync(KEYS.CREDENTIAL_IDS, JSON.stringify(credentials));
// console.debug(
// "[ExpoPasskey] Updated credential last used time:",
// credentialId,
// );
}
catch (error) {
console.error("[ExpoPasskey] Error updating credential last used time:", error);
}
}
/**
* Removes a credential ID from storage
* @param credentialId The credential ID to remove
* @param options Client options with optional storage prefix
* @returns Promise resolving when credential is removed
*/
export async function removeCredentialId(credentialId, options = {}) {
try {
const { SecureStore } = getModules();
const KEYS = getStorageKeys(options);
// Get existing credential IDs
const existingIdsStr = await SecureStore.getItemAsync(KEYS.CREDENTIAL_IDS);
if (!existingIdsStr) {
return;
}
let credentials = {};
try {
credentials = JSON.parse(existingIdsStr);
}
catch (e) {
console.warn("[ExpoPasskey] Failed to parse stored credential IDs:", e);
return;
}
// Remove the credential ID
delete credentials[credentialId];
// Update storage
await SecureStore.setItemAsync(KEYS.CREDENTIAL_IDS, JSON.stringify(credentials));
// console.debug("[ExpoPasskey] Removed credential ID:", credentialId);
// If there are no more credentials, also remove the user ID
if (Object.keys(credentials).length === 0) {
await SecureStore.deleteItemAsync(KEYS.USER_ID);
}
}
catch (error) {
console.error("[ExpoPasskey] Error removing credential ID:", error);
throw error;
}
}
/**
* Checks if a credential ID exists in storage
* @param credentialId The credential ID to check
* @param options Client options with optional storage prefix
* @returns Promise resolving to boolean indicating if credential exists
*/
export async function hasCredentialId(credentialId, options = {}) {
try {
const credentials = await getCredentialMetadata(options);
return !!credentials[credentialId];
}
catch (error) {
console.error("[ExpoPasskey] Error checking credential ID:", error);
return false;
}
}
/**
* Gets the user ID associated with a credential ID
* @param credentialId The credential ID to get user ID for
* @param options Client options with optional storage prefix
* @returns Promise resolving to user ID or null if not found
*/
export async function getUserIdForCredential(credentialId, options = {}) {
try {
const credentials = await getCredentialMetadata(options);
return credentials[credentialId]?.userId || null;
}
catch (error) {
console.error("[ExpoPasskey] Error getting user ID for credential:", error);
return null;
}
}
//# sourceMappingURL=storage.js.map