setup Auth component

This commit is contained in:
Thomas Peetz
2025-11-12 21:06:28 +01:00
parent 66a93b2b97
commit 447030533f
6 changed files with 145 additions and 25 deletions
@@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { catchError, throwError } from 'rxjs';
interface AuthResponseData { interface AuthResponseData {
@@ -10,6 +11,11 @@ interface AuthResponseData {
expiresIn: string; expiresIn: string;
} }
export interface TokenData {
access_token: string;
token_type: string;
}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@@ -19,11 +25,30 @@ export class AuthService {
} }
signup(email: string, password: string) { signup(email: string, password: string) {
return this.http.post<AuthResponseData>('http://localhost:8800/signup', return this.http.post<TokenData>('http://localhost:8800/signup',
{ {
email: email, username: email,
password: password, password: password
returnToken: true }
).pipe(catchError(errorRes => {
let errorMessage = 'An unknown error occurred!';
const err = Error(errorMessage);
if (!errorRes.error) {
return throwError(() => err);
}
switch(errorRes.statusText) {
case 'ERROR':
errorMessage = 'Uups...';
}
return throwError(() => Error(errorMessage));
}));
}
login(email: string, password: string) {
return this.http.post<TokenData>('http://127.0.0.1:8800/api/login/token',
{
username: email,
password: password
} }
); );
} }
+28 -16
View File
@@ -1,22 +1,34 @@
<div class="row"> <div class="row">
<div style="margin-top: 16px;"> <div style="margin-top: 16px;">
<form (ngSubmit)="onSubmit(authForm)" #authForm="ngForm"> @if(error) {
<div class="form-group" style="margin-bottom: 7px;"> <div class="alert alter-danger">
<label for="email">E-Mail</label> <p>{{ error }}</p>
<input type="email" id="email" class="form-control" ngModel name="email" required email=""/>
</div> </div>
<div class="form-group" style="margin-bottom: 7px;"> }
<label for="password">Password</label> @if(isLoading) {
<input type="password" id="password" class="form-control" ngModel name="password" required/> <div style="text-align: center;">
<app-loading-spinner></app-loading-spinner>
</div> </div>
<div> }
<button class="btn btn-primary" type="submit" [disabled]="!authForm.valid"> @else {
{{ isLoginMode ? 'Login' : 'Sign Up' }} <form (ngSubmit)="onSubmit(authForm)" #authForm="ngForm">
</button> <div class="form-group" style="margin-bottom: 7px;">
<button class="btn btn-primary" (click)="onSwitchMode()" type="button"> <label for="email">E-Mail</label>
Switch to {{ isLoginMode ? 'Sign Up' : 'Login' }} <input type="email" id="email" class="form-control" ngModel name="email" required email=""/>
</button> </div>
</div> <div class="form-group" style="margin-bottom: 7px;">
</form> <label for="password">Password</label>
<input type="password" id="password" class="form-control" ngModel name="password" required/>
</div>
<div>
<button class="btn btn-primary" type="submit" [disabled]="!authForm.valid">
{{ isLoginMode ? 'Login' : 'Sign Up' }}
</button>
<button class="btn btn-primary" (click)="onSwitchMode()" type="button">
Switch to {{ isLoginMode ? 'Sign Up' : 'Login' }}
</button>
</div>
</form>
}
</div> </div>
</div> </div>
+35 -2
View File
@@ -1,21 +1,54 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { AuthService, TokenData } from './auth-service';
import { LoadingSpinner } from "../../shared/loading-spinner/loading-spinner";
import { Observable } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
imports: [FormsModule], imports: [FormsModule, LoadingSpinner],
templateUrl: './auth.html', templateUrl: './auth.html',
styleUrl: './auth.css' styleUrl: './auth.css'
}) })
export class Auth { export class Auth {
isLoginMode = true; isLoginMode = true;
isLoading = false;
error: string | null = null;
constructor(private authService: AuthService) {}
onSwitchMode() { onSwitchMode() {
this.isLoginMode =!this.isLoginMode; this.isLoginMode =!this.isLoginMode;
} }
onSubmit(form: NgForm) { onSubmit(form: NgForm) {
console.log(form.value); if (!form.valid) {
return;
}
const email = form.value.email;
const password = form.value.password;
let authObservable: Observable<TokenData>;
this.isLoading = true;
if (this.isLoginMode) {
authObservable = this.authService.login(email, password);
} else {
authObservable = this.authService.signup(email, password)
}
authObservable.subscribe(
token => {
console.log(token);
this.isLoading = false;
},
(err: HttpErrorResponse) => {
console.log(err);
this.isLoading = false;
}
);
form.reset(); form.reset();
} }
} }
@@ -0,0 +1,11 @@
import { Component } from "@angular/core"
@Component({
selector: 'app-loading-spinner',
template: '<div class="lds-ring"><div></div><div></div><div></div><div></div></div>',
styleUrls: ['./loading.spinner.css']
})
export class LoadingSpinner {}
@@ -0,0 +1,41 @@
.lds-ring,
.lds-ring div {
box-sizing: border-box;
}
.lds-ring {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 64px;
height: 64px;
margin: 8px;
border: 8px solid currentColor;
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: currentColor transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+1 -3
View File
@@ -45,9 +45,7 @@ class OAuth2PasswordBearerWithCookie(OAuth2):
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error) super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]: async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.cookies.get( authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
"access_token"
) # changed to accept access token from httpOnly Cookie
scheme, param = get_authorization_scheme_param(authorization) scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer": if not authorization or scheme.lower() != "bearer":