feat: Security
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
interface StoredAuthToken {
|
||||
token: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = 'metoyou.authTokens';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthTokenStoreService {
|
||||
setToken(serverUrl: string, token: string, expiresAt: number): void {
|
||||
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
||||
const store = this.readStore();
|
||||
|
||||
store[normalizedUrl] = { token, expiresAt };
|
||||
this.writeStore(store);
|
||||
}
|
||||
|
||||
getToken(serverUrl: string): string | null {
|
||||
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
||||
const entry = this.readStore()[normalizedUrl];
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entry.expiresAt <= Date.now()) {
|
||||
this.clearToken(serverUrl);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.token;
|
||||
}
|
||||
|
||||
clearToken(serverUrl: string): void {
|
||||
const normalizedUrl = this.normalizeServerUrl(serverUrl);
|
||||
const store = this.readStore();
|
||||
const nextStore = Object.fromEntries(
|
||||
Object.entries(store).filter(([key]) => key !== normalizedUrl)
|
||||
) as Record<string, StoredAuthToken>;
|
||||
|
||||
this.writeStore(nextStore);
|
||||
}
|
||||
|
||||
hasAnyValidToken(): boolean {
|
||||
const now = Date.now();
|
||||
|
||||
return Object.values(this.readStore()).some((entry) => entry.expiresAt > now);
|
||||
}
|
||||
|
||||
findTokenForApiUrl(apiUrl: string): string | null {
|
||||
const normalizedApiUrl = apiUrl.trim().replace(/\/+$/, '');
|
||||
|
||||
for (const [serverUrl, entry] of Object.entries(this.readStore())) {
|
||||
if (entry.expiresAt <= Date.now()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (normalizedApiUrl === serverUrl || normalizedApiUrl.startsWith(`${serverUrl}/`)) {
|
||||
return entry.token;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private readStore(): Record<string, StoredAuthToken> {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const parsed = JSON.parse(raw) as Record<string, StoredAuthToken>;
|
||||
|
||||
return parsed && typeof parsed === 'object' ? parsed : {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
private writeStore(store: Record<string, StoredAuthToken>): void {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
|
||||
}
|
||||
|
||||
private normalizeServerUrl(serverUrl: string): string {
|
||||
return serverUrl.trim().replace(/\/+$/, '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user