ndwallet-core
Version:
Core cryptographic library for NDWallet browser environments
250 lines (249 loc) • 8.52 kB
JavaScript
import axios from 'axios';
import { getLocalData, KEY_AUTH_TOKEN } from '../storage';
let serverConfig = null;
/**
* Normalize a string by converting it to lowercase and trimming whitespace
* @param value - The string to normalize
* @returns The normalized string
*/
function _normalizeString(value) {
return value.toLowerCase().trim();
}
/**
* Set server configuration for user module
* @param config - Configuration object containing server URL and API key
*/
export function setServerConfig(config) {
serverConfig = config;
}
/**
* Check if a user exists by email
* @param email - The user's email address
* @returns Promise resolving to a boolean indicating if the user exists
*/
export async function checkUserExists(email) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
const response = await axios.get(`${serverConfig.serverUrl}/users/check-email`, {
params: { email },
headers: { 'X-API-KEY': serverConfig.apiKey }
});
return response.data.exists;
}
catch (error) {
console.error('Error checking user existence:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to check user existence: ${String(error)}`);
}
}
}
/**
* Start the authentication process for a user
* @param email - The user's email address
* @param credentialId - The user's credential ID (optional)
* @param deviceToken - The device token for the user (optional)
* @returns Authentication options and server data
*/
export async function startUserAuthentication(email, credentialId, deviceToken) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
const response = await axios.post(`${serverConfig.serverUrl}/auth/authenticate/start`, {
email,
credentialId,
deviceToken
});
return response.data;
}
catch (error) {
console.error('Error starting authentication:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to start authentication: ${String(error)}`);
}
}
}
/**
* Complete the authentication process
* @param credentialId - The user's credential ID
* @param authenticationResponse - The WebAuthn response from the authenticator
* @returns Authentication completion data
*/
export async function completeUserAuthentication(credentialId, authenticationResponse) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
const serverResponse = await axios.post(`${serverConfig.serverUrl}/auth/authenticate/complete`, {
credentialId,
authenticationResponse
});
return serverResponse.data;
}
catch (error) {
console.error('Error completing authentication:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to complete authentication: ${String(error)}`);
}
}
}
/**
* Start the registration process for a new user
* @param email - The user's email address
* @param serverShare - The encrypted share for the server data
* @param backupShare - The encrypted share for the backup data
* @param secretQuestionId - The security question ID (optional)
* @param deviceToken - The device token for the user (optional)
* @returns Registration options and server data
*/
export async function startUserRegistration(email, serverShare, backupShare, secretQuestionId, deviceToken) {
if (!serverConfig) {
throw new Error('User configuration not set. Please call setUserConfig first.');
}
try {
const response = await axios.post(`${serverConfig.serverUrl}/auth/register/start`, {
email,
serverShare,
ipfsShare: backupShare,
secretQuestionId,
deviceToken
});
return response.data;
}
catch (error) {
console.error('Error starting registration:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to start registration: ${String(error)}`);
}
}
}
/**
* Complete the registration process
* @param email - The user's email address
* @param deviceServerShare - The encrypted share for the server data (using device passkey)
* @param registrationResponse - The WebAuthn response from the authenticator
* @param prfSalt - The PRF salt used for key derivation
* @returns Registration completion data
*/
export async function completeUserRegistration(email, deviceId, deviceServerShare, registrationResponse, prfSalt) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
const serverResponse = await axios.post(`${serverConfig.serverUrl}/auth/register/complete`, {
email,
deviceId,
deviceServerShare,
registrationResponse,
prfSalt
});
return serverResponse.data;
}
catch (error) {
console.error('Error completing registration:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to complete registration: ${String(error)}`);
}
}
}
/**
* Fetch user data.
* @param authToken - The user's authentication token
* @returns User data including credential details and encrypted server share
*/
async function _fetchUserData(authToken) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
const response = await axios.get(`${serverConfig.serverUrl}/users/me`, {
headers: { 'X-API-KEY': serverConfig.apiKey, 'Authorization': `Bearer ${authToken}` }
});
return response.data;
}
catch (error) {
console.error('Error fetching user by token:', error);
if (error instanceof Error) {
throw error;
}
else {
throw new Error(`Failed to fetch user by token: ${String(error)}`);
}
}
}
/**
* Get user data.
* @param email - The user's email address
* @returns User data including credential details and encrypted server share
*/
export async function getUserData(email) {
const authToken = getLocalData(email, KEY_AUTH_TOKEN);
if (!authToken) {
throw new Error('No authentication token found');
}
const userData = await _fetchUserData(authToken);
const { user, credential } = userData;
return {
id: user.id,
email: user.email,
credential
};
}
/**
* Fetch the list of secret questions from the server
* @returns Promise resolving to an array of SecretQuestion objects
*/
export async function allSecretQuestions() {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
// Make the API request using axios
const response = await axios.get(`${serverConfig.serverUrl}/auth/secret-questions/`, {
headers: { 'X-API-KEY': serverConfig.apiKey }
});
// Return the questions
return response.data;
}
/**
* Get the secret question for a specific user
* @param email - The user's email address
* @returns The secret question object or null if not found
*/
export async function getSecretQuestion(email) {
if (!serverConfig) {
throw new Error('Server configuration not set. Please call setServerConfig first.');
}
try {
// Normalize email to lowercase
const normalizedEmail = _normalizeString(email);
// Make the request to get the question for this email
const response = await axios.post(`${serverConfig.serverUrl}/auth/recovery/get-question`, {
email: normalizedEmail
}, {
headers: { 'X-API-KEY': serverConfig.apiKey, 'Content-Type': 'application/json' }
});
// Check if we got a valid response with a question
return response.data;
}
catch (error) {
console.error('Error fetching secret question:', error);
return null;
}
}