prototype login frontend
This commit is contained in:
parent
ae59139fdf
commit
a3a9e57dea
@ -73,6 +73,9 @@
|
|||||||
"buildTarget": "frontend:build:development"
|
"buildTarget": "frontend:build:development"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"options": {
|
||||||
|
"proxyConfig": "src/proxy.conf.json"
|
||||||
|
},
|
||||||
"defaultConfiguration": "development"
|
"defaultConfiguration": "development"
|
||||||
},
|
},
|
||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
|
|||||||
@ -2,7 +2,17 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
|||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import {GandalfClientBaseAPI} from './clients/gandalf/gandalf-client';
|
||||||
|
import {provideHttpClient} from '@angular/common/http';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
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)">
|
@if (errors().length > 0) {
|
||||||
<input required type="text" name="username" placeholder="Username">
|
<div class="errors">
|
||||||
<input required type="password" name="password" placeholder="*****">
|
@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">
|
<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>
|
<label for="remember">Keep me signed in</label><br>
|
||||||
<input type="submit" value="Login">
|
<input type="submit" value="Login">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, inject, signal} from '@angular/core';
|
||||||
import {FormsModule} from '@angular/forms';
|
import {FormControl, FormGroup, FormsModule, FormSubmittedEvent, ReactiveFormsModule} from '@angular/forms';
|
||||||
|
import {AuthService, ValidateCredentialsCommand} from '../../clients/gandalf/mithrandir/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-login',
|
selector: 'app-login',
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule
|
FormsModule,
|
||||||
|
ReactiveFormsModule
|
||||||
],
|
],
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrl: './login.component.scss',
|
styleUrl: './login.component.scss',
|
||||||
@ -12,8 +14,29 @@ import {FormsModule} from '@angular/forms';
|
|||||||
})
|
})
|
||||||
export class LoginComponent {
|
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();
|
$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,
|
Secure = true,
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
SameSite = SameSiteMode.Lax,
|
SameSite = SameSiteMode.None,
|
||||||
Expires = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10)
|
Expires = tokenRequestResponse.RefreshTokenExpiresAt.AddSeconds(-10)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,12 @@ var app = builder.Build();
|
|||||||
if (app.Environment.IsDevelopment())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.MapOpenApi();
|
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();
|
app.UseHttpsRedirection();
|
||||||
|
|||||||
@ -70,7 +70,6 @@ public class TokenRequestCommandHandler(TimeProvider timeProvider, IHashids hash
|
|||||||
{
|
{
|
||||||
public async Task<Result<TokenRequestResponse>> Handle(TokenRequestCommand command, CancellationToken cancellationToken)
|
public async Task<Result<TokenRequestResponse>> Handle(TokenRequestCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ValidateCredentialsResponse validateCredentialsResponse;
|
|
||||||
CreateTokensCommand createTokensCommand;
|
CreateTokensCommand createTokensCommand;
|
||||||
if (command.GrandType == GrandType.Password)
|
if (command.GrandType == GrandType.Password)
|
||||||
{
|
{
|
||||||
@ -85,7 +84,7 @@ public class TokenRequestCommandHandler(TimeProvider timeProvider, IHashids hash
|
|||||||
return validateCredentialsResult.AsErrorResult<TokenRequestResponse, ValidateCredentialsResponse>();
|
return validateCredentialsResult.AsErrorResult<TokenRequestResponse, ValidateCredentialsResponse>();
|
||||||
}
|
}
|
||||||
|
|
||||||
validateCredentialsResponse = validateCredentialsResult.GetValue();
|
var validateCredentialsResponse = validateCredentialsResult.GetValue();
|
||||||
|
|
||||||
if (!validateCredentialsResponse.IsValid)
|
if (!validateCredentialsResponse.IsValid)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,14 +6,14 @@ public static class ServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
public static IServiceCollection AddPalantirClient(this IServiceCollection services)
|
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
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler //TODO: Remove this in production
|
||||||
{
|
{
|
||||||
ServerCertificateCustomValidationCallback =
|
ServerCertificateCustomValidationCallback =
|
||||||
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
|
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
|
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler //TODO: Remove this in production
|
||||||
{
|
{
|
||||||
ServerCertificateCustomValidationCallback =
|
ServerCertificateCustomValidationCallback =
|
||||||
|
|||||||
@ -7,6 +7,22 @@ using Suspectus.Gandalf.Core.Abstractions.Extensions;
|
|||||||
|
|
||||||
namespace Suspectus.Gandalf.Palantir.Client;
|
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
|
public interface IPalantirClient
|
||||||
{
|
{
|
||||||
IPalantirInternalClient Internal { get; init; }
|
IPalantirInternalClient Internal { get; init; }
|
||||||
@ -58,24 +74,19 @@ public class PalantirClient : IPalantirClient
|
|||||||
public IPalantirInternalClient Internal { get; init; }
|
public IPalantirInternalClient Internal { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PalantirInternalAuthClient : IPalantirInternalAuthClient
|
public class PalantirInternalAuthClient : ClientBase, IPalantirInternalAuthClient
|
||||||
{
|
{
|
||||||
private readonly HttpClient _http;
|
public PalantirInternalAuthClient(HttpClient http) : base(http) {}
|
||||||
|
|
||||||
public PalantirInternalAuthClient(HttpClient http)
|
|
||||||
{
|
|
||||||
_http = http;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result<bool>> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand)
|
public async Task<Result<bool>> ValidateCredentials(ValidateCredentialsCommand validateCredentialsCommand)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _http.PostAsJsonAsync("validate-credentials", validateCredentialsCommand);
|
var response = await Http.PostAsJsonAsync("validate-credentials", validateCredentialsCommand);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return $"status: {response.StatusCode}".AsErrorResult<bool>();
|
return await HandleNotSuccess<bool>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<bool>();
|
var result = await response.Content.ReadFromJsonAsync<bool>();
|
||||||
@ -91,11 +102,11 @@ public class PalantirInternalAuthClient : IPalantirInternalAuthClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _http.PostAsJsonAsync("token", command);
|
var response = await Http.PostAsJsonAsync("token", command);
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return $"status: {response.StatusCode}".AsErrorResult<TokenRequestResponse?>();
|
return await HandleNotSuccess<TokenRequestResponse?>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<TokenRequestResponse?>();
|
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) : base(http) {}
|
||||||
|
|
||||||
public PalantirInternalAppClient(HttpClient http)
|
|
||||||
{
|
|
||||||
_http = http;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Result<AppInfo>> GetInfo(string appId)
|
public async Task<Result<AppInfo>> GetInfo(string appId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _http.GetAsync($"info/{appId}");
|
var response = await Http.GetAsync($"info/{appId}");
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return $"status: {response.StatusCode}".AsErrorResult<AppInfo>();
|
return await HandleNotSuccess<AppInfo>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
||||||
@ -147,11 +153,11 @@ public class PalantirInternalAppClient : IPalantirInternalAppClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _http.GetAsync($"master/info");
|
var response = await Http.GetAsync($"master/info");
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return $"status: {response.StatusCode}".AsErrorResult<AppInfo>();
|
return await HandleNotSuccess<AppInfo>(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
var result = await response.Content.ReadFromJsonAsync<AppInfo>();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user