|
|
import { |
|
|
fetchDataFromTable, |
|
|
insertDataIntoTable, |
|
|
updateDataInTable |
|
|
} from '../../db/supabaseHelper'; |
|
|
import { createLogger } from '../../utils/logger'; |
|
|
import { v4 as uuidv4 } from 'uuid'; |
|
|
import { supabase } from '../../db/supabase'; |
|
|
import { BotContext } from '../types/botTypes'; |
|
|
import { getBotIdFromContext } from '../../utils/botUtils'; |
|
|
|
|
|
const logger = createLogger('AuthService'); |
|
|
|
|
|
interface UserBotTelegraf { |
|
|
id?: number; |
|
|
telegramId: number; |
|
|
username?: string | null; |
|
|
firstName?: string | null; |
|
|
lastName?: string | null; |
|
|
email: string; |
|
|
passwordHash: string; |
|
|
language?: string; |
|
|
role?: string; |
|
|
balance?: number; |
|
|
isBanned?: boolean; |
|
|
lastLogin?: Date | string | null; |
|
|
botId?: string | null; |
|
|
createdAt?: Date | string; |
|
|
updatedAt?: Date | string; |
|
|
} |
|
|
|
|
|
export class AuthService { |
|
|
private static instance: AuthService; |
|
|
|
|
|
private constructor() { |
|
|
|
|
|
} |
|
|
|
|
|
public static getInstance(): AuthService { |
|
|
if (!AuthService.instance) { |
|
|
AuthService.instance = new AuthService(); |
|
|
} |
|
|
return AuthService.instance; |
|
|
} |
|
|
|
|
|
private generatePassword(): string { |
|
|
return uuidv4().replace(/-/g, '').substring(0, 12); |
|
|
} |
|
|
|
|
|
public async hashPassword(password: string): Promise<string> { |
|
|
const encoder = new TextEncoder(); |
|
|
const data = encoder.encode(password); |
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data); |
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer)); |
|
|
return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); |
|
|
} |
|
|
|
|
|
private generateEmail(telegramId: number, ctx: BotContext): string { |
|
|
const botData = ctx.botData; |
|
|
const suffix = botData?.suffix_email || 'saerosms.com'; |
|
|
return `user_${telegramId}@${suffix}`; |
|
|
} |
|
|
|
|
|
public async createUser(telegramId: number, name: string, ctx: BotContext): Promise<{ user: UserBotTelegraf; password: string }> { |
|
|
try { |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
if (!botId) { |
|
|
throw new Error('Could not determine bot ID from context'); |
|
|
} |
|
|
|
|
|
const email = this.generateEmail(telegramId, ctx); |
|
|
const password = this.generatePassword(); |
|
|
const passwordHash = await this.hashPassword(password); |
|
|
|
|
|
const userData: Omit<UserBotTelegraf, 'id'> = { |
|
|
telegramId: telegramId, |
|
|
firstName: name, |
|
|
email, |
|
|
passwordHash: passwordHash, |
|
|
botId: botId, |
|
|
balance: 0, |
|
|
language: 'ar', |
|
|
role: 'user', |
|
|
isBanned: false |
|
|
}; |
|
|
|
|
|
const user = await insertDataIntoTable('users_bot_telegram', userData); |
|
|
|
|
|
logger.info(`User ${telegramId} created successfully for bot ${botId}`); |
|
|
return { user, password }; |
|
|
|
|
|
} catch (error: any) { |
|
|
logger.error(`Error creating user ${telegramId}: ${error.message}`); |
|
|
throw new Error(`Failed to create user: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
private loggedInUsers: Map<string, boolean> = new Map(); |
|
|
|
|
|
private generateUserKey(telegramId: number, ctx: BotContext): string { |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
if (!botId) { |
|
|
throw new Error('Could not determine bot ID from context'); |
|
|
} |
|
|
return `${telegramId}_${botId}`; |
|
|
} |
|
|
|
|
|
public setUserLoggedIn(telegramId: number, ctx: BotContext, isLoggedIn: boolean = true): void { |
|
|
const key = this.generateUserKey(telegramId, ctx); |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
this.loggedInUsers.set(key, isLoggedIn); |
|
|
logger.info(`User ${telegramId} for bot ${botId} login state set to ${isLoggedIn}`); |
|
|
} |
|
|
|
|
|
public isUserLoggedIn(telegramId: number, ctx: BotContext): boolean { |
|
|
const key = this.generateUserKey(telegramId, ctx); |
|
|
return this.loggedInUsers.get(key) || false; |
|
|
} |
|
|
|
|
|
public async loginUser(telegramId: number, ctx: BotContext): Promise<UserBotTelegraf | null> { |
|
|
try { |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
if (!botId) { |
|
|
throw new Error('Could not determine bot ID from context'); |
|
|
} |
|
|
|
|
|
const { data: user, error } = await supabase |
|
|
.from('users_bot_telegram') |
|
|
.select('*') |
|
|
.eq('telegram_id', telegramId) |
|
|
.eq('bot_id', botId) |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
if (error.code === 'PGRST116') { |
|
|
return null; |
|
|
} |
|
|
throw error; |
|
|
} |
|
|
|
|
|
if (user) { |
|
|
this.setUserLoggedIn(telegramId, ctx, true); |
|
|
return user; |
|
|
} |
|
|
|
|
|
return null; |
|
|
} catch (error) { |
|
|
logger.error('Error in loginUser:', error); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
public async getUserById(userId: string): Promise<UserBotTelegraf | null> { |
|
|
try { |
|
|
const { data: user, error } = await supabase |
|
|
.from('users_bot_telegram') |
|
|
.select('*') |
|
|
.eq('id', userId) |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
if (error.code === 'PGRST116') { |
|
|
return null; |
|
|
} |
|
|
throw error; |
|
|
} |
|
|
|
|
|
return user; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error fetching user ${userId}: ${error.message}`); |
|
|
throw new Error(`Failed to get user: ${error.message}`); |
|
|
} |
|
|
} |
|
|
|
|
|
async getUserByTelegramId(telegramId: number, ctx: BotContext): Promise<UserBotTelegraf | null> { |
|
|
try { |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
if (!botId) { |
|
|
throw new Error('Could not determine bot ID from context'); |
|
|
} |
|
|
|
|
|
const { data: user, error } = await supabase |
|
|
.from('users_bot_telegram') |
|
|
.select('*') |
|
|
.eq('telegram_id', telegramId) |
|
|
.eq('bot_id', botId) |
|
|
.single(); |
|
|
|
|
|
if (error) { |
|
|
if (error.code === 'PGRST116') { |
|
|
return null; |
|
|
} |
|
|
throw error; |
|
|
} |
|
|
|
|
|
return user; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error getting user by Telegram ID: ${error.message}`); |
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
async updateUserBalance(telegramId: number, ctx: BotContext, newBalance: number): Promise<boolean> { |
|
|
try { |
|
|
const botId = getBotIdFromContext(ctx); |
|
|
if (!botId) { |
|
|
throw new Error('Could not determine bot ID from context'); |
|
|
} |
|
|
|
|
|
const { error } = await supabase |
|
|
.from('users_bot_telegram') |
|
|
.update({ balance: newBalance }) |
|
|
.eq('telegram_id', telegramId) |
|
|
.eq('bot_id', botId); |
|
|
|
|
|
if (error) throw error; |
|
|
return true; |
|
|
} catch (error: any) { |
|
|
logger.error(`Error updating user balance: ${error.message}`); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export const authService = AuthService.getInstance(); |