AI Implementation feature(857): Authenticated Shell: Header, Quick Tabs and Change Password (#14)

This commit was merged in pull request #14.
This commit is contained in:
2026-07-21 22:26:47 +00:00
parent 8dc8cee769
commit b6dbfcc511
46 changed files with 2705 additions and 211 deletions
+67 -1
View File
@@ -12,6 +12,8 @@ export interface CurrentUser {
id: string;
username: string;
role: 'admin' | 'player';
rank?: number | null;
points?: number;
}
interface RefreshResponse {
@@ -20,6 +22,14 @@ interface RefreshResponse {
user: CurrentUser;
}
export interface MeResponse {
id: string;
username: string;
role: 'admin' | 'player';
rank: number | null;
points: number;
}
export interface LoginSuccess {
ok: true;
accessToken: string;
@@ -52,6 +62,19 @@ export interface RegisterFailure {
export type RegisterResult = RegisterSuccess | RegisterFailure;
export interface ChangePasswordSuccess {
ok: true;
}
export interface ChangePasswordFailure {
ok: false;
status: number;
code: string;
message: string;
}
export type ChangePasswordResult = ChangePasswordSuccess | ChangePasswordFailure;
@Injectable({ providedIn: 'root' })
export class AuthService {
private http: HttpClient;
@@ -126,6 +149,49 @@ export class AuthService {
}
}
async logout(): Promise<void> {
await this.ensureCsrf();
try {
await firstValueFrom(
this.http.post('/api/v1/auth/logout', {}, { withCredentials: true, observe: 'response' }),
);
} catch {
// ignore network/auth errors; we still clear local state
}
this.clear();
}
async me(): Promise<MeResponse> {
const res = await firstValueFrom(
this.http.get<MeResponse>('/api/v1/auth/me', { withCredentials: true }),
);
const current = this.user();
if (current && current.id === res.id) {
this.user.set({ ...current, rank: res.rank, points: res.points });
}
return res;
}
async changePassword(dto: {
oldPassword: string;
newPassword: string;
confirmNewPassword: string;
}): Promise<ChangePasswordResult> {
await this.ensureCsrf();
try {
await firstValueFrom(
this.http.post(
'/api/v1/auth/change-password',
dto,
{ withCredentials: true, observe: 'response' },
),
);
return { ok: true };
} catch (e) {
return mapAuthError(e as HttpErrorResponse);
}
}
setSession(token: string, user: CurrentUser): void {
this.accessToken.set(token);
this.user.set(user);
@@ -204,4 +270,4 @@ function mapAuthError(err: HttpErrorResponse): LoginFailure {
code: body?.code ?? 'INTERNAL',
message: body?.message ?? err.message ?? 'Authentication failed',
};
}
}