Archived
Private
Public Access
1
0

Finished Login / Register system

This commit is contained in:
2022-09-07 12:12:42 +02:00
parent 15f48d259f
commit 9122d513ea
24 changed files with 450 additions and 27 deletions

View File

@@ -31,6 +31,7 @@
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
@@ -99,6 +100,7 @@
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []

View File

@@ -9,10 +9,12 @@
"version": "0.0.0",
"dependencies": {
"@angular/animations": "~13.1.0",
"@angular/cdk": "^13.3.9",
"@angular/common": "~13.1.0",
"@angular/compiler": "~13.1.0",
"@angular/core": "~13.1.0",
"@angular/forms": "~13.1.0",
"@angular/material": "^13.3.9",
"@angular/platform-browser": "~13.1.0",
"@angular/platform-browser-dynamic": "~13.1.0",
"@angular/router": "~13.1.0",
@@ -347,6 +349,28 @@
"@angular/core": "13.1.3"
}
},
"node_modules/@angular/cdk": {
"version": "13.3.9",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.9.tgz",
"integrity": "sha512-XCuCbeuxWFyo3EYrgEYx7eHzwl76vaWcxtWXl00ka8d+WAOtMQ6Tf1D98ybYT5uwF9889fFpXAPw98mVnlo3MA==",
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^5.0.0"
},
"peerDependencies": {
"@angular/common": "^13.0.0 || ^14.0.0-0",
"@angular/core": "^13.0.0 || ^14.0.0-0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/cdk/node_modules/parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
},
"node_modules/@angular/cli": {
"version": "13.1.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.1.4.tgz",
@@ -472,6 +496,23 @@
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
"version": "13.3.9",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.9.tgz",
"integrity": "sha512-FU8lcMgo+AL8ckd27B4V097ZPoIZNRHiCe3wpgkImT1qC0YwcyXZVn0MqQTTFSdC9a/aI8wPm3AbTClJEVw5Vw==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^13.0.0 || ^14.0.0-0",
"@angular/cdk": "13.3.9",
"@angular/common": "^13.0.0 || ^14.0.0-0",
"@angular/core": "^13.0.0 || ^14.0.0-0",
"@angular/forms": "^13.0.0 || ^14.0.0-0",
"@angular/platform-browser": "^13.0.0 || ^14.0.0-0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.1.3.tgz",
@@ -11779,6 +11820,23 @@
"tslib": "^2.3.0"
}
},
"@angular/cdk": {
"version": "13.3.9",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.9.tgz",
"integrity": "sha512-XCuCbeuxWFyo3EYrgEYx7eHzwl76vaWcxtWXl00ka8d+WAOtMQ6Tf1D98ybYT5uwF9889fFpXAPw98mVnlo3MA==",
"requires": {
"parse5": "^5.0.0",
"tslib": "^2.3.0"
},
"dependencies": {
"parse5": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
"optional": true
}
}
},
"@angular/cli": {
"version": "13.1.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.1.4.tgz",
@@ -11857,6 +11915,14 @@
"tslib": "^2.3.0"
}
},
"@angular/material": {
"version": "13.3.9",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.9.tgz",
"integrity": "sha512-FU8lcMgo+AL8ckd27B4V097ZPoIZNRHiCe3wpgkImT1qC0YwcyXZVn0MqQTTFSdC9a/aI8wPm3AbTClJEVw5Vw==",
"requires": {
"tslib": "^2.3.0"
}
},
"@angular/platform-browser": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.1.3.tgz",

View File

@@ -11,10 +11,12 @@
"private": true,
"dependencies": {
"@angular/animations": "~13.1.0",
"@angular/cdk": "^13.3.9",
"@angular/common": "~13.1.0",
"@angular/compiler": "~13.1.0",
"@angular/core": "~13.1.0",
"@angular/forms": "~13.1.0",
"@angular/material": "^13.3.9",
"@angular/platform-browser": "~13.1.0",
"@angular/platform-browser-dynamic": "~13.1.0",
"@angular/router": "~13.1.0",
@@ -36,4 +38,4 @@
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.2"
}
}
}

View File

@@ -1,9 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {LoginComponent} from "./sites/login/login.component";
import {RegisterComponent} from "./sites/register/register.component";
const routes: Routes = [
{path: "login", component: LoginComponent}
{path: "login", component: LoginComponent},
{path: "register", component: RegisterComponent}
];
@NgModule({

View File

@@ -22,6 +22,7 @@ export class AppComponent implements OnInit {
if (await this.backend.requestToken()) this.loaded = true;
else await this.router.navigate(["login"]);
this.loaded = true;
}, 0);
}
}

View File

@@ -5,16 +5,30 @@ import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {HttpClientModule} from "@angular/common/http";
import { LoginComponent } from './sites/login/login.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {MatFormFieldModule} from "@angular/material/form-field";
import {MatInputModule} from "@angular/material/input";
import {MatButtonModule} from "@angular/material/button";
import {MatDividerModule} from "@angular/material/divider";
import { RegisterComponent } from './sites/register/register.component';
import {ReactiveFormsModule} from "@angular/forms";
@NgModule({
declarations: [
AppComponent,
LoginComponent
LoginComponent,
RegisterComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
HttpClientModule,
BrowserAnimationsModule,
MatFormFieldModule,
MatInputModule,
MatButtonModule,
MatDividerModule,
ReactiveFormsModule
],
providers: [],
bootstrap: [AppComponent]

View File

@@ -1,6 +1,6 @@
export interface User extends UserEditor {
id: string;
created: Date;
created: string;
}
export interface UserLogin {

View File

@@ -74,7 +74,7 @@ export class BackendService {
}
}
return {content: undefined, success: false, code: error.status, message: error.error.title};
return {content: undefined, success: false, code: error.status, message: error.error};
}
}

View File

@@ -32,8 +32,8 @@ export class UserApi {
return response.success;
}
public async getUserPermissions(id: string, includeGroupPermissions: boolean = true): Promise<string[]> {
const response = await this.backend.sendRequest<string[]>(RequestTypes.GET, "users/" + id + "/permissions" + (includeGroupPermissions ? "/raw" : ""), undefined, {authorized: true});
public async getUserPermissions(id: string, excludeGroupPermissions: boolean = false): Promise<string[]> {
const response = await this.backend.sendRequest<string[]>(RequestTypes.GET, "users/" + id + "/permissions" + (excludeGroupPermissions ? "/raw" : ""), undefined, {authorized: true});
if (!response.success) return [];
return response.content;
}
@@ -48,7 +48,7 @@ export class UserApi {
return response.success;
}
public async login(login: UserLogin): Promise<{success: boolean, errorMessage: string}> {
public async login(login: UserLogin): Promise<{success: boolean, errorMessage: string, errorCode: number}> {
const response = await this.backend.sendRequest<AccessToken>(RequestTypes.PUT, "users/login", login, {withCredentials: true});
if (response.success) {
@@ -56,10 +56,10 @@ export class UserApi {
await this.getAuthorizedUser();
}
return {success: response.success, errorMessage: response.message};
return {success: response.success, errorMessage: response.message, errorCode: response.code};
}
public async register(register: UserEditor): Promise<{success: boolean, errorMessage: string}> {
public async register(register: UserEditor): Promise<{success: boolean, errorMessage: string, errorCode: number}> {
const response = await this.backend.sendRequest<AccessToken>(RequestTypes.POST, "users/register", register, {withCredentials: true});
if (response.success) {
@@ -67,7 +67,7 @@ export class UserApi {
await this.getAuthorizedUser();
}
return {success: response.success, errorMessage: response.message};
return {success: response.success, errorMessage: response.message, errorCode: response.code};
}
public async logout(id: string): Promise<boolean> {

View File

@@ -1 +1,24 @@
<p>login works!</p>
<section class="login">
<form class="glass" (submit)="$event.preventDefault(); login(form, username.value, password.value)" #form>
<mat-form-field appearance="fill">
<mat-label>Benutzername oder E-Mail</mat-label>
<input matInput type="text" required #username>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Passwort</mat-label>
<input matInput type="password" required #password>
</mat-form-field>
<button mat-button type="submit" [disabled]="disableLogin">Einloggen</button>
<mat-divider></mat-divider>
<div>
<span>Du besitzt keinen Account? </span>
<a routerLink="/register">Registrieren</a>
</div>
</form>
</section>
<div class="error" #error></div>

View File

@@ -0,0 +1,42 @@
@use "src/styles";
.login {
width: 100vw;
height: 100vh;
overflow: hidden;
background-image: url(styles.$background);
display: flex;
align-items: center;
justify-content: center;
form {
padding: 30px;
display: flex;
gap: 5px;
flex-direction: column;
button[type="submit"] {
width: max-content;
align-self: center;
}
mat-divider {
margin-top: 10px;
}
}
}
.error {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
background-color: #F00;
padding: 10px;
border-radius: 5px;
opacity: 0;
transition: 500ms;
}

View File

@@ -1,15 +1,45 @@
import { Component, OnInit } from '@angular/core';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {UserApi} from "../../services/users.service";
import {UserLogin} from "../../entitys/user";
import {Router} from "@angular/router";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
export class LoginComponent {
@ViewChild('error') error: ElementRef;
public disableLogin: boolean = false;
constructor() { }
constructor(private users: UserApi, private router: Router) { }
ngOnInit(): void {
public async login(form: HTMLFormElement, username: string, password: string) {
if (!form.reportValidity()) return;
const login: UserLogin = {usernameOrEmail: username, password: password};
const response = await this.users.login(login);
if (!response.success) {
this.showError(response.errorMessage)
return;
}
await this.router.navigate([""]);
}
public showError(error: string) {
this.disableLogin = true;
this.error.nativeElement.innerText = error;
this.error.nativeElement.style.opacity = "1";
this.error.nativeElement.style.bottom = "150px";
setTimeout(() => {
this.error.nativeElement.style.opacity = "0";
this.error.nativeElement.style.bottom = "120px";
this.disableLogin = false;
}, 5000)
}
}

View File

@@ -0,0 +1,57 @@
<section class="login">
<form class="glass" (submit)="$event.preventDefault(); register(form, {
username: username.value,
password: password.value,
firstName: firstname.value,
lastName: lastname.value,
email: email.value
}, pwRepeat.value)" #form>
<table>
<tr>
<td>
<mat-form-field appearance="fill">
<mat-label>Vorname</mat-label>
<input matInput type="text" required #firstname>
</mat-form-field>
</td>
<td>
<mat-form-field appearance="fill">
<mat-label>Nachname</mat-label>
<input matInput type="text" required #lastname>
</mat-form-field>
</td>
</tr>
</table>
<mat-form-field appearance="fill">
<mat-label>Benutzername</mat-label>
<input matInput type="text" required #username>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>E-Mail</mat-label>
<input matInput type="email" required #email>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Passwort</mat-label>
<input matInput type="password" required #password>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Passwort wiederholen</mat-label>
<input matInput type="password" required #pwRepeat>
</mat-form-field>
<button mat-button [disabled]="disableRegister" type="submit">Registrieren</button>
<mat-divider></mat-divider>
<div>
<span>Du hast bereits einen Account? </span>
<a routerLink="/login">Einloggen</a>
</div>
</form>
</section>
<div class="error" #error></div>

View File

@@ -0,0 +1,42 @@
@use "src/styles";
.login {
width: 100vw;
height: 100vh;
overflow: hidden;
background-image: url(styles.$background);
display: flex;
align-items: center;
justify-content: center;
form {
padding: 30px;
display: flex;
gap: 5px;
flex-direction: column;
button[type="submit"] {
width: max-content;
align-self: center;
}
mat-divider {
margin-top: 10px;
}
}
}
.error {
position: absolute;
bottom: 120px;
left: 50%;
transform: translateX(-50%);
background-color: #F00;
padding: 10px;
border-radius: 5px;
opacity: 0;
transition: 500ms;
}

View File

@@ -0,0 +1,64 @@
import {Component, ElementRef, ViewChild} from '@angular/core';
import {UserApi} from "../../services/users.service";
import {Router} from "@angular/router";
import {UserEditor} from "../../entitys/user";
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.scss']
})
export class RegisterComponent {
@ViewChild('error') error: ElementRef;
public disableRegister: boolean = false;
constructor(private users: UserApi, private router: Router) { }
public async register(form: HTMLFormElement, register: UserEditor, pwRepeat: string) {
if (!form.reportValidity()) return;
if (register.password !== pwRepeat) {
this.showError("Passwörter stimmen nicht überein!");
return;
}
if (!register.email.includes("@") || !register.email.includes(".")) {
this.showError("Bitte gebe eine gültige E-Mail Adresse ein!");
return;
}
if (register.username.includes("@")) {
this.showError("Benutzername enthätlt ungültige Zeichen!");
return;
}
if (register.password.length < 8) {
this.showError("Dein Passwort muss mindestens 8 Zeichen lang sein!");
return;
}
const response = await this.users.register(register);
if (!response.success) {
this.showError(response.errorMessage);
return;
}
await this.router.navigate([""]);
}
public showError(error: string) {
this.disableRegister = true;
this.error.nativeElement.innerText = error;
this.error.nativeElement.style.opacity = "1";
this.error.nativeElement.style.bottom = "150px";
setTimeout(() => {
this.error.nativeElement.style.opacity = "0";
this.error.nativeElement.style.bottom = "120px";
this.disableRegister = false;
}, 5000)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

4
Frontend/src/colors.scss Normal file
View File

@@ -0,0 +1,4 @@
$dark: #0D1321;
$medium: #1D2D44;
$light: #3E5C76;
$text: #FFFFFF;

View File

@@ -6,8 +6,11 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<body class="mat-typography">
<app-root></app-root>
</body>
</html>

View File

@@ -1 +1,31 @@
/* You can add global styles to this file, and also import other style files */
@use "colors";
html, body { height: 100%; overflow: hidden; }
body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }
$background: "/assets/background.png";
* {
color: colors.$text;
}
.glass {
background: rgba(colors.$light, 0.15);
backdrop-filter: blur(10px);
border-radius: 20px;
overflow: hidden;
}
@mixin mat-select-theme() {
.mat-form-field-appearance-fill .mat-form-field-underline::before {
background-color: colors.$text;
}
.mat-divider {
border-top-color: colors.$text;
}
}
@include mat-select-theme();