checking api
This commit is contained in:
@@ -1,13 +1,20 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
||||
import { APP_INITIALIZER, ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
||||
import { AuthSessionService } from './auth-session.service';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideRouter(routes),
|
||||
provideHttpClient()
|
||||
provideHttpClient(),
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [AuthSessionService],
|
||||
useFactory: (authSession: AuthSessionService) => () => authSession.initialize()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, computed, effect, inject, input, output, signal } from '@angular/core';
|
||||
import { USERS_VITANOVA_API } from '../api';
|
||||
import { AuthSessionService, WebSessionResponse } from '../auth-session.service';
|
||||
import { TranslatePipe } from '../translate/translate.pipe';
|
||||
|
||||
interface WebSessionResponse {
|
||||
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';
|
||||
|
||||
export interface AuthDialogAuthorizedEvent {
|
||||
@@ -35,8 +22,8 @@ type AuthDialogState = 'loading' | 'ready' | 'checking' | 'expired' | 'error';
|
||||
})
|
||||
export class AuthDialogComponent {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly authSession = inject(AuthSessionService);
|
||||
private readonly telegramBot = 'myAMLKYCBOT';
|
||||
private readonly sessionStorageKey = 'fc_session';
|
||||
|
||||
open = input(false);
|
||||
mode = input<AuthDialogMode>('login');
|
||||
@@ -114,7 +101,7 @@ export class AuthDialogComponent {
|
||||
this.webSessionId.set('');
|
||||
this.state.set('checking');
|
||||
|
||||
const existingSession = localStorage.getItem(this.sessionStorageKey) ?? '';
|
||||
const existingSession = this.authSession.getSessionId();
|
||||
if (existingSession) {
|
||||
this.checkExistingSession(existingSession);
|
||||
return;
|
||||
@@ -124,21 +111,17 @@ export class AuthDialogComponent {
|
||||
}
|
||||
|
||||
private checkExistingSession(sessionId: string): void {
|
||||
this.http.get<WebSessionResponse>(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`).subscribe({
|
||||
this.authSession.validateSession(sessionId).subscribe({
|
||||
next: (response) => {
|
||||
if (this.isAuthorized(response)) {
|
||||
if (response) {
|
||||
this.webSessionId.set(sessionId);
|
||||
this.handleAuthorized(response, sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(this.sessionStorageKey);
|
||||
this.createSession();
|
||||
},
|
||||
error: () => {
|
||||
localStorage.removeItem(this.sessionStorageKey);
|
||||
this.createSession();
|
||||
}
|
||||
error: () => this.createSession()
|
||||
});
|
||||
}
|
||||
|
||||
@@ -195,6 +178,7 @@ export class AuthDialogComponent {
|
||||
this.authenticated = true;
|
||||
this.stopPolling();
|
||||
this.webSessionId.set(sessionId);
|
||||
this.authSession.persistAuthorizedSession(sessionId, response.expires);
|
||||
this.state.set('checking');
|
||||
this.authorized.emit({
|
||||
sessionId,
|
||||
@@ -217,14 +201,10 @@ export class AuthDialogComponent {
|
||||
|
||||
const sessionId = this.webSessionId();
|
||||
if (!sessionId) {
|
||||
if (!persistSession) {
|
||||
localStorage.removeItem(this.sessionStorageKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (persistSession) {
|
||||
localStorage.setItem(this.sessionStorageKey, sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -232,7 +212,9 @@ export class AuthDialogComponent {
|
||||
.delete(`${USERS_VITANOVA_API}/users/sessions/${sessionId}`)
|
||||
.subscribe({ error: () => undefined });
|
||||
|
||||
localStorage.removeItem(this.sessionStorageKey);
|
||||
if (this.authSession.getSessionId() === sessionId) {
|
||||
this.authSession.clearSession();
|
||||
}
|
||||
}
|
||||
|
||||
private getSessionId(response: WebSessionResponse | null | undefined): string {
|
||||
|
||||
202
src/app/auth-session.service.ts
Normal file
202
src/app/auth-session.service.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { catchError, finalize, firstValueFrom, map, Observable, of, tap } from 'rxjs';
|
||||
import { USERS_VITANOVA_API } from './api';
|
||||
|
||||
export interface WebSessionResponse {
|
||||
sessionId?: string;
|
||||
webSessionID?: string;
|
||||
webSessionId?: string;
|
||||
userId?: string;
|
||||
userID?: string;
|
||||
telegramID?: string;
|
||||
expires?: string;
|
||||
userSessionId?: string;
|
||||
userSessionID?: string;
|
||||
Status?: boolean;
|
||||
status?: boolean | string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthSessionService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly cookieName = 'fc_session';
|
||||
private readonly legacyStorageKey = 'fc_session';
|
||||
private readonly cookieLifetimeMs = 60 * 60 * 1000;
|
||||
private initialized = false;
|
||||
|
||||
readonly sessionId = signal('');
|
||||
readonly authenticated = signal(false);
|
||||
readonly validating = signal(false);
|
||||
|
||||
initialize(): Promise<void> {
|
||||
if (this.initialized) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
const storedSessionId = this.readStoredSessionId();
|
||||
if (!storedSessionId) {
|
||||
this.clearSession();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this.sessionId.set(storedSessionId);
|
||||
this.authenticated.set(false);
|
||||
|
||||
return firstValueFrom(this.validateSession(storedSessionId).pipe(map(() => undefined)));
|
||||
}
|
||||
|
||||
getSessionId(): string {
|
||||
const current = this.sessionId().trim();
|
||||
if (current) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const storedSessionId = this.readStoredSessionId();
|
||||
if (storedSessionId) {
|
||||
this.sessionId.set(storedSessionId);
|
||||
}
|
||||
|
||||
return storedSessionId;
|
||||
}
|
||||
|
||||
persistAuthorizedSession(sessionId: string, expires?: string): void {
|
||||
const normalizedSessionId = sessionId.trim();
|
||||
if (!normalizedSessionId) {
|
||||
this.clearSession();
|
||||
return;
|
||||
}
|
||||
|
||||
this.writeCookie(normalizedSessionId, this.resolveExpiry(expires));
|
||||
this.removeLegacySession();
|
||||
this.sessionId.set(normalizedSessionId);
|
||||
this.authenticated.set(true);
|
||||
}
|
||||
|
||||
validateStoredSession(): Observable<WebSessionResponse | null> {
|
||||
return this.validateSession(this.getSessionId());
|
||||
}
|
||||
|
||||
validateSession(sessionId: string): Observable<WebSessionResponse | null> {
|
||||
const normalizedSessionId = sessionId.trim();
|
||||
if (!normalizedSessionId) {
|
||||
this.clearSession();
|
||||
return of(null);
|
||||
}
|
||||
|
||||
this.validating.set(true);
|
||||
|
||||
return this.http
|
||||
.get<WebSessionResponse>(`${USERS_VITANOVA_API}/users/sessions/${normalizedSessionId}`)
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
if (this.isAuthorized(response)) {
|
||||
this.persistAuthorizedSession(normalizedSessionId, response.expires);
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearSession();
|
||||
}),
|
||||
map((response) => (this.isAuthorized(response) ? response : null)),
|
||||
catchError(() => {
|
||||
this.clearSession();
|
||||
return of(null);
|
||||
}),
|
||||
finalize(() => this.validating.set(false))
|
||||
);
|
||||
}
|
||||
|
||||
clearSession(): void {
|
||||
this.deleteCookie();
|
||||
this.removeLegacySession();
|
||||
this.sessionId.set('');
|
||||
this.authenticated.set(false);
|
||||
}
|
||||
|
||||
private readStoredSessionId(): string {
|
||||
const cookieSessionId = this.readCookie();
|
||||
if (cookieSessionId) {
|
||||
return cookieSessionId;
|
||||
}
|
||||
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (localStorage.getItem(this.legacyStorageKey) ?? '').trim();
|
||||
}
|
||||
|
||||
private removeLegacySession(): void {
|
||||
if (typeof localStorage === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(this.legacyStorageKey);
|
||||
}
|
||||
|
||||
private resolveExpiry(expires?: string): Date {
|
||||
const fallback = new Date(Date.now() + this.cookieLifetimeMs);
|
||||
if (!expires) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
const parsed = new Date(expires);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return new Date(Math.min(parsed.getTime(), fallback.getTime()));
|
||||
}
|
||||
|
||||
private isAuthorized(response: WebSessionResponse | null | undefined): boolean {
|
||||
const status = response?.Status ?? response?.status;
|
||||
return status === true || String(status).toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
private readCookie(): string {
|
||||
if (typeof document === 'undefined') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const prefix = `${this.cookieName}=`;
|
||||
for (const part of document.cookie.split(';')) {
|
||||
const segment = part.trim();
|
||||
if (!segment.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return decodeURIComponent(segment.slice(prefix.length));
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private writeCookie(sessionId: string, expiresAt: Date): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const cookieParts = [
|
||||
`${this.cookieName}=${encodeURIComponent(sessionId)}`,
|
||||
'Path=/',
|
||||
`Expires=${expiresAt.toUTCString()}`,
|
||||
'SameSite=Lax'
|
||||
];
|
||||
|
||||
if (typeof location !== 'undefined' && location.protocol === 'https:') {
|
||||
cookieParts.push('Secure');
|
||||
}
|
||||
|
||||
document.cookie = cookieParts.join('; ');
|
||||
}
|
||||
|
||||
private deleteCookie(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
document.cookie = `${this.cookieName}=; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax`;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { FastcheckService } from '../../fastcheck.service';
|
||||
import { API_VITANOVA_NETWORK, FASTCHECK_API, FASTCHECK_STORE_API, QR_VITANOVA_API } from '../../api';
|
||||
import { AuthDialogAuthorizedEvent, AuthDialogComponent, AuthDialogMode } from '../../auth-dialog/auth-dialog';
|
||||
import { AuthSessionService } from '../../auth-session.service';
|
||||
import { TranslatePipe } from '../../translate/translate.pipe';
|
||||
import { TranslationService } from '../../translate/translation.service';
|
||||
|
||||
@@ -55,6 +56,7 @@ export class FastcheckPage {
|
||||
private readonly defaultPartnerId = 'fast-c202-4062-bcfb-8b4c8cc59adc';
|
||||
|
||||
private http = inject(HttpClient);
|
||||
private authSession = inject(AuthSessionService);
|
||||
private store = inject(FastcheckService);
|
||||
private i18n = inject(TranslationService);
|
||||
|
||||
@@ -277,13 +279,20 @@ export class FastcheckPage {
|
||||
event.preventDefault();
|
||||
|
||||
const id = this.partnerId() || this.defaultPartnerId;
|
||||
const sessionId = (localStorage.getItem('fc_session') ?? '').trim();
|
||||
const sessionId = this.authSession.getSessionId();
|
||||
|
||||
if (!sessionId) {
|
||||
this.openAuth('new');
|
||||
return;
|
||||
}
|
||||
|
||||
this.authSession.validateSession(sessionId).subscribe({
|
||||
next: (response) => {
|
||||
if (!response) {
|
||||
this.openAuth('new');
|
||||
return;
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: JSON.stringify({ sessionID: sessionId, partnerID: id })
|
||||
};
|
||||
@@ -297,17 +306,25 @@ export class FastcheckPage {
|
||||
},
|
||||
error: () => {
|
||||
// Not authorized: force fresh Telegram auth QR popup.
|
||||
localStorage.removeItem('fc_session');
|
||||
this.authSession.clearSession();
|
||||
this.openAuth('new');
|
||||
}
|
||||
});
|
||||
},
|
||||
error: () => {
|
||||
this.authSession.clearSession();
|
||||
this.openAuth('new');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private doRedirectToNew(sessionId?: string): void {
|
||||
const tok = sessionId || localStorage.getItem('fc_session') || '';
|
||||
if (tok) {
|
||||
localStorage.setItem('fc_session', tok);
|
||||
const tok = sessionId || this.authSession.getSessionId() || '';
|
||||
if (!tok) {
|
||||
this.openAuth('new');
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = this.newQrUrl();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user