py-trade / src /app /auth /auth.service.ts
Oviya
update signin page
a828e09
import { Injectable, computed, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map, tap } from 'rxjs';
export interface AuthUser {
id?: number | string;
displayName?: string;
email?: string;
}
interface AuthState {
user: AuthUser | null;
accessToken?: string;
refreshToken?: string;
}
const AUTH_KEY = 'auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _state = signal<AuthState>({ user: null });
readonly user = computed(() => this._state().user);
readonly isAuthenticated = computed(() => !!this._state().user && !!this._state().accessToken);
private readonly baseUrl =
location.hostname.endsWith('hf.space')
? 'https://pykara-pytrade-backend.hf.space'
: 'http://127.0.0.1:5000';
private readonly refreshUrl = `${this.baseUrl}/refresh`;
constructor(private http: HttpClient) {
this.restoreFromStorage();
}
setSession(
res: { userId?: number | string; accessToken?: string; refreshToken?: string; displayName?: string },
opts: { email?: string; rememberMe?: boolean } = {}
) {
const displayName =
res.displayName ??
this.tryGetNameFromJwt(res.accessToken) ??
opts.email ?? // fallback
undefined;
const user: AuthUser = { id: res.userId, displayName, email: opts.email };
const newState: AuthState = {
user,
accessToken: res.accessToken,
refreshToken: res.refreshToken,
};
this._state.set(newState);
const storage = opts.rememberMe ? localStorage : sessionStorage;
const other = opts.rememberMe ? sessionStorage : localStorage;
storage.setItem(AUTH_KEY, JSON.stringify(newState));
other.removeItem(AUTH_KEY);
}
logout() {
this._state.set({ user: null });
localStorage.removeItem(AUTH_KEY);
sessionStorage.removeItem(AUTH_KEY);
}
getAccessToken(): string | undefined {
return this._state().accessToken;
}
getRefreshToken(): string | undefined {
return this._state().refreshToken;
}
tokenExpired(): boolean {
const token = this._state().accessToken;
if (!token) return true;
try {
const payloadPart = token.split('.')[1];
const payload = JSON.parse(atob(payloadPart.replace(/-/g, '+').replace(/_/g, '/')));
const expSec = payload.exp as number | undefined;
if (!expSec) return false;
const nowSec = Math.floor(Date.now() / 1000);
return expSec <= nowSec;
} catch {
return true;
}
}
refreshAccessToken(): Observable<string> {
const refreshToken = this.getRefreshToken();
if (!refreshToken) throw new Error('No refresh token');
return this.http
.post<{ accessToken: string; refreshToken?: string }>(this.refreshUrl, { refreshToken })
.pipe(
tap(res => {
const state = this._state();
// keep same rememberMe behavior based on where it was stored
const rememberMe = !!localStorage.getItem(AUTH_KEY);
this.setSession(
{
userId: state.user?.id,
displayName: state.user?.displayName,
accessToken: res.accessToken,
refreshToken: res.refreshToken ?? state.refreshToken
},
{ email: state.user?.email, rememberMe }
);
}),
map(res => res.accessToken)
);
}
private restoreFromStorage() {
const raw = localStorage.getItem(AUTH_KEY) || sessionStorage.getItem(AUTH_KEY);
if (!raw) return;
try {
const state = JSON.parse(raw) as AuthState;
this._state.set(state);
} catch {
localStorage.removeItem(AUTH_KEY);
sessionStorage.removeItem(AUTH_KEY);
}
}
private tryGetNameFromJwt(token?: string): string | undefined {
if (!token) return undefined;
try {
const payloadPart = token.split('.')[1];
const payload = JSON.parse(atob(payloadPart.replace(/-/g, '+').replace(/_/g, '/')));
return payload.name || payload.preferred_username || [payload.given_name, payload.family_name].filter(Boolean).join(' ') || undefined;
} catch {
return undefined;
}
}
}