AI Implementation feature(823): Landing Page and Login/Register Modal (#11)
This commit was merged in pull request #11.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Injectable, signal, computed, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import {
|
||||
readStoredSession,
|
||||
@@ -20,6 +20,38 @@ interface RefreshResponse {
|
||||
user: CurrentUser;
|
||||
}
|
||||
|
||||
export interface LoginSuccess {
|
||||
ok: true;
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
user: CurrentUser;
|
||||
}
|
||||
|
||||
export interface LoginFailure {
|
||||
ok: false;
|
||||
status: number;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type LoginResult = LoginSuccess | LoginFailure;
|
||||
|
||||
export interface RegisterSuccess {
|
||||
ok: true;
|
||||
accessToken: string;
|
||||
expiresIn: number;
|
||||
user: CurrentUser;
|
||||
}
|
||||
|
||||
export interface RegisterFailure {
|
||||
ok: false;
|
||||
status: number;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type RegisterResult = RegisterSuccess | RegisterFailure;
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AuthService {
|
||||
private http: HttpClient;
|
||||
@@ -36,6 +68,64 @@ export class AuthService {
|
||||
readonly currentUser = computed(() => this.user());
|
||||
readonly isAuthenticated = computed(() => !!this.user());
|
||||
|
||||
async ensureCsrf(): Promise<void> {
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this.http.get('/api/v1/auth/csrf', { withCredentials: true }),
|
||||
);
|
||||
} catch {
|
||||
// middleware will still mint the cookie on the next response
|
||||
}
|
||||
}
|
||||
|
||||
async login(creds: { username: string; password: string }): Promise<LoginResult> {
|
||||
await this.ensureCsrf();
|
||||
try {
|
||||
const res = await firstValueFrom(
|
||||
this.http.post<{ accessToken: string; expiresIn: number; user: CurrentUser }>(
|
||||
'/api/v1/auth/login',
|
||||
creds,
|
||||
{ withCredentials: true, observe: 'response' },
|
||||
),
|
||||
);
|
||||
const success: LoginSuccess = {
|
||||
ok: true,
|
||||
accessToken: res.body!.accessToken,
|
||||
expiresIn: res.body!.expiresIn,
|
||||
user: res.body!.user,
|
||||
};
|
||||
return success;
|
||||
} catch (e) {
|
||||
return mapAuthError(e as HttpErrorResponse);
|
||||
}
|
||||
}
|
||||
|
||||
async register(creds: {
|
||||
username: string;
|
||||
password: string;
|
||||
passwordConfirm: string;
|
||||
}): Promise<RegisterResult> {
|
||||
await this.ensureCsrf();
|
||||
try {
|
||||
const res = await firstValueFrom(
|
||||
this.http.post<{ accessToken: string; expiresIn: number; user: CurrentUser }>(
|
||||
'/api/v1/auth/register',
|
||||
creds,
|
||||
{ withCredentials: true, observe: 'response' },
|
||||
),
|
||||
);
|
||||
const success: RegisterSuccess = {
|
||||
ok: true,
|
||||
accessToken: res.body!.accessToken,
|
||||
expiresIn: res.body!.expiresIn,
|
||||
user: res.body!.user,
|
||||
};
|
||||
return success;
|
||||
} catch (e) {
|
||||
return mapAuthError(e as HttpErrorResponse) as RegisterFailure;
|
||||
}
|
||||
}
|
||||
|
||||
setSession(token: string, user: CurrentUser): void {
|
||||
this.accessToken.set(token);
|
||||
this.user.set(user);
|
||||
@@ -104,4 +194,14 @@ export class AuthService {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function mapAuthError(err: HttpErrorResponse): LoginFailure {
|
||||
const body = err.error as { code?: string; message?: string } | null;
|
||||
return {
|
||||
ok: false,
|
||||
status: err.status ?? 0,
|
||||
code: body?.code ?? 'INTERNAL',
|
||||
message: body?.message ?? err.message ?? 'Authentication failed',
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user