Initial Version

This commit is contained in:
root
2025-12-21 10:09:54 -05:00
commit 4ae6befc7b
422 changed files with 47225 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
import type { ChallengeResponse, IdentifyResponse, RedirectResponse, SessionStatus, StartResponse, VerifyResponse } from '@KTXC/types/authenticationTypes';
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper';
export const authenticationService = {
/**
* Initialize authentication - get session and available methods
*/
async start(): Promise<StartResponse> {
return fetchWrapper.get('/auth/start', undefined, { skipLogoutOnError: true });
},
/**
* Identify user - stores identity in session for identity-first flow
* Returns tenant-wide methods (no user-specific filtering to prevent enumeration)
*
* @param session - Session ID from start
* @param identity - User identity (email/username)
*/
async identify(session: string, identity: string): Promise<IdentifyResponse> {
return fetchWrapper.post('/auth/identify', {
session,
identity,
}, { skipLogoutOnError: true });
},
/**
* Verify a factor (primary or secondary)
*
* @param session - Session ID from init
* @param method - Provider/method ID (e.g., 'default', 'totp')
* @param response - User's response (password, code, etc.)
* @param identity - User identity for credential-based auth (email/username)
*/
async verify(
session: string,
method: string,
response: string,
identity?: string
): Promise<VerifyResponse> {
return fetchWrapper.post('/auth/verify', {
session,
method,
response,
...(identity && { identity }),
}, { autoRetry: false, skipLogoutOnError: true });
},
/**
* Begin redirect-based authentication (OIDC/SAML)
*/
async beginRedirect(
session: string,
method: string,
returnUrl: string = '/'
): Promise<RedirectResponse> {
return fetchWrapper.post('/auth/redirect', {
session,
method,
return_url: returnUrl,
}, { skipLogoutOnError: true });
},
/**
* Start a challenge for methods that require it (SMS, email, TOTP)
*/
async beginChallenge(session: string, method: string): Promise<ChallengeResponse> {
return fetchWrapper.post('/auth/challenge', {
session,
method,
}, { skipLogoutOnError: true });
},
/**
* Get current session status
*/
async getStatus(session: string): Promise<SessionStatus> {
return fetchWrapper.get(`/auth/status?session=${encodeURIComponent(session)}`, undefined, { skipLogoutOnError: true });
},
/**
* Cancel authentication session
*/
async cancelSession(session: string): Promise<void> {
await fetchWrapper.delete(`/auth/session?session=${encodeURIComponent(session)}`);
},
/**
* Refresh access token
*/
async refresh(): Promise<void> {
await fetchWrapper.post('/auth/refresh', {});
},
/**
* Logout
*/
async logout(): Promise<void> {
await fetchWrapper.post('/auth/logout', {});
},
};

View File

@@ -0,0 +1 @@
export { userService } from './userService';

View File

@@ -0,0 +1,153 @@
import { fetchWrapper } from '@KTXC/utils/helpers/fetch-wrapper';
/**
* User Service
* Provides methods for managing user profile and settings with batched updates
*/
// Pending updates for profile and settings
let pendingProfileUpdates: Record<string, any> = {};
let pendingSettingsUpdates: Record<string, any> = {};
let profileUpdateTimer: ReturnType<typeof setTimeout> | null = null;
let settingsUpdateTimer: ReturnType<typeof setTimeout> | null = null;
// Default debounce delay in milliseconds
const DEBOUNCE_DELAY = 500;
export const userService = {
/**
* Update profile field(s) with debouncing
* Multiple calls within the debounce window will be batched into a single request
*/
updateProfile(fields: Record<string, any>): Promise<void> {
return new Promise((resolve, reject) => {
// Merge new fields with pending updates
Object.assign(pendingProfileUpdates, fields);
// Clear existing timer
if (profileUpdateTimer) {
clearTimeout(profileUpdateTimer);
}
// Set new timer to batch updates
profileUpdateTimer = setTimeout(async () => {
const updates = { ...pendingProfileUpdates };
pendingProfileUpdates = {};
profileUpdateTimer = null;
try {
await fetchWrapper.put('/user/profile', { data: updates });
resolve();
} catch (error) {
reject(error);
}
}, DEBOUNCE_DELAY);
});
},
/**
* Update profile field(s) immediately without debouncing
*/
async updateProfileImmediate(fields: Record<string, any>): Promise<void> {
// Cancel pending debounced update
if (profileUpdateTimer) {
clearTimeout(profileUpdateTimer);
profileUpdateTimer = null;
}
// Merge with pending updates and send immediately
const updates = { ...pendingProfileUpdates, ...fields };
pendingProfileUpdates = {};
return fetchWrapper.put('/user/profile', { data: updates });
},
/**
* Flush any pending profile updates immediately
*/
async flushProfileUpdates(): Promise<void> {
if (profileUpdateTimer) {
clearTimeout(profileUpdateTimer);
profileUpdateTimer = null;
}
if (Object.keys(pendingProfileUpdates).length > 0) {
const updates = { ...pendingProfileUpdates };
pendingProfileUpdates = {};
return fetchWrapper.put('/user/profile', { data: updates });
}
},
/**
* Update setting(s) with debouncing
* Multiple calls within the debounce window will be batched into a single request
*/
updateSettings(settings: Record<string, any>): Promise<void> {
return new Promise((resolve, reject) => {
// Merge new settings with pending updates
Object.assign(pendingSettingsUpdates, settings);
// Clear existing timer
if (settingsUpdateTimer) {
clearTimeout(settingsUpdateTimer);
}
// Set new timer to batch updates
settingsUpdateTimer = setTimeout(async () => {
const updates = { ...pendingSettingsUpdates };
pendingSettingsUpdates = {};
settingsUpdateTimer = null;
try {
await fetchWrapper.put('/user/settings', { data: updates });
resolve();
} catch (error) {
reject(error);
}
}, DEBOUNCE_DELAY);
});
},
/**
* Update setting(s) immediately without debouncing
*/
async updateSettingsImmediate(settings: Record<string, any>): Promise<void> {
// Cancel pending debounced update
if (settingsUpdateTimer) {
clearTimeout(settingsUpdateTimer);
settingsUpdateTimer = null;
}
// Merge with pending updates and send immediately
const updates = { ...pendingSettingsUpdates, ...settings };
pendingSettingsUpdates = {};
return fetchWrapper.put('/user/settings', { data: updates });
},
/**
* Flush any pending settings updates immediately
*/
async flushSettingsUpdates(): Promise<void> {
if (settingsUpdateTimer) {
clearTimeout(settingsUpdateTimer);
settingsUpdateTimer = null;
}
if (Object.keys(pendingSettingsUpdates).length > 0) {
const updates = { ...pendingSettingsUpdates };
pendingSettingsUpdates = {};
return fetchWrapper.put('/user/settings', { data: updates });
}
},
/**
* Flush all pending updates (profile and settings)
*/
async flushAll(): Promise<void> {
await Promise.all([
this.flushProfileUpdates(),
this.flushSettingsUpdates(),
]);
},
};