prototype login frontend
This commit is contained in:
parent
ae59139fdf
commit
a3a9e57dea
@ -73,6 +73,9 @@
|
||||
"buildTarget": "frontend:build:development"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"proxyConfig": "src/proxy.conf.json"
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
|
||||
@ -2,7 +2,17 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import {GandalfClientBaseAPI} from './clients/gandalf/gandalf-client';
|
||||
import {provideHttpClient} from '@angular/common/http';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(),
|
||||
{
|
||||
provide: GandalfClientBaseAPI,
|
||||
useValue: 'http://localhost:5055'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import {inject, InjectionToken} from '@angular/core';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {catchError, Observable, ObservableInput, throwError} from 'rxjs';
|
||||
|
||||
export const GandalfClientBaseAPI = new InjectionToken<string>('GandalfClientBaseAPIToken');
|
||||
|
||||
export abstract class GandalfClient {
|
||||
|
||||
protected readonly basePath = inject(GandalfClientBaseAPI);
|
||||
protected readonly http = inject(HttpClient);
|
||||
|
||||
protected handleRequest<T>(request: Observable<T>, onError?: (error: any) => void): Observable<T> {
|
||||
return request.pipe(catchError((x, y) => this.handleError(x, y, onError)));
|
||||
}
|
||||
|
||||
private handleError<T>(err: any, _: Observable<T>, onError?: (error: any) => void): Observable<never> {
|
||||
onError?.(err);
|
||||
return throwError(() => err);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {GandalfClient} from '../gandalf-client';
|
||||
import {lastValueFrom, Observable, ObservableInput} from 'rxjs';
|
||||
|
||||
export interface ValidateCredentialsCommand {
|
||||
usernameOrEmail: string;
|
||||
password: string;
|
||||
keep: boolean;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService extends GandalfClient {
|
||||
|
||||
private base = '/api/auth';
|
||||
|
||||
check(): Observable<boolean> {
|
||||
return this.handleRequest(this.http.get<boolean>(this.base + '/check'));
|
||||
}
|
||||
|
||||
login(command: ValidateCredentialsCommand, onError?: (error: any) => void): Observable<void> {
|
||||
return this.handleRequest(this.http.post<void>(this.base + '/login', command), onError);
|
||||
}
|
||||
|
||||
loginAsync(command: ValidateCredentialsCommand, onError?: (error: any) => void): Promise<void> {
|
||||
return lastValueFrom(this.login(command, onError));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,8 +1,15 @@
|
||||
<form (ngSubmit)="onSubmit($event)">
|
||||
<input required type="text" name="username" placeholder="Username">
|
||||
<input required type="password" name="password" placeholder="*****">
|
||||
@if (errors().length > 0) {
|
||||
<div class="errors">
|
||||
@for (error of errors(); track error) {
|
||||
<div class="error">{{ error }}</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<form [formGroup]="loginFormGroup" (ngSubmit)="onSubmit($event)">
|
||||
<input required type="text" name="username" placeholder="Username" formControlName="usernameOrEmail">
|
||||
<input required type="password" name="password" placeholder="*****" formControlName="password">
|
||||
<div class="group">
|
||||
<input id="remember" type="checkbox" name="remember">
|
||||
<input id="remember" type="checkbox" name="remember" formControlName="keep">
|
||||
<label for="remember">Keep me signed in</label><br>
|
||||
<input type="submit" value="Login">
|
||||
</div>
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {FormControl, FormGroup, FormsModule, FormSubmittedEvent, ReactiveFormsModule} from '@angular/forms';
|
||||
import {AuthService, ValidateCredentialsCommand} from '../../clients/gandalf/mithrandir/auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [
|
||||
FormsModule
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss',
|
||||
@ -12,8 +14,29 @@ import {FormsModule} from '@angular/forms';
|
||||
})
|
||||
export class LoginComponent {
|
||||
|
||||
onSubmit($event: Event): void {
|
||||
private auth = inject(AuthService);
|
||||
|
||||
protected loginFormGroup = new FormGroup({
|
||||
usernameOrEmail: new FormControl('housemasterr'),
|
||||
password: new FormControl('kR0pNCspBKx8lOzAIch5'),
|
||||
keep: new FormControl(false),
|
||||
});
|
||||
|
||||
protected errors = signal<string[]>([]);
|
||||
|
||||
protected async onSubmit($event: Event): Promise<void> {
|
||||
$event.preventDefault();
|
||||
console.log('Login form submitted');
|
||||
|
||||
this.errors.set([]);
|
||||
|
||||
if (!this.loginFormGroup.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.auth.loginAsync(this.loginFormGroup.value as unknown as ValidateCredentialsCommand, error => {
|
||||
this.errors.set([...this.errors(), error.error.trim()]);
|
||||
});
|
||||
|
||||
console.log('login successful');
|
||||
}
|
||||
}
|
||||
|
||||
7
src/angular/frontend/src/proxy.conf.json
Normal file
7
src/angular/frontend/src/proxy.conf.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:5055",
|
||||
"secure": false,
|
||||
"logLevel": "debug"
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ public class AuthController : ControllerBase
|
||||
{
|
||||
Secure = true,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
SameSite = SameSiteMode.None,
|
||||
Expires = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10)
|
||||
});
|
||||
|
||||
|
||||
@ -26,6 +26,12 @@ var app = builder.Build();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
app.UseCors(x => x
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.SetIsOriginAllowed(origin => true) // allow any origin
|
||||
//.WithOrigins("https://localhost:44351")); // Allow only this origin can also have multiple origins separated with comma
|
||||
.AllowCredentials()); // allow credentials
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
@ -70,7 +70,6 @@ public class TokenRequestCommandHandler(TimeProvider timeProvider, IHashids hash
|
||||
{
|
||||
public async Task<Result<TokenRequestResponse>> Handle(TokenRequestCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateCredentialsResponse validateCredentialsResponse;
|
||||
CreateTokensCommand createTokensCommand;
|
||||
if (command.GrandType == GrandType.Password)
|
||||
{
|
||||
@ -85,7 +84,7 @@ public class TokenRequestCommandHandler(TimeProvider timeProvider, IHashids hash
|
||||
return validateCredentialsResult.AsErrorResult<TokenRequestResponse, ValidateCredentialsResponse>();
|
||||
}
|
||||
|
||||
validateCredentialsResponse = validateCredentialsResult.GetValue();
|
||||
var validateCredentialsResponse = validateCredentialsResult.GetValue();
|
||||
|
||||
if (!validateCredentialsResponse.IsValid)
|
||||
{
|
||||
|
||||
@ -6,14 +6,14 @@ public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddPalantirClient(this IServiceCollection services)
|
||||
{
|
||||
services.AddHttpClient<IPalantirInternalAuthClient, PalantirInternalAuthClient>(opt => { opt.BaseAddress = new Uri("https://localhost:7269/api/internal/auth/"); })
|
||||
services.AddHttpClient<IPalantirInternalAuthClient, PalantirInternalAuthClient>(opt => { opt.BaseAddress = new Uri("http://localhost:5035/api/internal/auth/"); })
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler //TODO: Remove this in production
|
||||
{
|
||||
ServerCertificateCustomValidationCallback =
|
||||
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
||||
});
|
||||
|
||||
services.AddHttpClient<IPalantirInternalAppClient, PalantirInternalAppClient>(opt => { opt.BaseAddress = new Uri("https://localhost:7269/api/internal/app/"); })
|
||||
services.AddHttpClient<IPalantirInternalAppClient, PalantirInternalAppClient>(opt => { opt.BaseAddress = new Uri("http://localhost:5035/api/internal/app/"); })
|
||||
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler //TODO: Remove this in production
|
||||
{
|
||||
ServerCertificateCustomValidationCallback =
|
||||
|
||||
@ -7,6 +7,22 @@ using Suspectus.Gandalf.Core.Abstractions.Extensions;
|
||||
|
||||
namespace Suspectus.Gandalf.Palantir.Client;
|
||||
|
||||
public abstract class ClientBase
|
||||
{
|
||||
protected readonly HttpClient Http;
|
||||
|
||||
protected ClientBase(HttpClient http)
|
||||
{
|
||||
Http = http;
|
||||
}
|
||||
|
||||
protected static async Task<Result<T>> HandleNotSuccess<T>(HttpResponseMessage response)
|
||||
{
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
return $"status: {response.StatusCode}{(string.IsNullOrWhiteSpace(body) ? "" : $" message: {body}")}".AsErrorResult<T>();
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPalantirClient
|
||||
{
|
||||
IPalantirInternalClient Internal { get; init; }
|
||||
@ -58,24 +74,19 @@ public class PalantirClient : IPalantirClient
|
||||
public IPalantirInternalClient Internal { get; init; }
|
||||
}
|
||||
|
||||
public class PalantirInternalAuthClient : IPalantirInternalAuthClient
|
||||
public class PalantirInternalAuthClient : ClientBase, IPalantirInternalAuthClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public PalantirInternalAuthClient(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
public PalantirInternalAuthClient(HttpClient http) : base(http) {}
|
||||
|
||||
public async Task<Result<bool>> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync("validate-credentials", validateCredentialsCommand);
|
||||
var response = await Http.PostAsJsonAsync("validate-credentials", validateCredentialsCommand);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return $"status: {response.StatusCode}".AsErrorResult<bool>();
|
||||
return await HandleNotSuccess<bool>(response);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<bool>();
|
||||
@ -91,11 +102,11 @@ public class PalantirInternalAuthClient : IPalantirInternalAuthClient
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync("token", command);
|
||||
var response = await Http.PostAsJsonAsync("token", command);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return $"status: {response.StatusCode}".AsErrorResult<TokenRequestResponse?>();
|
||||
return await HandleNotSuccess<TokenRequestResponse?>(response);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<TokenRequestResponse?>();
|
||||
@ -108,24 +119,19 @@ public class PalantirInternalAuthClient : IPalantirInternalAuthClient
|
||||
}
|
||||
}
|
||||
|
||||
public class PalantirInternalAppClient : IPalantirInternalAppClient
|
||||
public class PalantirInternalAppClient : ClientBase, IPalantirInternalAppClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public PalantirInternalAppClient(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
public PalantirInternalAppClient(HttpClient http) : base(http) {}
|
||||
|
||||
public async Task<Result<AppInfo>> GetInfo(string appId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.GetAsync($"info/{appId}");
|
||||
var response = await Http.GetAsync($"info/{appId}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return $"status: {response.StatusCode}".AsErrorResult<AppInfo>();
|
||||
return await HandleNotSuccess<AppInfo>(response);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
||||
@ -147,11 +153,11 @@ public class PalantirInternalAppClient : IPalantirInternalAppClient
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.GetAsync($"master/info");
|
||||
var response = await Http.GetAsync($"master/info");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return $"status: {response.StatusCode}".AsErrorResult<AppInfo>();
|
||||
return await HandleNotSuccess<AppInfo>(response);
|
||||
}
|
||||
|
||||
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user