mya-cli
Version:
MYA - AI-Powered Stock & Options Analysis CLI Tool
257 lines • 10.6 kB
JavaScript
/**
* Authentication manager for coordinating authentication operations
* Acts as a facade to simplify authentication operations and maintain backward compatibility
*/
/**
* Authentication manager singleton
* Single responsibility: Provide unified access to authentication operations
*/
export class AuthManager {
static instance;
/**
* Create a new auth manager
* @param authProvider Authentication provider
*/
constructor() {
// Intentionally left blank (no authProvider initialization)
}
/**
* Get the singleton instance of AuthManager
* @returns AuthManager instance
*/
static getInstance() {
if (!AuthManager.instance) {
// ...existing code...
// Create and store the instance
AuthManager.instance = new AuthManager();
}
return AuthManager.instance;
}
get baseUrl() {
return process.env.MYA_API_URL || 'http://localhost:8787';
}
/**
* Send a one-time code to the provided email
* @param email Email to send code to
* @returns Response with method ID
*/
async sendOtpCode(email) {
try {
const response = await fetch(`${this.baseUrl}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (!response.ok) {
// Try to get the error message from the response body
let errorMessage = `Request failed with status ${response.status} ${response.statusText}`;
try {
const errorBody = await response.json();
if (typeof errorBody === 'object' &&
errorBody !== null &&
'message' in errorBody &&
typeof errorBody.message === 'string') {
errorMessage += `: ${errorBody.message}`;
}
}
catch {
// Ignore if we can't parse the error body
}
throw new Error(`Failed to send verification code: ${errorMessage}`);
}
const data = await response.json();
if (typeof data !== 'object' ||
data === null ||
!('method_id' in data) ||
typeof data.method_id !== 'string') {
throw new Error('Invalid response from backend');
}
return {
methodId: data.method_id,
method_id: data.method_id,
user_id: typeof data.user_id === 'string'
? data.user_id
: '',
status_code: typeof data.status_code === 'number'
? data.status_code
: 0,
};
}
catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to send verification code: ${error.message}`);
}
throw new Error('Failed to send verification code: Unknown error');
}
}
/**
* Logout the user by clearing the token from the backend
* @returns Success status
*/
async logout() {
try {
const backendUrl = process.env.MYA_API_URL || 'http://localhost:8787';
const response = await fetch(`${backendUrl}/auth/logout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to logout: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (typeof data !== 'object' ||
data === null ||
!('success' in data) ||
typeof data.success !== 'boolean' ||
!('message' in data) ||
typeof data.message !== 'string') {
throw new Error('Invalid response from backend');
}
return data;
}
catch (err) {
if (err instanceof Error && err.message.includes('Failed to fetch')) {
throw new Error('Network error: Unable to connect to authentication service');
}
else if (err instanceof Error) {
throw new Error(`Logout error: ${err.message || 'Unknown error'}`);
}
throw new Error('Logout error: Unknown error');
}
}
/**
* Check authentication status from the backend
* @returns Authentication status information
*/
async checkAuthStatus() {
try {
const backendUrl = process.env.MYA_API_URL || 'http://localhost:8787';
const response = await fetch(`${backendUrl}/auth/status`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
if (!response.ok) {
throw new Error(`Failed to check auth status: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (typeof data !== 'object' ||
data === null ||
!('authenticated' in data) ||
typeof data.authenticated !== 'boolean') {
throw new Error('Invalid response from backend');
}
// Optionally check user and expiresAt
if ('user' in data &&
data.user !== undefined &&
(typeof data.user !== 'object' || data.user === null ||
!('id' in data.user) || typeof data.user.id !== 'string' ||
!('email' in data.user) || typeof data.user.email !== 'string')) {
throw new Error('Invalid user object in response');
}
if ('expiresAt' in data &&
data.expiresAt !== undefined &&
typeof data.expiresAt !== 'number') {
throw new Error('Invalid expiresAt in response');
}
return data;
}
catch (err) {
if (err instanceof Error && err.message.includes('Failed to fetch')) {
throw new Error('Network error: Unable to connect to authentication service');
}
else if (err instanceof Error) {
throw new Error(`Authentication status error: ${err.message || 'Unknown error'}`);
}
throw new Error('Authentication status error: Unknown error');
}
}
/**
* Verify a one-time code
* @param methodId Method ID from send operation
* @param code OTP code received by user
* @param email Email associated with the OTP
* @returns Session information upon successful verification
*/
async verifyOtpCode(methodId, code, email) {
try {
// Attempt to contact backend to verify OTP
const backendUrl = process.env.MYA_API_URL || 'http://localhost:8787';
const response = await fetch(`${backendUrl}/auth/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ method_id: methodId, code, email }),
});
// Debug: Check for Set-Cookie header
const setCookieHeader = response.headers.get('Set-Cookie');
// Extract and save the session cookie
if (setCookieHeader) {
// No-op: Do not persist cookies to disk. All session/auth state should be handled via KV cache.
}
if (!response.ok) {
// Try to get the error message from the response
let errorMessage = `Failed to verify OTP: ${response.status} ${response.statusText}`;
try {
const errorData = await response.json();
if (typeof errorData === 'object' &&
errorData !== null &&
'error' in errorData &&
typeof errorData.error === 'string') {
errorMessage = errorData.error;
}
}
catch {
// Ignore JSON parse error
}
throw new Error(errorMessage);
}
const data = await response.json();
if (typeof data !== 'object' ||
data === null ||
!('token' in data) ||
typeof data.token !== 'string' ||
!('user' in data) ||
typeof data.user !== 'object' ||
data.user === null ||
typeof data.user !== 'object' ||
data.user === null ||
!('id' in data.user) ||
typeof (data.user.id) !== 'string') {
throw new Error('Invalid response from backend');
}
// Return the complete response data including token and user info
return {
session_token: data.token || '',
user_id: (data.user.id) || '',
token: data.token,
email: typeof (data.user.email) === 'string'
? (data.user.email)
: email,
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days from now
};
}
catch (err) {
if (err instanceof Error && err.message.includes('Failed to fetch')) {
throw new Error('Network error: Unable to connect to authentication service');
}
else if (err instanceof Error && err.message.includes('otp_code_not_found')) {
throw new Error('otp_code_not_found');
}
else if (err instanceof Error && err.message.includes('Invalid response')) {
throw new Error('Invalid response from authentication service');
}
else if (err instanceof Error) {
throw new Error(`Authentication error: ${err.message || 'Unknown error'}`);
}
throw new Error('Authentication error: Unknown error');
}
}
/**
* Set a custom authentication provider
* Used primarily for testing or alternative implementations
* @param provider Authentication provider implementation
*/
setAuthProvider(_provider) {
// Intentionally left blank (no authProvider assignment)
}
}
//# sourceMappingURL=auth-manager.js.map