| @@ -2400,6 +2400,16 @@ components: | |||
| type: string | |||
| active: | |||
| type: boolean | |||
| roles: | |||
| readOnly: true | |||
| type: array | |||
| items: | |||
| type: string | |||
| token: | |||
| readOnly: true | |||
| type: | |||
| - string | |||
| - 'null' | |||
| createdAt: | |||
| readOnly: true | |||
| type: | |||
| @@ -2474,6 +2484,16 @@ components: | |||
| type: string | |||
| active: | |||
| type: boolean | |||
| roles: | |||
| readOnly: true | |||
| type: array | |||
| items: | |||
| type: string | |||
| token: | |||
| readOnly: true | |||
| type: | |||
| - string | |||
| - 'null' | |||
| createdAt: | |||
| readOnly: true | |||
| type: | |||
| @@ -2905,31 +2925,8 @@ components: | |||
| AuthResponse: | |||
| type: object | |||
| properties: | |||
| token: | |||
| type: string | |||
| readOnly: true | |||
| id: | |||
| type: integer | |||
| readOnly: true | |||
| email: | |||
| type: string | |||
| readOnly: true | |||
| firstName: | |||
| type: string | |||
| readOnly: true | |||
| lastName: | |||
| type: string | |||
| readOnly: true | |||
| roles: | |||
| type: array | |||
| items: | |||
| type: string | |||
| readOnly: true | |||
| user: | |||
| type: object | |||
| allOf: | |||
| - | |||
| $ref: '#/components/schemas/User' | |||
| $ref: '#/components/schemas/User.jsonld' | |||
| readOnly: true | |||
| responses: { } | |||
| parameters: { } | |||
| @@ -87,6 +87,8 @@ export const userForm = new FormGroup({ | |||
| fullName: new FormControl(null, []), | |||
| password: new FormControl(null, []), | |||
| active: new FormControl(null, []), | |||
| roles: new FormControl(null, []), | |||
| token: new FormControl(null, []), | |||
| createdAt: new FormControl(null, []) | |||
| }); | |||
| @@ -100,6 +102,8 @@ export const userJsonldForm = new FormGroup({ | |||
| fullName: new FormControl(null, []), | |||
| password: new FormControl(null, []), | |||
| active: new FormControl(null, []), | |||
| roles: new FormControl(null, []), | |||
| token: new FormControl(null, []), | |||
| createdAt: new FormControl(null, []) | |||
| }); | |||
| @@ -193,11 +197,5 @@ export const credentialsForm = new FormGroup({ | |||
| }); | |||
| export const authResponseForm = new FormGroup({ | |||
| token: new FormControl(null, []), | |||
| id: new FormControl(null, []), | |||
| email: new FormControl(null, []), | |||
| firstName: new FormControl(null, []), | |||
| lastName: new FormControl(null, []), | |||
| roles: new FormControl(null, []), | |||
| user: new FormControl(null, []) | |||
| }); | |||
| @@ -1,2 +1 @@ | |||
| export * from './alert'; | |||
| export * from './user'; | |||
| export * from './alert'; | |||
| @@ -1,12 +0,0 @@ | |||
| import {UserJsonld} from "@app/core/api/v1"; | |||
| export class User { | |||
| id?: string; | |||
| email?: string; | |||
| password?: string; | |||
| firstName?: string; | |||
| lastName?: string; | |||
| roles?: string[]; | |||
| token?: string; | |||
| userResource?: UserJsonld; | |||
| } | |||
| @@ -4,21 +4,19 @@ import { HttpClient } from '@angular/common/http'; | |||
| import { BehaviorSubject, Observable } from 'rxjs'; | |||
| import { map } from 'rxjs/operators'; | |||
| import { environment } from '@environments/environment'; | |||
| import { User } from '@app/_models'; | |||
| import {AuthService} from "@app/core/api/v1"; | |||
| import {AuthService, UserJsonld} from "@app/core/api/v1"; | |||
| @Injectable({ providedIn: 'root' }) | |||
| export class AccountService { | |||
| private userSubject: BehaviorSubject<User | null>; | |||
| public user: Observable<User | null>; | |||
| private userSubject: BehaviorSubject<UserJsonld | null>; | |||
| public user: Observable<UserJsonld | null>; | |||
| constructor( | |||
| private router: Router, | |||
| private http: HttpClient, | |||
| private loginCheckService: AuthService | |||
| private authService: AuthService | |||
| ) { | |||
| this.userSubject = new BehaviorSubject(JSON.parse(localStorage.getItem('user')!)); | |||
| this.user = this.userSubject.asObservable(); | |||
| @@ -29,24 +27,12 @@ export class AccountService { | |||
| } | |||
| login(email: string, password: string) { | |||
| return this.loginCheckService.postCredentialsItem( | |||
| { email, password } | |||
| ).pipe(map(authResponse => { | |||
| // store user details and jwt token in local storage to keep user logged in between page refreshes | |||
| localStorage.setItem('user', JSON.stringify(authResponse.user)); | |||
| if (authResponse.user) { | |||
| this.userSubject.next(authResponse.user); | |||
| } | |||
| return authResponse.user; | |||
| })); | |||
| // | |||
| // return this.http.post<User>(`${environment.apiUrl}/auth`, { email, password }) | |||
| // .pipe(map(user => { | |||
| // // store user details and jwt token in local storage to keep user logged in between page refreshes | |||
| // localStorage.setItem('user', JSON.stringify(user)); | |||
| // this.userSubject.next(user); | |||
| // return user; | |||
| // })); | |||
| return this.authService.postCredentialsItem({ email, password }) | |||
| .pipe(map(response => { | |||
| localStorage.setItem('user', JSON.stringify(response.user)); | |||
| this.userSubject.next(response.user || null); | |||
| return response.user; | |||
| })); | |||
| } | |||
| logout() { | |||
| @@ -73,43 +59,4 @@ export class AccountService { | |||
| } | |||
| return false; | |||
| } | |||
| register(user: User) { | |||
| return this.http.post(`${environment.apiUrl}/users/register`, user); | |||
| } | |||
| getAll() { | |||
| return this.http.get<User[]>(`${environment.apiUrl}/users`); | |||
| } | |||
| getById(id: string) { | |||
| return this.http.get<User>(`${environment.apiUrl}/users/${id}`); | |||
| } | |||
| update(id: string, params: any) { | |||
| return this.http.put(`${environment.apiUrl}/users/${id}`, params) | |||
| .pipe(map(x => { | |||
| // update stored user if the logged in user updated their own record | |||
| if (id == this.userValue?.id) { | |||
| // update local storage | |||
| const user = { ...this.userValue, ...params }; | |||
| localStorage.setItem('user', JSON.stringify(user)); | |||
| // publish updated user to subscribers | |||
| this.userSubject.next(user); | |||
| } | |||
| return x; | |||
| })); | |||
| } | |||
| delete(id: string) { | |||
| return this.http.delete(`${environment.apiUrl}/users/${id}`) | |||
| .pipe(map(x => { | |||
| // auto logout if the logged in user deleted their own record | |||
| if (id == this.userValue?.id) { | |||
| this.logout(); | |||
| } | |||
| return x; | |||
| })); | |||
| } | |||
| } | |||
| @@ -1,23 +0,0 @@ | |||
| import { Injectable } from '@angular/core'; | |||
| import {HttpClient, HttpHeaders} from "@angular/common/http"; | |||
| import {User} from "@app/_models"; | |||
| import {environment} from "@environments/environment"; | |||
| @Injectable({ | |||
| providedIn: 'root' | |||
| }) | |||
| export class LogService { | |||
| constructor( | |||
| private httpClient: HttpClient | |||
| ) { | |||
| } | |||
| getLogs(lines: number) { | |||
| return this.httpClient.get( | |||
| `${environment.apiUrl}/logs/logs/${lines}`, | |||
| ); | |||
| } | |||
| } | |||
| @@ -3,14 +3,12 @@ import { Routes, RouterModule } from '@angular/router'; | |||
| import { LayoutComponent } from './layout.component'; | |||
| import { LoginComponent } from './login.component'; | |||
| import { RegisterComponent } from './register.component'; | |||
| const routes: Routes = [ | |||
| { | |||
| path: '', component: LayoutComponent, | |||
| children: [ | |||
| { path: 'login', component: LoginComponent }, | |||
| { path: 'register', component: RegisterComponent } | |||
| ] | |||
| } | |||
| ]; | |||
| @@ -5,7 +5,6 @@ import { CommonModule } from '@angular/common'; | |||
| import { AccountRoutingModule } from './account-routing.module'; | |||
| import { LayoutComponent } from './layout.component'; | |||
| import { LoginComponent } from './login.component'; | |||
| import { RegisterComponent } from './register.component'; | |||
| @NgModule({ | |||
| imports: [ | |||
| @@ -16,7 +15,6 @@ import { RegisterComponent } from './register.component'; | |||
| declarations: [ | |||
| LayoutComponent, | |||
| LoginComponent, | |||
| RegisterComponent | |||
| ] | |||
| }) | |||
| export class AccountModule { } | |||
| @@ -1,43 +0,0 @@ | |||
| <div class="card"> | |||
| <h4 class="card-header">Register</h4> | |||
| <div class="card-body"> | |||
| <form [formGroup]="form" (ngSubmit)="onSubmit()"> | |||
| <div class="mb-3"> | |||
| <label class="form-label">First Name</label> | |||
| <input type="text" formControlName="firstName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['firstName'].errors }" /> | |||
| <div *ngIf="submitted && f['firstName'].errors" class="invalid-feedback"> | |||
| <div *ngIf="f['firstName'].hasError('required')">First Name is required</div> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label">Last Name</label> | |||
| <input type="text" formControlName="lastName" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['lastName'].errors }" /> | |||
| <div *ngIf="submitted && f['lastName'].errors" class="invalid-feedback"> | |||
| <div *ngIf="f['lastName'].hasError('required')">Last Name is required</div> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label">Username</label> | |||
| <input type="text" formControlName="username" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['username'].errors }" /> | |||
| <div *ngIf="submitted && f['username'].errors" class="invalid-feedback"> | |||
| <div *ngIf="f['username'].hasError('required')">Username is required</div> | |||
| </div> | |||
| </div> | |||
| <div class="mb-3"> | |||
| <label class="form-label">Password</label> | |||
| <input type="password" formControlName="password" class="form-control" [ngClass]="{ 'is-invalid': submitted && f['password'].errors }" /> | |||
| <div *ngIf="submitted && f['password'].errors" class="invalid-feedback"> | |||
| <div *ngIf="f['password'].hasError('required')">Password is required</div> | |||
| <div *ngIf="f['password'].hasError('minlength')">Password must be at least 6 characters</div> | |||
| </div> | |||
| </div> | |||
| <div> | |||
| <button [disabled]="loading" class="btn btn-primary"> | |||
| <span *ngIf="loading" class="spinner-border spinner-border-sm me-1"></span> | |||
| Register | |||
| </button> | |||
| <a routerLink="../login" class="btn btn-link">Cancel</a> | |||
| </div> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| @@ -1,59 +0,0 @@ | |||
| import { Component, OnInit } from '@angular/core'; | |||
| import { Router, ActivatedRoute } from '@angular/router'; | |||
| import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |||
| import { first } from 'rxjs/operators'; | |||
| import { AccountService, AlertService } from '@app/_services'; | |||
| @Component({ templateUrl: 'register.component.html' }) | |||
| export class RegisterComponent implements OnInit { | |||
| form!: FormGroup; | |||
| loading = false; | |||
| submitted = false; | |||
| constructor( | |||
| private formBuilder: FormBuilder, | |||
| private route: ActivatedRoute, | |||
| private router: Router, | |||
| private accountService: AccountService, | |||
| private alertService: AlertService | |||
| ) { } | |||
| ngOnInit() { | |||
| this.form = this.formBuilder.group({ | |||
| firstName: ['', Validators.required], | |||
| lastName: ['', Validators.required], | |||
| username: ['', Validators.required], | |||
| password: ['', [Validators.required, Validators.minLength(6)]] | |||
| }); | |||
| } | |||
| // convenience getter for easy access to form fields | |||
| get f() { return this.form.controls; } | |||
| onSubmit() { | |||
| this.submitted = true; | |||
| // reset alerts on submit | |||
| this.alertService.clear(); | |||
| // stop here if form is invalid | |||
| if (this.form.invalid) { | |||
| return; | |||
| } | |||
| this.loading = true; | |||
| this.accountService.register(this.form.value) | |||
| .pipe(first()) | |||
| .subscribe({ | |||
| next: () => { | |||
| this.alertService.success('Registration successful', { keepAfterRouteChange: true }); | |||
| this.router.navigate(['../login'], { relativeTo: this.route }); | |||
| }, | |||
| error: error => { | |||
| this.alertService.error(error); | |||
| this.loading = false; | |||
| } | |||
| }); | |||
| } | |||
| } | |||
| @@ -1,5 +1,4 @@ | |||
| import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'; | |||
| import {LogService} from "@app/_services/log.service"; | |||
| import {FormControl} from "@angular/forms"; | |||
| @Component({ | |||
| @@ -1,8 +1,8 @@ | |||
| import {AfterViewInit, Component, OnInit} from '@angular/core'; | |||
| import {User} from '@app/_models'; | |||
| import {AccountService} from '@app/_services'; | |||
| import {AppHelperService} from "@app/_helpers/app-helper.service"; | |||
| import {UserJsonld} from "@app/core/api/v1"; | |||
| @Component({ | |||
| templateUrl: 'home.component.html', | |||
| @@ -10,7 +10,7 @@ import {AppHelperService} from "@app/_helpers/app-helper.service"; | |||
| }) | |||
| export class HomeComponent implements OnInit, AfterViewInit { | |||
| protected user: User | null; | |||
| protected user: UserJsonld | null; | |||
| constructor( | |||
| private accountService: AccountService, | |||
| @@ -49,8 +49,8 @@ export class UserDetailComponent implements OnInit, AfterViewInit { | |||
| } | |||
| setIsCurrentUser() { | |||
| if (this.accountService.userValue?.userResource) { | |||
| let user = this.accountService.userValue?.userResource; | |||
| if (this.accountService.userValue) { | |||
| let user = this.accountService.userValue; | |||
| this.isCurrentUser = this.appHelperService.extractId(this.user.id) == user?.id; | |||
| } | |||
| } | |||
| @@ -1,11 +1,11 @@ | |||
| import {ChangeDetectorRef, Component, Inject, OnInit, Renderer2} from '@angular/core'; | |||
| import {AccountService} from './_services'; | |||
| import {User} from './_models'; | |||
| import {TranslateService} from "@ngx-translate/core"; | |||
| import {environment} from "@environments/environment"; | |||
| import {Subscription} from "rxjs"; | |||
| import {LoadingService} from "@app/_services/loading.service"; | |||
| import {DOCUMENT} from "@angular/common"; | |||
| import {UserJsonld} from "@app/core/api/v1"; | |||
| @Component({ | |||
| selector: 'app-root', | |||
| @@ -14,7 +14,7 @@ import {DOCUMENT} from "@angular/common"; | |||
| }) | |||
| export class AppComponent implements OnInit { | |||
| protected readonly environment = environment; | |||
| protected user?: User | null; | |||
| protected user?: UserJsonld | null; | |||
| protected loadingSub: Subscription; | |||
| protected loading: boolean = false; | |||
| protected isLeftHanded = false; | |||
| @@ -55,7 +55,7 @@ export class AppComponent implements OnInit { | |||
| // TODO: Hilfsfunktion - entfernen | |||
| copyTokenToClipboard() { | |||
| const el = document.createElement('textarea'); | |||
| el.value = this.user?.token !== undefined ? this.user.token : ""; | |||
| el.value = this.user?.token || ""; | |||
| document.body.appendChild(el); | |||
| el.select(); | |||
| document.execCommand('copy'); | |||
| @@ -33,7 +33,6 @@ model/apiUsersGetCollection200Response.ts | |||
| model/apiVesselsGetCollection200Response.ts | |||
| model/apiZonesGetCollection200Response.ts | |||
| model/authResponse.ts | |||
| model/authResponseUser.ts | |||
| model/credentials.ts | |||
| model/location.ts | |||
| model/locationJsonld.ts | |||
| @@ -9,16 +9,10 @@ | |||
| * https://openapi-generator.tech | |||
| * Do not edit the class manually. | |||
| */ | |||
| import { AuthResponseUser } from './authResponseUser'; | |||
| import { UserJsonld } from './userJsonld'; | |||
| export interface AuthResponse { | |||
| readonly token?: string; | |||
| readonly id?: number; | |||
| readonly email?: string; | |||
| readonly firstName?: string; | |||
| readonly lastName?: string; | |||
| roles?: Array<string>; | |||
| user?: AuthResponseUser; | |||
| readonly user?: UserJsonld; | |||
| } | |||
| @@ -9,9 +9,13 @@ | |||
| * https://openapi-generator.tech | |||
| * Do not edit the class manually. | |||
| */ | |||
| import { LocationJsonldContext } from './locationJsonldContext'; | |||
| export interface AuthResponseUser { | |||
| context?: LocationJsonldContext; | |||
| readonly id?: any | null; | |||
| readonly type?: any | null; | |||
| readonly dbId?: any | null; | |||
| email: any | null; | |||
| firstName: any | null; | |||
| @@ -24,6 +28,8 @@ export interface AuthResponseUser { | |||
| */ | |||
| password?: any | null; | |||
| active?: any | null; | |||
| roles?: any | null; | |||
| readonly token?: any | null; | |||
| readonly createdAt?: any | null; | |||
| } | |||
| @@ -13,7 +13,6 @@ export * from './apiUsersGetCollection200Response'; | |||
| export * from './apiVesselsGetCollection200Response'; | |||
| export * from './apiZonesGetCollection200Response'; | |||
| export * from './authResponse'; | |||
| export * from './authResponseUser'; | |||
| export * from './credentials'; | |||
| export * from './location'; | |||
| export * from './locationJsonld'; | |||
| @@ -27,6 +27,8 @@ export interface User { | |||
| */ | |||
| password?: string; | |||
| active?: boolean; | |||
| roles?: Array<string>; | |||
| readonly token?: string | null; | |||
| readonly createdAt?: string | null; | |||
| } | |||
| @@ -31,6 +31,8 @@ export interface UserJsonld { | |||
| */ | |||
| password?: string; | |||
| active?: boolean; | |||
| roles?: Array<string>; | |||
| readonly token?: string | null; | |||
| readonly createdAt?: string | null; | |||
| } | |||
| @@ -24,6 +24,7 @@ | |||
| "symfony/flex": "^2", | |||
| "symfony/form": "7.1.*", | |||
| "symfony/framework-bundle": "7.1.*", | |||
| "symfony/http-client": "7.1.*", | |||
| "symfony/property-access": "7.1.*", | |||
| "symfony/property-info": "7.1.*", | |||
| "symfony/runtime": "7.1.*", | |||
| @@ -4,7 +4,7 @@ | |||
| "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | |||
| "This file is @generated automatically" | |||
| ], | |||
| "content-hash": "85ee4ff0a9e8228f5ef5dae1cf910077", | |||
| "content-hash": "1d97e4496ebfcc30dce08cc0102bd47e", | |||
| "packages": [ | |||
| { | |||
| "name": "api-platform/doctrine-common", | |||
| @@ -4876,6 +4876,178 @@ | |||
| ], | |||
| "time": "2024-09-20T13:35:23+00:00" | |||
| }, | |||
| { | |||
| "name": "symfony/http-client", | |||
| "version": "v7.1.10", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/symfony/http-client.git", | |||
| "reference": "e221bfd51e70f12568e2f84890e6a5ffed0c35c7" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/symfony/http-client/zipball/e221bfd51e70f12568e2f84890e6a5ffed0c35c7", | |||
| "reference": "e221bfd51e70f12568e2f84890e6a5ffed0c35c7", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "php": ">=8.2", | |||
| "psr/log": "^1|^2|^3", | |||
| "symfony/deprecation-contracts": "^2.5|^3", | |||
| "symfony/http-client-contracts": "~3.4.4|^3.5.2", | |||
| "symfony/service-contracts": "^2.5|^3" | |||
| }, | |||
| "conflict": { | |||
| "php-http/discovery": "<1.15", | |||
| "symfony/http-foundation": "<6.4" | |||
| }, | |||
| "provide": { | |||
| "php-http/async-client-implementation": "*", | |||
| "php-http/client-implementation": "*", | |||
| "psr/http-client-implementation": "1.0", | |||
| "symfony/http-client-implementation": "3.0" | |||
| }, | |||
| "require-dev": { | |||
| "amphp/amp": "^2.5", | |||
| "amphp/http-client": "^4.2.1", | |||
| "amphp/http-tunnel": "^1.0", | |||
| "amphp/socket": "^1.1", | |||
| "guzzlehttp/promises": "^1.4|^2.0", | |||
| "nyholm/psr7": "^1.0", | |||
| "php-http/httplug": "^1.0|^2.0", | |||
| "psr/http-client": "^1.0", | |||
| "symfony/dependency-injection": "^6.4|^7.0", | |||
| "symfony/http-kernel": "^6.4|^7.0", | |||
| "symfony/messenger": "^6.4|^7.0", | |||
| "symfony/process": "^6.4|^7.0", | |||
| "symfony/rate-limiter": "^6.4|^7.0", | |||
| "symfony/stopwatch": "^6.4|^7.0" | |||
| }, | |||
| "type": "library", | |||
| "autoload": { | |||
| "psr-4": { | |||
| "Symfony\\Component\\HttpClient\\": "" | |||
| }, | |||
| "exclude-from-classmap": [ | |||
| "/Tests/" | |||
| ] | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "MIT" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Nicolas Grekas", | |||
| "email": "p@tchwork.com" | |||
| }, | |||
| { | |||
| "name": "Symfony Community", | |||
| "homepage": "https://symfony.com/contributors" | |||
| } | |||
| ], | |||
| "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", | |||
| "homepage": "https://symfony.com", | |||
| "keywords": [ | |||
| "http" | |||
| ], | |||
| "support": { | |||
| "source": "https://github.com/symfony/http-client/tree/v7.1.10" | |||
| }, | |||
| "funding": [ | |||
| { | |||
| "url": "https://symfony.com/sponsor", | |||
| "type": "custom" | |||
| }, | |||
| { | |||
| "url": "https://github.com/fabpot", | |||
| "type": "github" | |||
| }, | |||
| { | |||
| "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | |||
| "type": "tidelift" | |||
| } | |||
| ], | |||
| "time": "2024-12-30T18:35:03+00:00" | |||
| }, | |||
| { | |||
| "name": "symfony/http-client-contracts", | |||
| "version": "v3.5.2", | |||
| "source": { | |||
| "type": "git", | |||
| "url": "https://github.com/symfony/http-client-contracts.git", | |||
| "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" | |||
| }, | |||
| "dist": { | |||
| "type": "zip", | |||
| "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", | |||
| "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", | |||
| "shasum": "" | |||
| }, | |||
| "require": { | |||
| "php": ">=8.1" | |||
| }, | |||
| "type": "library", | |||
| "extra": { | |||
| "thanks": { | |||
| "url": "https://github.com/symfony/contracts", | |||
| "name": "symfony/contracts" | |||
| }, | |||
| "branch-alias": { | |||
| "dev-main": "3.5-dev" | |||
| } | |||
| }, | |||
| "autoload": { | |||
| "psr-4": { | |||
| "Symfony\\Contracts\\HttpClient\\": "" | |||
| }, | |||
| "exclude-from-classmap": [ | |||
| "/Test/" | |||
| ] | |||
| }, | |||
| "notification-url": "https://packagist.org/downloads/", | |||
| "license": [ | |||
| "MIT" | |||
| ], | |||
| "authors": [ | |||
| { | |||
| "name": "Nicolas Grekas", | |||
| "email": "p@tchwork.com" | |||
| }, | |||
| { | |||
| "name": "Symfony Community", | |||
| "homepage": "https://symfony.com/contributors" | |||
| } | |||
| ], | |||
| "description": "Generic abstractions related to HTTP clients", | |||
| "homepage": "https://symfony.com", | |||
| "keywords": [ | |||
| "abstractions", | |||
| "contracts", | |||
| "decoupling", | |||
| "interfaces", | |||
| "interoperability", | |||
| "standards" | |||
| ], | |||
| "support": { | |||
| "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" | |||
| }, | |||
| "funding": [ | |||
| { | |||
| "url": "https://symfony.com/sponsor", | |||
| "type": "custom" | |||
| }, | |||
| { | |||
| "url": "https://github.com/fabpot", | |||
| "type": "github" | |||
| }, | |||
| { | |||
| "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", | |||
| "type": "tidelift" | |||
| } | |||
| ], | |||
| "time": "2024-12-07T08:49:48+00:00" | |||
| }, | |||
| { | |||
| "name": "symfony/http-foundation", | |||
| "version": "v7.1.5", | |||
| @@ -8644,7 +8816,7 @@ | |||
| ], | |||
| "aliases": [], | |||
| "minimum-stability": "stable", | |||
| "stability-flags": [], | |||
| "stability-flags": {}, | |||
| "prefer-stable": true, | |||
| "prefer-lowest": false, | |||
| "platform": { | |||
| @@ -8652,6 +8824,6 @@ | |||
| "ext-ctype": "*", | |||
| "ext-iconv": "*" | |||
| }, | |||
| "platform-dev": [], | |||
| "platform-dev": {}, | |||
| "plugin-api-version": "2.6.0" | |||
| } | |||
| @@ -94,6 +94,12 @@ class UserApi | |||
| #[ApiProperty(security: 'object === null or is_granted("EDIT", object)')] | |||
| public bool $active; | |||
| #[ApiProperty(writable: false)] | |||
| public array $roles = []; | |||
| #[ApiProperty(writable: false)] | |||
| public ?string $token = null; | |||
| #[ApiProperty(writable: false)] | |||
| public ?\DateTimeImmutable $createdAt = null; | |||
| @@ -42,6 +42,7 @@ class UserEntityToApiMapper implements MapperInterface | |||
| $dto->firstName = $entity->getFirstName(); | |||
| $dto->lastName = $entity->getLastName(); | |||
| $dto->image = $entity->getImage(); | |||
| $dto->roles = $entity->getRoles(); | |||
| $dto->imageUrl = $this->fileUrlService->getFileUrl($entity->getImage()); | |||
| $dto->fullName = $entity->getFirstName() . " " . $entity->getLastName(); | |||
| $dto->createdAt = $entity->getCreatedAt(); | |||
| @@ -38,41 +38,11 @@ final class AuthDecorator implements OpenApiFactoryInterface | |||
| $schemas['AuthResponse'] = new \ArrayObject([ | |||
| 'type' => 'object', | |||
| 'properties' => [ | |||
| 'token' => [ | |||
| 'type' => 'string', | |||
| 'readOnly' => true, | |||
| ], | |||
| 'id' => [ | |||
| 'type' => 'integer', | |||
| 'readOnly' => true, | |||
| ], | |||
| 'email' => [ | |||
| 'type' => 'string', | |||
| 'readOnly' => true, | |||
| ], | |||
| 'firstName' => [ | |||
| 'type' => 'string', | |||
| 'readOnly' => true, | |||
| ], | |||
| 'lastName' => [ | |||
| 'type' => 'string', | |||
| 'readOnly' => true, | |||
| ], | |||
| 'roles' => [ | |||
| 'type' => 'array', | |||
| 'items' => [ | |||
| 'type' => 'string' | |||
| ], | |||
| 'readOnly' => true, | |||
| ], | |||
| 'user' => [ | |||
| 'type' => 'object', | |||
| 'allOf' => [ | |||
| ['$ref' => '#/components/schemas/User'] | |||
| ], | |||
| '$ref' => '#/components/schemas/User.jsonld', | |||
| 'readOnly' => true, | |||
| ], | |||
| ], | |||
| ] | |||
| ] | |||
| ]); | |||
| $pathItem = new PathItem( | |||
| @@ -2,8 +2,14 @@ | |||
| // src/Security/JwtAuthenticator.php | |||
| namespace App\Security; | |||
| use ApiPlatform\Metadata\Get; | |||
| use ApiPlatform\Metadata\IriConverterInterface; | |||
| use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface; | |||
| use ApiPlatform\State\SerializerContextBuilderInterface; | |||
| use App\ApiResource\UserApi; | |||
| use App\Entity\User; | |||
| use App\Repository\UserRepository; | |||
| use App\State\EntityToDtoStateProvider; | |||
| use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface; | |||
| use Symfony\Component\HttpFoundation\JsonResponse; | |||
| use Symfony\Component\HttpFoundation\Request; | |||
| @@ -15,14 +21,19 @@ use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator; | |||
| use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge; | |||
| use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials; | |||
| use Symfony\Component\Security\Http\Authenticator\Passport\Passport; | |||
| use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | |||
| use Symfony\Component\Serializer\SerializerInterface; | |||
| use Symfony\Contracts\HttpClient\HttpClientInterface; | |||
| use Symfonycasts\MicroMapper\MicroMapperInterface; | |||
| class JwtAuthenticator extends AbstractAuthenticator | |||
| { | |||
| public function __construct( | |||
| private JWTTokenManagerInterface $jwtManager, | |||
| private UserRepository $userRepository, | |||
| private MicroMapperInterface $microMapper | |||
| private MicroMapperInterface $microMapper, | |||
| private HttpClientInterface $httpClient, | |||
| private SerializerInterface $serializer, | |||
| private SerializerContextBuilderInterface $serializerContextBuilder, | |||
| ) {} | |||
| public function supports(Request $request): ?bool | |||
| @@ -49,21 +60,22 @@ class JwtAuthenticator extends AbstractAuthenticator | |||
| public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response | |||
| { | |||
| /** @var User $user */ | |||
| $user = $token->getUser(); | |||
| $userApi = $this->microMapper->map($user, UserApi::class); | |||
| $jwtToken = $this->jwtManager->create($user); | |||
| // NOTE: This is a necessary workaround, since it is ot possible to map this in the usual api platform style at this point | |||
| $userApiArray = []; | |||
| $userApiArray['@id'] = '/api/users/' . $user->getId(); | |||
| $userApiArray['@type'] = 'User'; | |||
| foreach (get_object_vars($userApi) as $property => $value) { | |||
| if ($property !== 'id') { | |||
| $userApiArray[$property] = $value; | |||
| } | |||
| } | |||
| $userApiArray['token'] = $this->jwtManager->create($user); | |||
| return new JsonResponse([ | |||
| 'token' => $jwtToken, | |||
| 'id' => $user->getId(), | |||
| 'email' => $user->getEmail(), | |||
| 'firstName' => $user->getFirstName(), | |||
| 'lastName' => $user->getLastName(), | |||
| 'roles' => $user->getRoles(), | |||
| 'user' => $this->microMapper->map( | |||
| $user, UserApi::class | |||
| ) | |||
| ]); | |||
| return new JsonResponse(['user' => $userApiArray]); | |||
| } | |||
| public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response | |||