diff --git a/proxy.conf.json b/proxy.conf.json index fcdce66..26f4cf2 100644 --- a/proxy.conf.json +++ b/proxy.conf.json @@ -19,5 +19,12 @@ "changeOrigin": true, "pathRewrite": { "^/proxy/api-vitanova": "" }, "logLevel": "debug" + }, + "/proxy/users-vitanova": { + "target": "https://users.vitanova.network:456", + "secure": true, + "changeOrigin": true, + "pathRewrite": { "^/proxy/users-vitanova": "" }, + "logLevel": "debug" } } diff --git a/src/app/api.ts b/src/app/api.ts index 7d4cc78..94a5220 100644 --- a/src/app/api.ts +++ b/src/app/api.ts @@ -10,6 +10,14 @@ export const FASTCHECK_API = isDevMode() ? '/proxy/fastcheck-store/api' : 'https://fastcheck.store/api'; +/** + * Base URL for Telegram/websession authorization. + * Auth routes: POST /users/sessions, GET/DELETE /users/sessions/:webSessionID. + */ +export const USERS_VITANOVA_API = isDevMode() + ? '/proxy/users-vitanova' + : 'https://users.vitanova.network:456'; + /** * Base URL for the QR/SBP backend (qr.vitanova.network). * Used for the /api/settings endpoint that may return active check data on load. diff --git a/src/app/auth-dialog/auth-dialog.html b/src/app/auth-dialog/auth-dialog.html index 56ed28c..f8f4203 100644 --- a/src/app/auth-dialog/auth-dialog.html +++ b/src/app/auth-dialog/auth-dialog.html @@ -30,6 +30,10 @@ {{ 'auth.telegram_btn' | translate }} + + {{ telegramLink() }} + +

{{ 'auth.qr_hint' | translate }}

diff --git a/src/app/auth-dialog/auth-dialog.scss b/src/app/auth-dialog/auth-dialog.scss index 578eeb8..002dbda 100644 --- a/src/app/auth-dialog/auth-dialog.scss +++ b/src/app/auth-dialog/auth-dialog.scss @@ -118,6 +118,20 @@ h2 { flex-shrink: 0; } +.telegram-link { + display: block; + margin-top: 10px; + color: var(--telegram-hover); + font-size: 12px; + line-height: 1.4; + overflow-wrap: anywhere; + text-decoration: none; +} + +.telegram-link:hover { + text-decoration: underline; +} + .qr-section { margin-top: 20px; } diff --git a/src/app/auth-dialog/auth-dialog.ts b/src/app/auth-dialog/auth-dialog.ts index 735819b..ad40147 100644 --- a/src/app/auth-dialog/auth-dialog.ts +++ b/src/app/auth-dialog/auth-dialog.ts @@ -1,14 +1,20 @@ import { HttpClient } from '@angular/common/http'; import { Component, computed, effect, inject, input, output, signal } from '@angular/core'; -import { FASTCHECK_API } from '../api'; +import { USERS_VITANOVA_API } from '../api'; import { TranslatePipe } from '../translate/translate.pipe'; interface WebSessionResponse { - sessionId: string; - userId: string; - expires: string; - userSessionId: string; - Status: boolean; + sessionId?: string; + webSessionID?: string; + webSessionId?: string; + userId?: string; + userID?: string; + telegramID?: string; + expires?: string; + userSessionId?: string; + userSessionID?: string; + Status?: boolean; + status?: boolean | string; } export type AuthDialogMode = 'payment' | 'login' | 'new'; @@ -31,7 +37,6 @@ export class AuthDialogComponent { private readonly http = inject(HttpClient); private readonly telegramBot = 'myAMLKYCBOT'; private readonly sessionStorageKey = 'fc_session'; - private readonly maxPollAttempts = 100; open = input(false); mode = input('login'); @@ -61,7 +66,6 @@ export class AuthDialogComponent { } private pollHandle: ReturnType | null = null; - private pollAttempts = 0; private wasOpen = false; private authenticated = false; @@ -106,7 +110,6 @@ export class AuthDialogComponent { private startAuthFlow(): void { this.stopPolling(); this.authenticated = false; - this.pollAttempts = 0; this.messageKey.set(''); this.webSessionId.set(''); this.state.set('checking'); @@ -121,9 +124,9 @@ export class AuthDialogComponent { } private checkExistingSession(sessionId: string): void { - this.http.get(`${FASTCHECK_API}/websession/${sessionId}`).subscribe({ + this.http.get(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`).subscribe({ next: (response) => { - if (response?.Status) { + if (this.isAuthorized(response)) { this.webSessionId.set(sessionId); this.handleAuthorized(response, sessionId); return; @@ -141,26 +144,30 @@ export class AuthDialogComponent { private createSession(): void { this.state.set('loading'); - this.http.get(`${FASTCHECK_API}/websession`).subscribe({ + const sessionId = this.createGuid(); + this.webSessionId.set(sessionId); + + this.http.post(`${USERS_VITANOVA_API}/users/sessions`, { + webSessionID: sessionId, + sessionId + }).subscribe({ next: (response) => { - const sessionId = response?.sessionId ?? ''; - if (!sessionId) { + const responseSessionId = this.getSessionId(response) || sessionId; + if (!responseSessionId) { this.messageKey.set('auth.session_failed'); this.state.set('error'); return; } - this.webSessionId.set(sessionId); + this.webSessionId.set(responseSessionId); - if (this.isMobile) { - this.state.set('checking'); - window.location.href = this.telegramLink(); - this.startPolling(sessionId); + if (this.isAuthorized(response)) { + this.handleAuthorized(response, responseSessionId); return; } this.state.set('ready'); - this.startPolling(sessionId); + this.startPolling(responseSessionId); }, error: () => { this.messageKey.set('auth.session_failed'); @@ -171,19 +178,10 @@ export class AuthDialogComponent { private startPolling(sessionId: string): void { this.stopPolling(); - this.pollAttempts = 0; this.pollHandle = setInterval(() => { - this.pollAttempts += 1; - if (this.pollAttempts >= this.maxPollAttempts) { - this.stopPolling(); - this.messageKey.set('auth.expired'); - this.state.set('expired'); - return; - } - - this.http.get(`${FASTCHECK_API}/websession/${sessionId}`).subscribe({ + this.http.get(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`).subscribe({ next: (response) => { - if (!response?.Status) return; + if (!this.isAuthorized(response)) return; this.handleAuthorized(response, sessionId); }, error: () => undefined @@ -200,8 +198,8 @@ export class AuthDialogComponent { this.state.set('checking'); this.authorized.emit({ sessionId, - userId: response.userId ?? '', - userSessionId: response.userSessionId ?? '' + userId: response.userId ?? response.userID ?? response.telegramID ?? '', + userSessionId: response.userSessionId ?? response.userSessionID ?? '' }); } @@ -212,7 +210,6 @@ export class AuthDialogComponent { this.webSessionId.set(''); this.state.set('loading'); this.authenticated = false; - this.pollAttempts = 0; } private cleanupSession(persistSession: boolean): void { @@ -232,14 +229,33 @@ export class AuthDialogComponent { } this.http - .request('DELETE', `${FASTCHECK_API}/websession/${sessionId}`, { - body: { sessionId } - }) + .delete(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`) .subscribe({ error: () => undefined }); localStorage.removeItem(this.sessionStorageKey); } + private getSessionId(response: WebSessionResponse | null | undefined): string { + return response?.webSessionID ?? response?.webSessionId ?? response?.sessionId ?? ''; + } + + private isAuthorized(response: WebSessionResponse | null | undefined): boolean { + const status = response?.Status ?? response?.status; + return status === true || String(status).toLowerCase() === 'true'; + } + + private createGuid(): string { + if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) { + return crypto.randomUUID(); + } + + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char) => { + const randomValue = Math.floor(Math.random() * 16); + const value = char === 'x' ? randomValue : (randomValue & 0x3) | 0x8; + return value.toString(16); + }); + } + private stopPolling(): void { if (this.pollHandle === null) return; clearInterval(this.pollHandle);