Compare commits
3 Commits
0be5c36194
...
b14a4a38a0
Author | SHA1 | Date |
---|---|---|
Igor Hrenowitsch Propisnov | b14a4a38a0 | |
Igor Hrenowitsch Propisnov | 38d127c948 | |
Igor Hrenowitsch Propisnov | bdc9ec3ee0 |
|
@ -28,6 +28,9 @@
|
||||||
"@angular/platform-browser": "^17.3.0",
|
"@angular/platform-browser": "^17.3.0",
|
||||||
"@angular/platform-browser-dynamic": "^17.3.0",
|
"@angular/platform-browser-dynamic": "^17.3.0",
|
||||||
"@angular/router": "^17.3.0",
|
"@angular/router": "^17.3.0",
|
||||||
|
"@types/dompurify": "^3.0.5",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"dompurify": "^3.1.3",
|
||||||
"primeng": "^17.11.0",
|
"primeng": "^17.11.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
|
@ -45,6 +48,7 @@
|
||||||
"@stylistic/eslint-plugin": "^2.1.0",
|
"@stylistic/eslint-plugin": "^2.1.0",
|
||||||
"@stylistic/eslint-plugin-migrate": "^2.1.0",
|
"@stylistic/eslint-plugin-migrate": "^2.1.0",
|
||||||
"@stylistic/eslint-plugin-ts": "^2.1.0",
|
"@stylistic/eslint-plugin-ts": "^2.1.0",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/jest": "^29.5.12",
|
"@types/jest": "^29.5.12",
|
||||||
"@typescript-eslint/eslint-plugin": "6.19.0",
|
"@typescript-eslint/eslint-plugin": "6.19.0",
|
||||||
"@typescript-eslint/parser": "6.19.0",
|
"@typescript-eslint/parser": "6.19.0",
|
||||||
|
|
|
@ -29,6 +29,15 @@ dependencies:
|
||||||
'@angular/router':
|
'@angular/router':
|
||||||
specifier: ^17.3.0
|
specifier: ^17.3.0
|
||||||
version: 17.3.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/platform-browser@17.3.0)(rxjs@7.8.1)
|
version: 17.3.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/platform-browser@17.3.0)(rxjs@7.8.1)
|
||||||
|
'@types/dompurify':
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
|
crypto-js:
|
||||||
|
specifier: ^4.2.0
|
||||||
|
version: 4.2.0
|
||||||
|
dompurify:
|
||||||
|
specifier: ^3.1.3
|
||||||
|
version: 3.1.3
|
||||||
primeng:
|
primeng:
|
||||||
specifier: ^17.11.0
|
specifier: ^17.11.0
|
||||||
version: 17.11.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/forms@17.3.0)(rxjs@7.8.1)(zone.js@0.14.4)
|
version: 17.11.0(@angular/common@17.3.0)(@angular/core@17.3.0)(@angular/forms@17.3.0)(rxjs@7.8.1)(zone.js@0.14.4)
|
||||||
|
@ -76,6 +85,9 @@ devDependencies:
|
||||||
'@stylistic/eslint-plugin-ts':
|
'@stylistic/eslint-plugin-ts':
|
||||||
specifier: ^2.1.0
|
specifier: ^2.1.0
|
||||||
version: 2.1.0(eslint@8.57.0)(typescript@5.4.2)
|
version: 2.1.0(eslint@8.57.0)(typescript@5.4.2)
|
||||||
|
'@types/crypto-js':
|
||||||
|
specifier: ^4.2.2
|
||||||
|
version: 4.2.2
|
||||||
'@types/jest':
|
'@types/jest':
|
||||||
specifier: ^29.5.12
|
specifier: ^29.5.12
|
||||||
version: 29.5.12
|
version: 29.5.12
|
||||||
|
@ -3231,6 +3243,16 @@ packages:
|
||||||
'@types/node': 20.11.27
|
'@types/node': 20.11.27
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/crypto-js@4.2.2:
|
||||||
|
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@types/dompurify@3.0.5:
|
||||||
|
resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==}
|
||||||
|
dependencies:
|
||||||
|
'@types/trusted-types': 2.0.7
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/eslint-scope@3.7.7:
|
/@types/eslint-scope@3.7.7:
|
||||||
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -3400,6 +3422,10 @@ packages:
|
||||||
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
|
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/trusted-types@2.0.7:
|
||||||
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/ws@8.5.10:
|
/@types/ws@8.5.10:
|
||||||
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
|
resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4710,6 +4736,10 @@ packages:
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/crypto-js@4.2.0:
|
||||||
|
resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/css-loader@6.10.0(webpack@5.90.3):
|
/css-loader@6.10.0(webpack@5.90.3):
|
||||||
resolution: {integrity: sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==}
|
resolution: {integrity: sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==}
|
||||||
engines: {node: '>= 12.13.0'}
|
engines: {node: '>= 12.13.0'}
|
||||||
|
@ -4998,6 +5028,10 @@ packages:
|
||||||
domelementtype: 2.3.0
|
domelementtype: 2.3.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/dompurify@3.1.3:
|
||||||
|
resolution: {integrity: sha512-5sOWYSNPaxz6o2MUPvtyxTTqR4D3L77pr5rUQoWgD5ROQtVIZQgJkXbo1DLlK3vj11YGw5+LnF4SYti4gZmwng==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/domutils@3.1.0:
|
/domutils@3.1.0:
|
||||||
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content-zone">
|
<div class="content-zone">
|
||||||
<h1>
|
<h1>
|
||||||
@if (this.isSignupSignal()) {
|
@if (isSignupSignal()) {
|
||||||
Anmelden
|
Anmelden
|
||||||
} @else if (this.isRegisterSignal()) {
|
} @else if (isRegisterSignal()) {
|
||||||
Registrieren
|
Registrieren
|
||||||
} @else {
|
} @else {
|
||||||
Erste Schritte
|
Erste Schritte
|
||||||
}
|
}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@if (this.isDisplayButtons()) {
|
@if (isDisplayButtons()) {
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button
|
<button
|
||||||
pButton
|
pButton
|
||||||
|
@ -30,10 +30,10 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div
|
@if (isSignupSignal() || isRegisterSignal()) {
|
||||||
class="register-wrapper"
|
<div class="register-wrapper">
|
||||||
*ngIf="isSignupSignal() || isRegisterSignal()">
|
@if (form) {
|
||||||
<form [formGroup]="form" *ngIf="form" (ngSubmit)="onSubmit()">
|
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||||
<div class="e-mail">
|
<div class="e-mail">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<label for="email">E-Mail</label>
|
<label for="email">E-Mail</label>
|
||||||
|
@ -55,13 +55,13 @@
|
||||||
aria-describedby="password"
|
aria-describedby="password"
|
||||||
[toggleMask]="true"></p-password>
|
[toggleMask]="true"></p-password>
|
||||||
</div>
|
</div>
|
||||||
@if (this.isRegisterSignal()) {
|
@if (isRegisterSignal()) {
|
||||||
<div class="terms">
|
<div class="terms">
|
||||||
<p-checkbox
|
<p-checkbox
|
||||||
formControlName="terms"
|
formControlName="terms"
|
||||||
label="Ich habe die AGB gelesen und stimme zu."
|
label="Ich habe die AGB gelesen und stimme zu."
|
||||||
name="terms"
|
name="terms"
|
||||||
[binary]="true" />
|
[binary]="true"></p-checkbox>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="signup">
|
<div class="signup">
|
||||||
|
@ -69,12 +69,17 @@
|
||||||
pButton
|
pButton
|
||||||
type="submit"
|
type="submit"
|
||||||
[label]="
|
[label]="
|
||||||
isSignupSignal() ? 'Anmelden' : '✨ Jetzt KOSTENFREI loslegen ✨'
|
isSignupSignal()
|
||||||
|
? 'Anmelden'
|
||||||
|
: '✨ Jetzt KOSTENFREI loslegen ✨'
|
||||||
"></button>
|
"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="change-mask">
|
<div class="change-mask">
|
||||||
<a (click)="switchMask()" (keyup.enter)="switchMask()" tabindex="0">
|
<a
|
||||||
@if (this.isSignupSignal()) {
|
(click)="switchMask()"
|
||||||
|
(keyup.enter)="switchMask()"
|
||||||
|
tabindex="0">
|
||||||
|
@if (isSignupSignal()) {
|
||||||
Kein Account? Erstellen Sie jetzt KOSTENFREI einen!
|
Kein Account? Erstellen Sie jetzt KOSTENFREI einen!
|
||||||
} @else {
|
} @else {
|
||||||
Schon einen Account? Hier einloggen
|
Schon einen Account? Hier einloggen
|
||||||
|
@ -82,6 +87,8 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,38 +1,77 @@
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { LoginCredentials, Tokens } from '../types';
|
import { LoginCredentials, Tokens } from '../types';
|
||||||
|
|
||||||
|
import { LocalStorageService } from './local-storage.service';
|
||||||
|
import { SessionStorageService } from './session-storage.service';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private isAuthenticated: boolean = false;
|
private readonly path: string = '/api/auth';
|
||||||
private access_token: string | null = null;
|
private access_token: string | null = null;
|
||||||
private refresh_token: string | null = null;
|
private refresh_token: string | null = null;
|
||||||
|
private isAuthenticated$: BehaviorSubject<boolean> =
|
||||||
|
new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly httpClient: HttpClient,
|
private readonly httpClient: HttpClient,
|
||||||
private readonly router: Router
|
private readonly localStorageService: LocalStorageService,
|
||||||
|
private readonly sessionStorageService: SessionStorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public signin(credentials: LoginCredentials): void {
|
public signin(credentials: LoginCredentials): void {
|
||||||
this.httpClient
|
this.httpClient
|
||||||
.post<Tokens>(environment.api.base + '/api/auth/signin', credentials)
|
.post<Tokens>(environment.api.base + `${this.path}/signin`, credentials)
|
||||||
.subscribe((response: Tokens) => {
|
.subscribe((response: Tokens) => {
|
||||||
this.access_token = response.access_token;
|
this.handleSuccess(response);
|
||||||
this.refresh_token = response.refresh_token;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public signup(credentials: LoginCredentials): void {
|
public signup(credentials: LoginCredentials): void {
|
||||||
this.httpClient
|
this.httpClient
|
||||||
.post<Tokens>(environment.api.base + '/api/auth/signup', credentials)
|
.post<Tokens>(environment.api.base + `${this.path}/signup`, credentials)
|
||||||
.subscribe((response: Tokens) => {
|
.subscribe((response: Tokens) => {
|
||||||
this.access_token = response.access_token;
|
// The checked accept terms should be saved with a timestamp in the db
|
||||||
this.refresh_token = response.refresh_token;
|
this.handleSuccess(response);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public signout(): void {
|
||||||
|
this.access_token = null;
|
||||||
|
this.refresh_token = null;
|
||||||
|
this.localStorageService.removeItem('access_token');
|
||||||
|
this.sessionStorageService.removeItem('refresh_token');
|
||||||
|
this.isAuthenticated$.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshToken(): void {
|
||||||
|
const headers = new HttpHeaders().set(
|
||||||
|
'Authorization',
|
||||||
|
'Bearer ' + this.refresh_token
|
||||||
|
);
|
||||||
|
|
||||||
|
this.httpClient
|
||||||
|
.post<Tokens>(
|
||||||
|
environment.api.base + `${this.path}/refresh`,
|
||||||
|
{},
|
||||||
|
{ headers: headers }
|
||||||
|
)
|
||||||
|
.subscribe((response: Tokens) => {
|
||||||
|
this.handleSuccess(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSuccess(tokens: Tokens): void {
|
||||||
|
this.access_token = tokens.access_token;
|
||||||
|
this.refresh_token = tokens.refresh_token;
|
||||||
|
this.localStorageService.setItem('access_token', tokens.access_token);
|
||||||
|
this.sessionStorageService.setItem('refresh_token', tokens.refresh_token);
|
||||||
|
this.isAuthenticated$.next(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import { EncryptionService } from './encryption.service';
|
||||||
|
import { SanitizationService } from './sanitization.service';
|
||||||
|
|
||||||
|
export abstract class BaseStorageService {
|
||||||
|
protected abstract getStorage(): Storage;
|
||||||
|
|
||||||
|
public setItem<T>(key: string, value: T): void {
|
||||||
|
const sanitizedValue = SanitizationService.sanitize(JSON.stringify(value));
|
||||||
|
const encryptedValue = EncryptionService.encrypt(sanitizedValue);
|
||||||
|
|
||||||
|
this.getStorage().setItem(key, encryptedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getItem<T>(key: string): T | null {
|
||||||
|
const encryptedValue = this.getStorage().getItem(key);
|
||||||
|
|
||||||
|
if (encryptedValue) {
|
||||||
|
const decryptedValue = EncryptionService.decrypt(encryptedValue);
|
||||||
|
const sanitizedValue = SanitizationService.sanitize(decryptedValue);
|
||||||
|
|
||||||
|
return JSON.parse(sanitizedValue) as T;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeItem(key: string): void {
|
||||||
|
this.getStorage().removeItem(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.getStorage().clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
import * as CryptoJS from 'crypto-js';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
export class EncryptionService {
|
||||||
|
private static readonly key: string = environment.security.encryptionKey;
|
||||||
|
|
||||||
|
public static encrypt(data: string): string {
|
||||||
|
return CryptoJS.AES.encrypt(data, this.key).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static decrypt(data: string): string {
|
||||||
|
return CryptoJS.AES.decrypt(data, this.key).toString(CryptoJS.enc.Utf8);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1,3 @@
|
||||||
export * from './auth.service';
|
export * from './auth.service';
|
||||||
|
export * from './local-storage.service';
|
||||||
|
export * from './session-storage.service';
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseStorageService } from './base-storage.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class LocalStorageService extends BaseStorageService {
|
||||||
|
protected getStorage(): Storage {
|
||||||
|
return localStorage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
|
export class SanitizationService {
|
||||||
|
public static sanitize(data: string): string {
|
||||||
|
return DOMPurify.sanitize(data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseStorageService } from './base-storage.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SessionStorageService extends BaseStorageService {
|
||||||
|
protected getStorage(): Storage {
|
||||||
|
return sessionStorage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ export const environment = {
|
||||||
api: {
|
api: {
|
||||||
base: 'http://localhost:3000',
|
base: 'http://localhost:3000',
|
||||||
},
|
},
|
||||||
|
security: {
|
||||||
|
encryptionKey: 'my-secret',
|
||||||
|
},
|
||||||
oauth: {
|
oauth: {
|
||||||
clinetId: 'app_FLXnxSBnnaKkXoYCgk3J62iA',
|
clinetId: 'app_FLXnxSBnnaKkXoYCgk3J62iA',
|
||||||
redirectUri: 'https://commonly-hot-airedale.ngrok-free.app/oauth',
|
redirectUri: 'https://commonly-hot-airedale.ngrok-free.app/oauth',
|
||||||
|
|
Loading…
Reference in New Issue