瀏覽代碼

Implementation of OAuth2 (not yet with Hub)

Lukas Cerny 2 年之前
父節點
當前提交
82d7219945
共有 34 個文件被更改,包括 569 次插入181 次删除
  1. 61 0
      package-lock.json
  2. 3 1
      package.json
  3. 9 0
      src/app/app-routing.module.ts
  4. 19 5
      src/app/app.module.ts
  5. 19 5
      src/app/auth/auth.module.ts
  6. 15 12
      src/app/auth/guards/auth.guard.ts
  7. 15 18
      src/app/auth/guards/role.guard.ts
  8. 30 38
      src/app/auth/interceptors/auth.interceptor.ts
  9. 1 0
      src/app/auth/models/user.ts
  10. 75 0
      src/app/auth/services/auth-security.service.ts
  11. 0 3
      src/app/auth/states/user.state.ts
  12. 1 20
      src/app/dashboard/components/unit-list/unit-list.component.ts
  13. 21 14
      src/app/login/components/login.component.html
  14. 38 16
      src/app/login/components/login.component.ts
  15. 19 1
      src/app/login/login.module.ts
  16. 5 6
      src/app/sensor/components/sensor.component.ts
  17. 2 4
      src/app/shared/api/endpoints/base-service.ts
  18. 7 0
      src/app/shared/api/endpoints/models/resource-server-info.ts
  19. 2 3
      src/app/shared/api/endpoints/models/user-info.ts
  20. 16 3
      src/app/shared/api/endpoints/services/administration.service.ts
  21. 1 1
      src/app/shared/api/endpoints/services/analytics.service.ts
  22. 1 1
      src/app/shared/api/endpoints/services/data.service.ts
  23. 17 3
      src/app/shared/api/endpoints/services/group.service.ts
  24. 2 2
      src/app/shared/api/endpoints/services/login.service.ts
  25. 6 6
      src/app/shared/api/endpoints/services/management.service.ts
  26. 2 2
      src/app/shared/api/endpoints/services/observation.service.ts
  27. 4 4
      src/app/shared/api/endpoints/services/sensors.service.ts
  28. 30 0
      src/app/shared/api/endpoints/services/user.service.ts
  29. 10 12
      src/app/shared/nav-bar/components/nav-bar.component.ts
  30. 7 0
      src/app/user-registration/user-profile.ts
  31. 44 0
      src/app/user-registration/user-registration.component.html
  32. 32 0
      src/app/user-registration/user-registration.component.scss
  33. 54 0
      src/app/user-registration/user-registration.component.ts
  34. 1 1
      src/environments/environment.ts

+ 61 - 0
package-lock.json

@@ -24,6 +24,7 @@
         "@ng-bootstrap/ng-bootstrap": "^15.1.2",
         "@ngx-translate/core": "^15.0.0",
         "@ngx-translate/http-loader": "^8.0.0",
+        "angular-auth-oidc-client": "^16.0.1",
         "bootstrap": "^5.3.2",
         "d3": "^6.7.0",
         "dayjs": "^1.10.7",
@@ -36,6 +37,7 @@
         "geotiff": "^1.0.8",
         "hslayers-ng": "12.1.0",
         "jquery": "^3.6.0",
+        "jwt-decode": "^4.0.0",
         "lodash": "^4.17.21",
         "moment": "2.29.4",
         "moment-timezone": "0.5.35",
@@ -6835,6 +6837,26 @@
         "ajv": "^8.8.2"
       }
     },
+    "node_modules/angular-auth-oidc-client": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/angular-auth-oidc-client/-/angular-auth-oidc-client-16.0.1.tgz",
+      "integrity": "sha512-jWNNw4LgKcfskMX5ZDt33/qKp2NmlI6PL+s8ptlIixpzCdNLQv7FwMhYlyq22MMjk87WSsOCWmMlGBoMTeNOfw==",
+      "dependencies": {
+        "rfc4648": "^1.5.0",
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": ">=15.0.0",
+        "@angular/core": ">=15.0.0",
+        "@angular/router": ">=15.0.0",
+        "rxjs": "^6.5.3 || ^7.4.0"
+      }
+    },
+    "node_modules/angular-auth-oidc-client/node_modules/tslib": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+      "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+    },
     "node_modules/ansi-colors": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -12798,6 +12820,14 @@
       "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
       "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
     },
+    "node_modules/jwt-decode": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+      "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/karma": {
       "version": "6.3.17",
       "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz",
@@ -16427,6 +16457,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/rfc4648": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.3.tgz",
+      "integrity": "sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ=="
+    },
     "node_modules/rfdc": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
@@ -25304,6 +25339,22 @@
         "fast-deep-equal": "^3.1.3"
       }
     },
+    "angular-auth-oidc-client": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/angular-auth-oidc-client/-/angular-auth-oidc-client-16.0.1.tgz",
+      "integrity": "sha512-jWNNw4LgKcfskMX5ZDt33/qKp2NmlI6PL+s8ptlIixpzCdNLQv7FwMhYlyq22MMjk87WSsOCWmMlGBoMTeNOfw==",
+      "requires": {
+        "rfc4648": "^1.5.0",
+        "tslib": "^2.3.0"
+      },
+      "dependencies": {
+        "tslib": {
+          "version": "2.6.2",
+          "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+          "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+        }
+      }
+    },
     "ansi-colors": {
       "version": "4.1.3",
       "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
@@ -29842,6 +29893,11 @@
         }
       }
     },
+    "jwt-decode": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+      "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA=="
+    },
     "karma": {
       "version": "6.3.17",
       "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.17.tgz",
@@ -32599,6 +32655,11 @@
       "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
       "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
     },
+    "rfc4648": {
+      "version": "1.5.3",
+      "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.3.tgz",
+      "integrity": "sha512-MjOWxM065+WswwnmNONOT+bD1nXzY9Km6u3kzvnx8F8/HXGZdz3T6e6vZJ8Q/RIMUSp/nxqjH3GwvJDy8ijeQQ=="
+    },
     "rfdc": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",

+ 3 - 1
package.json

@@ -28,6 +28,7 @@
     "@ng-bootstrap/ng-bootstrap": "^15.1.2",
     "@ngx-translate/core": "^15.0.0",
     "@ngx-translate/http-loader": "^8.0.0",
+    "angular-auth-oidc-client": "^16.0.1",
     "bootstrap": "^5.3.2",
     "d3": "^6.7.0",
     "dayjs": "^1.10.7",
@@ -40,6 +41,7 @@
     "geotiff": "^1.0.8",
     "hslayers-ng": "12.1.0",
     "jquery": "^3.6.0",
+    "jwt-decode": "^4.0.0",
     "lodash": "^4.17.21",
     "moment": "2.29.4",
     "moment-timezone": "0.5.35",
@@ -89,4 +91,4 @@
     "tslint": "~6.1.0",
     "typescript": "5.1.6"
   }
-}
+}

+ 9 - 0
src/app/app-routing.module.ts

@@ -6,6 +6,7 @@ import {AuthGuard} from './auth/guards/auth.guard';
 import {RoleGuard} from './auth/guards/role.guard';
 import {SensorComponent} from './sensor/components/sensor.component';
 import { UnitComponent } from './unit/components/unit.component';
+import {UserRegistrationComponent} from './user-registration/user-registration.component';
 
 const routes: Routes = [
   {
@@ -37,6 +38,14 @@ const routes: Routes = [
     path: 'login',
     component: LoginComponent,
     pathMatch: 'full',
+  }, {
+    canActivate: [AuthGuard, RoleGuard],
+    path: 'admin/userRegistration',
+    component: UserRegistrationComponent,
+    pathMatch: 'full',
+    data: {
+      expectedRole: ['0']
+    }
   }
 ];
 

+ 19 - 5
src/app/app.module.ts

@@ -1,4 +1,4 @@
-import {NgModule} from '@angular/core';
+import {APP_INITIALIZER, NgModule} from '@angular/core';
 import { PathLocationStrategy, LocationStrategy } from '@angular/common';
 import { BrowserModule } from '@angular/platform-browser';
 
@@ -6,7 +6,7 @@ import {AppRoutingModule} from './app-routing.module';
 import {AppComponent} from './app.component';
 import {NavBarModule} from './shared/nav-bar/nav-bar.module';
 import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
-import {HttpClientModule} from '@angular/common/http';
+import {HttpClient, HttpClientModule} from '@angular/common/http';
 import {AuthModule} from './auth/auth.module';
 import {LoginModule} from './login/login.module';
 import {DashboardModule} from './dashboard/dashboard.module';
@@ -15,6 +15,11 @@ import {FontAwesomeModule} from '@fortawesome/angular-fontawesome';
 import {UnitModule} from './unit/unit.module';
 import {ConfirmationService, MessageService} from 'primeng/api';
 import { ToastModule } from 'primeng/toast';
+import {tap} from 'rxjs/operators';
+import {ResourceServerInfo} from './shared/api/endpoints/models/resource-server-info';
+import {Observable} from 'rxjs';
+import {environment} from '../environments/environment';
+import {AuthSecurityService} from './auth/services/auth-security.service';
 
 @NgModule({
   declarations: [
@@ -37,9 +42,18 @@ import { ToastModule } from 'primeng/toast';
   providers: [
     ConfirmationService,
     MessageService,
-    { provide: LocationStrategy, useClass: PathLocationStrategy }
+    { provide: LocationStrategy, useClass: PathLocationStrategy },
+    { provide: APP_INITIALIZER,useFactory: initializeAppFactory, deps: [HttpClient], multi: true}
   ],
   bootstrap: [AppComponent]
 })
-export class AppModule {
-}
+export class AppModule {}
+
+function initializeAppFactory(http: HttpClient): () => Observable<any> {
+  return () => http.get(environment.sensLogBaseUrl+'/api/info')
+      .pipe(
+          tap((info: ResourceServerInfo): void => {
+            AuthSecurityService.securityType = info.authType;
+          })
+      );
+}

+ 19 - 5
src/app/auth/auth.module.ts

@@ -4,15 +4,29 @@ import {RouterModule} from '@angular/router';
 import {HTTP_INTERCEPTORS} from '@angular/common/http';
 import {AuthInterceptor} from './interceptors/auth.interceptor';
 import {ApiModule} from '../shared/api/endpoints/api.module';
+import { UserRegistrationComponent } from '../user-registration/user-registration.component';
+import {MatInputModule} from '@angular/material/input';
+import {MatSelectModule} from '@angular/material/select';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import {MatButtonModule} from '@angular/material/button';
+import {ButtonModule} from 'primeng/button';
 
 
 @NgModule({
-  declarations: [],
-  imports: [
-    CommonModule,
-    RouterModule,
-    ApiModule
+  declarations: [
+    UserRegistrationComponent
   ],
+    imports: [
+        CommonModule,
+        RouterModule,
+        ApiModule,
+        MatInputModule,
+        MatSelectModule,
+        FormsModule,
+        MatButtonModule,
+        ButtonModule,
+        ReactiveFormsModule
+    ],
   providers: [
     {provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true}
   ]

+ 15 - 12
src/app/auth/guards/auth.guard.ts

@@ -1,7 +1,8 @@
 import { Injectable } from '@angular/core';
 import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
-import { Observable } from 'rxjs';
-import { AuthService } from '../services/auth.service';
+import {Observable} from 'rxjs';
+import {map} from 'rxjs/operators';
+import {AuthSecurityService} from '../services/auth-security.service';
 
 @Injectable({
   providedIn: 'root'
@@ -9,17 +10,19 @@ import { AuthService } from '../services/auth.service';
 export class AuthGuard  {
 
   constructor(
-    private authService: AuthService,
-    private router: Router
+      private authSecurityService: AuthSecurityService,
+      private router: Router
   ) {}
 
-  canActivate(
-    next: ActivatedRouteSnapshot,
-    state: RouterStateSnapshot
-  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
-    if (!this.authService.getUser()) {
-      this.router.navigate(['']);
-    }
-    return !!this.authService.getUser();
+  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
+    return this.authSecurityService.isAuthenticated$.pipe(
+        map(({ isAuthenticated }) => {
+            if (isAuthenticated) {
+                return true;
+            }
+            localStorage.setItem('expectedRedirect',  this.router.getCurrentNavigation().extractedUrl.toString());
+            this.router.navigate(['/login']);
+            return false;
+        }));
   }
 }

+ 15 - 18
src/app/auth/guards/role.guard.ts

@@ -1,8 +1,9 @@
 import { Injectable } from '@angular/core';
 import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
 import { Observable } from 'rxjs';
-import { AuthService } from '../services/auth.service';
-import { LoginService } from '../../shared/api/endpoints/services';
+import {User} from '../models/user';
+import {AuthSecurityService} from '../services/auth-security.service';
+import {map} from 'rxjs/operators';
 
 @Injectable({
   providedIn: 'root'
@@ -10,24 +11,20 @@ import { LoginService } from '../../shared/api/endpoints/services';
 export class RoleGuard  {
 
   constructor(
-    private authService: AuthService,
-    private loginService: LoginService,
+    private authSecurityService: AuthSecurityService,
     private router: Router,
   ) {}
 
-  canActivate(
-    routeSnapshot: ActivatedRouteSnapshot,
-    state: RouterStateSnapshot
-  ): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
+  canActivate(routeSnapshot: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
     const expectedRole: string[] = routeSnapshot.data.expectedRole;
-    let user = this.authService.getUser();
-    let userInfo = user.userInfo;
-    if (expectedRole.includes(userInfo?.rightsId.toString()) || expectedRole.includes(user.userCookie.rightsID.toString())) {
-      console.log('You have enough rights!');
-      return true;
-    }
-    console.log('You are not allowed to be here!');
-    this.router.navigate(['']);
-    return false;
+    return this.authSecurityService.profile$.pipe(
+        map((user: User): boolean => {
+          if (expectedRole.includes(user.userInfo.rightsId.toString())) {
+            return true;
+          }
+          this.router.navigate(['']);
+          return false;
+        })
+    );
   }
-}
+}

+ 30 - 38
src/app/auth/interceptors/auth.interceptor.ts

@@ -7,17 +7,18 @@ import {
 } from '@angular/common/http';
 import {Observable, throwError, of} from 'rxjs';
 
-import {catchError} from 'rxjs/operators';
+import {catchError, map, switchMap, tap} from 'rxjs/operators';
 import {AuthService} from '../services/auth.service';
 import {ToastService} from '../../shared/services/toast.service';
-import {environment} from '../../../environments/environment';
+import {OidcSecurityService} from 'angular-auth-oidc-client';
 
 @Injectable()
 export class AuthInterceptor implements HttpInterceptor {
 
   constructor(
     private authService: AuthService,
-    private toastService: ToastService
+    private toastService: ToastService,
+    private oidcSecurityService: OidcSecurityService
   ) {
   }
 
@@ -27,41 +28,32 @@ export class AuthInterceptor implements HttpInterceptor {
    * @param next next
    */
   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
-    // check if data in cookies
-    //if (!this.cookieService.get(GlobalVariable.SESSION_ID) && !request.url.includes('ControllerServlet')) {
-    //  console.log('No sessionid!');
-    //  this.cookieService.deleteAll();
-    //  this.authService.doLogout();
-    //  return of(null);
-    //}
-
-    console.log('Sending request!', request.url);
-    if (request.url.includes(environment.sensLogBaseUrl)) {
-      request = request.clone({
-        withCredentials: true
-      });
-    }
-
-    return next.handle(request)
-      .pipe(
-        catchError(err => {
-          if ((err instanceof HttpErrorResponse && err.status === 502 || err.status === 401 ||
-            err.status === 0)) {
-            console.log(err);
-            this.toastService.showError(err.error.message);
-            return this.handleError(request, next);
-          } else if ((err instanceof HttpErrorResponse && err.status === 500 || err.status === 504 || err.status === 500)) {
-            if (err.error) {
-              this.toastService.showError(err.error);
-            } else {
-              this.toastService.showError('500 - Error!');
-            }
-            this.handle500Error(err);
-          } else {
-            return throwError(err);
-          }
-        })
-      );
+    return this.oidcSecurityService.getAccessToken().pipe(
+        map((token: string) => request.clone({
+              setHeaders: {
+                Authorization: `Bearer ${token}`
+              }
+        })),
+        switchMap(authRequest => next.handle(authRequest)
+            .pipe(
+                catchError(err => {
+                  if ((err instanceof HttpErrorResponse && err.status === 502 || err.status === 401 || err.status === 0)) {
+                      console.log(err);
+                      this.toastService.showError(err.error.message);
+                      return this.handleError(request, next);
+                  } else if ((err instanceof HttpErrorResponse && err.status === 500 || err.status === 504 || err.status === 500)) {
+                      if (err.error) {
+                          this.toastService.showError(err.error);
+                      } else {
+                          this.toastService.showError('500 - Error!');
+                      }
+                      this.handle500Error(err);
+                  } else {
+                      return throwError(err);
+                  }
+                })
+            ))
+    );
   }
 
   handleError(request: HttpRequest<any>, next: HttpHandler) {

+ 1 - 0
src/app/auth/models/user.ts

@@ -8,6 +8,7 @@ import {UserCookie} from '../../shared/api/endpoints/models/user-cookie';
  */
 export interface User {
   userInfo?: UserInfo;
+  userProfile?: any,
   isLoggedIn?: boolean;
   userCookie?: UserCookie
 }

+ 75 - 0
src/app/auth/services/auth-security.service.ts

@@ -0,0 +1,75 @@
+import { Injectable } from '@angular/core';
+import {Observable, of, switchMap} from 'rxjs';
+import {AuthenticatedResult} from 'angular-auth-oidc-client/lib/auth-state/auth-result';
+import {OidcSecurityService, UserDataResult} from 'angular-auth-oidc-client';
+import {AuthService} from './auth.service';
+import {User} from '../models/user';
+import {map} from 'rxjs/operators';
+import {UserInfo} from '../../shared/api/endpoints/models/user-info';
+import {UserService} from '../../shared/api/endpoints/services/user.service';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AuthSecurityService {
+
+  public static securityType: string;
+
+  constructor(
+      private oidcSecurityService: OidcSecurityService,
+      private authService: AuthService,
+      private userService: UserService
+  ) {}
+
+  public logout(): void {
+    if (this.isOAuthLoginEnable) {
+      this.oidcSecurityService.logoff()
+          .subscribe((result) => console.log(result));
+    }
+    if (this.isFormLoginEnable) {
+      this.authService.doLogout();
+    }
+  }
+
+  get profile$(): Observable<User> {
+    if (this.isOAuthLoginEnable) {
+      return this.oidcSecurityService.userData$
+          .pipe(
+              map((userData: UserDataResult) => userData.userData),
+              switchMap((userOAuth: any): Observable<User> => {
+                if (userOAuth) {
+                  return this.userService.userProfile$.pipe(
+                      map((userResource: UserInfo) => {
+                        return {
+                          userInfo: userResource,
+                          userProfile: userOAuth,
+                          isLoggedIn: true
+                        } as User;
+                      })
+                  );
+                } else {
+                  return of({isLoggedIn: false} as User)
+                }
+              })
+          );
+    }
+  }
+
+  get isAuthenticated$(): Observable<AuthenticatedResult> {
+    if (this.isOAuthLoginEnable) {
+      return this.oidcSecurityService.isAuthenticated$;
+    }
+    if (this.isFormLoginEnable) {
+      return of({isAuthenticated: false, allConfigsAuthenticated: []});
+    }
+    return of({isAuthenticated: false, allConfigsAuthenticated: []});
+  }
+
+  get isOAuthLoginEnable(): boolean {
+    return AuthSecurityService.securityType === 'OAUTH';
+  }
+
+  get isFormLoginEnable(): boolean {
+    return AuthSecurityService.securityType === 'LOCAL_AUTH';
+  }
+}

+ 0 - 3
src/app/auth/states/user.state.ts

@@ -3,7 +3,6 @@ import {BehaviorSubject, Observable} from 'rxjs';
 import {User} from '../models/user';
 import {LoginService} from '../../shared/api/endpoints/services/login.service';
 import {ToastService} from '../../shared/services/toast.service';
-//import {AuthService} from '../services/auth.service';
 import {CookieService} from '../services/cookie.service';
 
 @Injectable({
@@ -14,7 +13,6 @@ export class UserState {
 
   constructor(
     private loginService: LoginService,
-    //private authService: AuthService,
     private cookieService: CookieService,
     private toastService: ToastService
   ) {}
@@ -44,7 +42,6 @@ export class UserState {
         console.log('Authentication failed!');
         this.setUser(null);
         this.cookieService.deleteAll();
-      //this.authService.doLogout();
       });
    }
     return this.userState$.asObservable();

+ 1 - 20
src/app/dashboard/components/unit-list/unit-list.component.ts

@@ -5,10 +5,9 @@ import { GeneralInfo } from '../../../shared/api/endpoints/models/general-info';
 import { Lastpos } from '../../../shared/api/endpoints/models/lastpos';
 import { Sensor } from '../../../shared/api/endpoints/models/sensor';
 import { Unit } from '../../../shared/api/endpoints/models/unit';
-import { DataService } from '../../../shared/api/endpoints/services/data.service';
 import { Phenomenon } from '../../../shared/api/endpoints/models/phenomenon';
 import { SensorsService } from '../../../shared/api/endpoints/services/sensors.service';
-import { ConfirmationService, MenuItem, MessageService } from 'primeng/api';
+import { ConfirmationService, MenuItem } from 'primeng/api';
 import { ManagementService } from '../../../shared/api/endpoints/services/management.service';
 import { ToastService } from '../../../shared/services/toast.service';
 import { map } from 'rxjs/operators';
@@ -31,7 +30,6 @@ export class UnitListComponent implements OnInit, OnDestroy {
   phenomenons: Phenomenon[];
   sensorTypes: SensorType[];
 
-  inProgress: Boolean = true;
   items: MenuItem[] = [];
   position: 'bottom';
   groups: Group[];
@@ -42,10 +40,8 @@ export class UnitListComponent implements OnInit, OnDestroy {
   subscription: Subscription[] = [];
 
   constructor(
-    private dataService: DataService,
     private sensorService: SensorsService,
     private confirmationService: ConfirmationService,
-    private messageService: MessageService,
     private managementService: ManagementService,
     private toastService: ToastService,
     private authService: AuthService
@@ -70,11 +66,6 @@ export class UnitListComponent implements OnInit, OnDestroy {
     this.sensorService.getPhenomenons().subscribe(
       response => this.phenomenons = response
     );
-  //  this.sensorService.getSensorTypes().subscribe(
-  //    response => this.sensorTypes = response
-  //  );
-  //  this.setUser();
-  //  this.getUnits();
   }
 
   /**
@@ -89,16 +80,6 @@ export class UnitListComponent implements OnInit, OnDestroy {
   }
 
   /**
-   * Get all units and theirs sensors from backend
-   */
-  //getUnits() {
-  //  this.dataService.getData().subscribe(data => {
-  //    this.units = data;
-  //    this.units.forEach(unit => unit.sensors.sort((a, b) => a.sensorId - b.sensorId));
-  //  }, err => this.toastService.showError(err.error.message));
-  //}
-
-  /**
    * Show edit unit
    * @param $event click event
    * @param unit edited unit

+ 21 - 14
src/app/login/components/login.component.html

@@ -9,26 +9,33 @@
               <img src="/assets/images/senslog-logo.svg" alt="Logo SensLog">
             </div>
           </div>
-          <div class="login-heading">Sign in</div>
         </div>
         <div class="card-body">
-          <form [formGroup]="loginForm" (ngSubmit)="doLogin()">
-            <div class="input-group form-group">
-              <div class="input-group-prepend">
-                <span class="input-group-text"><i class="fas fa-user"></i></span>
+          <ng-container *ngIf="this.isFormLogin">
+            <form [formGroup]="loginForm" (ngSubmit)="doLogin()">
+              <div class="input-group form-group">
+                <div class="input-group-prepend">
+                  <span class="input-group-text"><i class="fas fa-user"></i></span>
+                </div>
+                <input type="text" formControlName="username" class="form-control" id="username" placeholder="Username"/>
               </div>
-              <input type="text" formControlName="username" class="form-control" id="username" placeholder="Username"/>
-            </div>
-            <div class="input-group form-group">
-              <div class="input-group-prepend">
-                <span class="input-group-text"><i class="fas fa-key"></i></span>
+              <div class="input-group form-group">
+                <div class="input-group-prepend">
+                  <span class="input-group-text"><i class="fas fa-key"></i></span>
+                </div>
+                <input type="password" formControlName="password" class="form-control" id="password" placeholder="Password"/>
               </div>
-              <input type="password" formControlName="password" class="form-control" id="password" placeholder="Password"/>
-            </div>
+              <div class="form-group text-center mt-4">
+                <button pButton type="submit" label="Login" class="p-button-primary dark" id="loginButton"><i class="fas fa-sign-in-alt"></i></button>
+              </div>
+            </form>
+          </ng-container>
+
+          <ng-container *ngIf="this.isOAuthLogin">
             <div class="form-group text-center mt-4">
-              <button pButton ttype="submit" label="Login" class="p-button-primary dark" id="loginButton"><i class="fas fa-sign-in-alt"></i></button>
+              <button (click)="doOAuthLogin()" class="p-button-primary dark" id="hub4everButton"><i class="fas">via Hub4Everybody</i></button>
             </div>
-          </form>
+          </ng-container>
         </div>
       </div>
     </div>

+ 38 - 16
src/app/login/components/login.component.ts

@@ -4,6 +4,8 @@ import {switchMap} from 'rxjs/operators';
 import {of, Subscription} from 'rxjs';
 import {AuthService} from '../../auth/services/auth.service';
 import {Router} from '@angular/router';
+import {LoginResponse, OidcSecurityService} from 'angular-auth-oidc-client';
+import {AuthSecurityService} from '../../auth/services/auth-security.service';
 
 @Component({
   selector: 'app-login',
@@ -15,9 +17,14 @@ export class LoginComponent implements OnInit, OnDestroy {
   loginForm: UntypedFormGroup;
   subscription: Subscription[] = [];
 
+  isOAuthLogin: boolean;
+  isFormLogin: boolean;
+
   constructor(
     private formBuilder: UntypedFormBuilder,
     private authService: AuthService,
+    private oidcSecurityService: OidcSecurityService,
+    private authSecurityService: AuthSecurityService,
     private router: Router
   ) {
   }
@@ -30,16 +37,37 @@ export class LoginComponent implements OnInit, OnDestroy {
   }
 
   ngOnInit(): void {
-    this.loginForm = this.formBuilder.group({
-      username: ['', [Validators.required]],
-      password: ['', Validators.required]
-    });
-    this.userRedirect();
+    this.isOAuthLogin = this.authSecurityService.isOAuthLoginEnable;
+    this.isFormLogin = this.authSecurityService.isFormLoginEnable;
+    console.log(`Auth: ${this.isOAuthLogin} | Form: ${this.isFormLogin}`);
+
+    if (this.isFormLogin) {
+      this.loginForm = this.formBuilder.group({
+        username: ['', [Validators.required]],
+        password: ['', Validators.required]
+      });
+      if (this.authService.getUser()){
+        this.router.navigate(['/dashboard']);
+      }
+    }
+    if (this.isOAuthLogin) {
+      this.oidcSecurityService.checkAuth().subscribe((loginResponse: LoginResponse): void => {
+        const {isAuthenticated, userData, accessToken, idToken} = loginResponse;
+        console.log(userData);
+        console.log(idToken);
+        if (isAuthenticated) {
+          let expectedRedirect = localStorage.getItem('expectedRedirect');
+          if (expectedRedirect === null) {
+            expectedRedirect = '/dashboard';
+          } else {
+            localStorage.removeItem('expectedRedirect');
+          }
+          this.router.navigateByUrl(expectedRedirect);
+        }
+      });
+    }
   }
 
-  /**
-   * Process login
-   */
   doLogin(): void {
     if (this.loginForm.valid) {
       const username = this.loginForm.controls.username.value;
@@ -64,13 +92,7 @@ export class LoginComponent implements OnInit, OnDestroy {
     }
   }
 
-  /**
-   * If user already logged with valid sessionId redirect to dashboard
-   */
-  userRedirect(): void {
-    console.log('Login redirect');
-    if (this.authService.getUser()){
-      this.router.navigate(['/dashboard']);
-    }
+  doOAuthLogin(): void {
+    this.oidcSecurityService.authorize();
   }
 }

+ 19 - 1
src/app/login/login.module.ts

@@ -5,6 +5,8 @@ import {RouterModule} from '@angular/router';
 import {DialogModule} from 'primeng/dialog';
 import {ButtonModule} from 'primeng/button';
 import {ReactiveFormsModule} from '@angular/forms';
+import {AuthModule, LogLevel} from 'angular-auth-oidc-client';
+import {environment} from '../../environments/environment';
 
 
 
@@ -16,7 +18,23 @@ import {ReactiveFormsModule} from '@angular/forms';
     RouterModule,
     DialogModule,
     ButtonModule,
-    ReactiveFormsModule
+    ReactiveFormsModule,
+    AuthModule.forRoot({
+      config: {
+        authority: 'https://dev-0uqvxrrzblyiryr2.us.auth0.com',
+        redirectUrl: window.location.origin,
+        postLogoutRedirectUri: window.location.origin,
+        clientId: 'idcSfV47mK3MSz9FGUTUGZ3AfGiS3Qf1',
+        scope: 'openid profile email offline_access senslog:all',
+        responseType: 'code',
+        silentRenew: false,
+        useRefreshToken: false,
+        logLevel: LogLevel.Debug,
+        customParamsAuthRequest: {
+          audience: environment.sensLogBaseUrl,
+        },
+      },
+    }),
   ]
 })
 export class LoginModule { }

+ 5 - 6
src/app/sensor/components/sensor.component.ts

@@ -22,7 +22,7 @@ export class SensorComponent implements OnInit, OnDestroy {
   data = [];
   time = [];
   from: Date = moment().hour(0).minutes(0).subtract(7, 'days').toDate();
-  to: Date = moment().toDate(); 
+  to: Date = moment().toDate();
   sensor: Sensor;
   dateChanged = false;
   unitDescription: string;
@@ -31,7 +31,7 @@ export class SensorComponent implements OnInit, OnDestroy {
   showIntervalError: boolean = false;
 
   constructor(
-    private activatedRoute: ActivatedRoute,    
+    private activatedRoute: ActivatedRoute,
     private observationService: ObservationService,
     private toastService: ToastService,
     private sensorsService: SensorsService,
@@ -56,7 +56,7 @@ export class SensorComponent implements OnInit, OnDestroy {
       }
     });
     this.sensorId = parseInt(this.activatedRoute.snapshot.paramMap.get('sensorId'), 10);
-    this.unitId = parseInt(this.activatedRoute.snapshot.paramMap.get('unitId'), 10);   
+    this.unitId = parseInt(this.activatedRoute.snapshot.paramMap.get('unitId'), 10);
   }
 
   /**
@@ -81,16 +81,15 @@ export class SensorComponent implements OnInit, OnDestroy {
       this.dateChanged = true;
       this.showIntervalError = false;
     }
-    
   }
 
   /**
    * Gets data based on selected time range
    */
-  showGraph() {   
+  showGraph() {
     const range: Date[] = [this.from, this.to];
     this.getObservations(range);
-  }  
+  }
 
   /**
    * Get data from observation endpoint

+ 2 - 4
src/app/shared/api/endpoints/base-service.ts

@@ -1,7 +1,5 @@
-/* tslint:disable */
-/* eslint-disable */
 import { Injectable } from '@angular/core';
-import { HttpClient } from '@angular/common/http';
+import {HttpClient, HttpContextToken} from '@angular/common/http';
 import { ApiConfiguration } from './api-configuration';
 import { environment } from '../../../../environments/environment';
 
@@ -20,7 +18,7 @@ export class BaseService {
 
   /**
    * Returns the root url for all operations in this service. If not set directly in this
-   * service, will fallback to `ApiConfiguration.rootUrl`.
+   * service, will fall back to `ApiConfiguration.rootUrl`.
    */
   get rootUrl(): string {
     return this._rootUrl || this.config.rootUrl;

+ 7 - 0
src/app/shared/api/endpoints/models/resource-server-info.ts

@@ -0,0 +1,7 @@
+
+export interface ResourceServerInfo {
+    uptime: string,
+    appVersion: string,
+    buildVersion: string,
+    authType: string
+}

+ 2 - 3
src/app/shared/api/endpoints/models/user-info.ts

@@ -1,5 +1,4 @@
-/* tslint:disable */
-/* eslint-disable */
+
 export interface UserInfo {
   groupId?: number;
   rightsId?: number;
@@ -7,4 +6,4 @@ export interface UserInfo {
   userName?: string;
   userPass?: string;
   userRealName?: string;
-}
+}

+ 16 - 3
src/app/shared/api/endpoints/services/administration.service.ts

@@ -1,7 +1,7 @@
 /* tslint:disable */
 /* eslint-disable */
 import { Injectable } from '@angular/core';
-import { HttpClient, HttpResponse } from '@angular/common/http';
+import {HttpClient, HttpRequest, HttpResponse} from '@angular/common/http';
 import { BaseService } from '../base-service';
 import { ApiConfiguration } from '../api-configuration';
 import { StrictHttpResponse } from '../strict-http-response';
@@ -30,7 +30,20 @@ export class AdministrationService extends BaseService {
   /**
    * Path part for operation createUser
    */
-  static readonly CreateUserPath = '/rest/user';
+  static readonly CreateUserPath = '/api/manage/user';
+
+  createUserByEncodedProfile(body: object): Observable<void> {
+    const reqBuild: RequestBuilder = new RequestBuilder(this.rootUrl, "/api/manage/user/new", "POST");
+    reqBuild.body(body, 'application/json')
+    const req: HttpRequest<any> = reqBuild.build();
+
+    return this.http.request(req).pipe(
+        filter((res: any): boolean => res instanceof HttpResponse),
+        map((httpRes: HttpResponse<any>) => {
+            return (httpRes.clone({body: undefined}) as StrictHttpResponse<void>).body as void;
+        })
+    );
+  }
 
   /**
    * Create User.
@@ -84,7 +97,7 @@ export class AdministrationService extends BaseService {
   /**
    * Path part for operation getRights
    */
-  static readonly GetRightsPath = '/rest/user/rights';
+  static readonly GetRightsPath = '/api/manage/user/rights';
 
   /**
    * Get rights.

+ 1 - 1
src/app/shared/api/endpoints/services/analytics.service.ts

@@ -29,7 +29,7 @@ export class AnalyticsService extends BaseService {
   /**
    * Path part for operation getAnalytics
    */
-  static readonly GetAnalyticsPath = '/analytics/analytics';
+  static readonly GetAnalyticsPath = '/api/analytics/analytics';
 
   /**
    * Get analytics.

+ 1 - 1
src/app/shared/api/endpoints/services/data.service.ts

@@ -33,7 +33,7 @@ export class DataService extends BaseService {
   /**
    * Path part for operation getData
    */
-  static readonly GetDataPath = '/DataService?Operation=GetUnits';
+  static readonly GetDataPath = '/api/data/DataService?Operation=GetUnits';
 
   /**
    * Get data.

+ 17 - 3
src/app/shared/api/endpoints/services/group.service.ts

@@ -1,7 +1,7 @@
 /* tslint:disable */
 /* eslint-disable */
 import { Injectable } from '@angular/core';
-import { HttpClient, HttpResponse } from '@angular/common/http';
+import {HttpClient, HttpRequest, HttpResponse} from '@angular/common/http';
 import { BaseService } from '../base-service';
 import { ApiConfiguration } from '../api-configuration';
 import { StrictHttpResponse } from '../strict-http-response';
@@ -29,7 +29,7 @@ export class GroupService extends BaseService {
   /**
    * Path part for operation getGroups
    */
-  static readonly GetGroupsPath = '/GroupService';
+  static readonly GetGroupsPath = '/api/manage/GroupService';
 
   /**
    * Get groups.
@@ -72,7 +72,7 @@ export class GroupService extends BaseService {
    *
    *
    *
-   * This method provides access to only to the response body.
+   * This method provides access only to the response body.
    * To access the full response (for headers, for example), `getGroups$Response()` instead.
    *
    * This method doesn't expect any request body.
@@ -91,4 +91,18 @@ export class GroupService extends BaseService {
     );
   }
 
+  public getAllGroups(): Observable<Array<Group>> {
+    const req: HttpRequest<any> =  new RequestBuilder(this.rootUrl, '/api/rest/group/all', 'GET').build({
+      responseType: 'json',
+      accept: 'application/json'
+    });
+
+    return this.http.request(req).pipe(
+        filter((res: any): boolean => res instanceof HttpResponse),
+        map((httpRes: HttpResponse<any>) => {
+          return httpRes.body as Array<Group>;
+        })
+    );
+  }
+
 }

+ 2 - 2
src/app/shared/api/endpoints/services/login.service.ts

@@ -30,7 +30,7 @@ export class LoginService extends BaseService {
   /**
    * Path part for operation login
    */
-  static readonly LoginPath = '/ControllerServlet';
+  static readonly LoginPath = '/api/ControllerServlet';
 
   /**
    * Authorize login.
@@ -103,7 +103,7 @@ export class LoginService extends BaseService {
   /**
    * Path part for operation getUserInfo
    */
-  static readonly GetUserInfoPath = '/rest/user';
+  static readonly GetUserInfoPath = '/api/manage/user';
 
   /**
    * User information.

+ 6 - 6
src/app/shared/api/endpoints/services/management.service.ts

@@ -30,7 +30,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation insertUnit
    */
-  static readonly InsertUnitPath = '/ManagementService?Operation=InsertUnit';
+  static readonly InsertUnitPath = '/api/manage/ManagementService?Operation=InsertUnit';
 
   /**
    * Insert Unit.
@@ -84,7 +84,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation insertSensor
    */
-  static readonly InsertSensorPath = '/ManagementService?Operation=InsertSensor';
+  static readonly InsertSensorPath = '/api/manage/ManagementService?Operation=InsertSensor';
 
   /**
    * Insert Sensor.
@@ -138,7 +138,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation updateUnit
    */
-  static readonly UpdateUnitPath = '/ManagementService?Operation=UpdateUnit';
+  static readonly UpdateUnitPath = '/api/manage/ManagementService?Operation=UpdateUnit';
 
   /**
    * Update Unit.
@@ -192,7 +192,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation deleteUnit
    */
-  static readonly DeleteUnitPath = '/ManagementService?Operation=DeleteUnit';
+  static readonly DeleteUnitPath = '/api/manage/ManagementService?Operation=DeleteUnit';
 
   /**
    * Delete Unit.
@@ -246,7 +246,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation deleteSensor
    */
-  static readonly DeleteSensorPath = '/ManagementService?Operation=DeleteSensor';
+  static readonly DeleteSensorPath = '/api/manage/ManagementService?Operation=DeleteSensor';
 
   /**
    * Delete Sensor.
@@ -300,7 +300,7 @@ export class ManagementService extends BaseService {
   /**
    * Path part for operation updateSensor
    */
-  static readonly UpdateSensorPath = '/ManagementService?Operation=UpdateSensor';
+  static readonly UpdateSensorPath = '/api/manage/ManagementService?Operation=UpdateSensor';
 
   /**
    * Update Sensor.

+ 2 - 2
src/app/shared/api/endpoints/services/observation.service.ts

@@ -31,8 +31,8 @@ export class ObservationService extends BaseService {
   /**
    * Path part for operation getObservation
    */
-  static readonly GetObservationPath = '/SensorService?Operation=GetObservations';
-  static readonly RestObservationsPath = '/rest/observation/';
+  static readonly GetObservationPath = '/api/SensorService?Operation=GetObservations';
+  static readonly RestObservationsPath = '/api/data/observation/';
 
   /**
    * Get observation.

+ 4 - 4
src/app/shared/api/endpoints/services/sensors.service.ts

@@ -31,7 +31,7 @@ export class SensorsService extends BaseService {
   /**
    * Path part for operation getUnitSensors
    */
-  static readonly GetUnitSensorsPath = '/SensorService?Operation=GetSensors';
+  static readonly GetUnitSensorsPath = '/api/data/SensorService?Operation=GetSensors';
 
   /**
    * Get unit sensors.
@@ -85,7 +85,7 @@ export class SensorsService extends BaseService {
   /**
    * Path part for operation getPhenomenons
    */
-  static readonly GetPhenomenonsPath = '/SensorService?Operation=GetAllPhenomenons';
+  static readonly GetPhenomenonsPath = '/api/data/SensorService?Operation=GetAllPhenomenons';
 
   /**
    * Get phenomenons.
@@ -136,7 +136,7 @@ export class SensorsService extends BaseService {
   /**
    * Path part for operation getSensorTypes
    */
-  static readonly GetSensorTypesPath = '/SensorService?Operation=GetAllSensorTypes';
+  static readonly GetSensorTypesPath = '/api/data/SensorService?Operation=GetAllSensorTypes';
 
   /**
    * Get sensor types.
@@ -187,7 +187,7 @@ export class SensorsService extends BaseService {
   /**
    * Path part for operation insertPosition
    */
-  static readonly InsertPositionPath = '/FeederServlet?Operation=InsertPosition';
+  static readonly InsertPositionPath = '/api/data/FeederServlet?Operation=InsertPosition';
 
   /**
    * Insert position to unit.

+ 30 - 0
src/app/shared/api/endpoints/services/user.service.ts

@@ -0,0 +1,30 @@
+import { Injectable } from '@angular/core';
+import {Observable} from 'rxjs';
+import {RequestBuilder} from '../request-builder';
+import {filter, map} from 'rxjs/operators';
+import {HttpClient, HttpRequest, HttpResponse} from '@angular/common/http';
+import {BaseService} from '../base-service';
+import {ApiConfiguration} from '../api-configuration';
+import {UserInfo} from '../models/user-info';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class UserService extends BaseService {
+
+  constructor(
+      protected config: ApiConfiguration,
+      protected http: HttpClient
+  ) {
+    super(config, http);
+  }
+
+  get userProfile$(): Observable<UserInfo> {
+    const httpReq: HttpRequest<any> = new RequestBuilder(this.rootUrl, '/api/manage/user', 'GET')
+        .build({responseType: 'json', accept: 'application/json'});
+    return this.http.request(httpReq).pipe(
+        filter((r: any): boolean => r instanceof HttpResponse),
+        map((res: HttpResponse<any>) => res.body as UserInfo)
+    );
+  }
+}

+ 10 - 12
src/app/shared/nav-bar/components/nav-bar.component.ts

@@ -1,5 +1,4 @@
 import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
-import {AuthService} from '../../../auth/services/auth.service';
 import {User} from '../../../auth/models/user';
 import {Subscription} from 'rxjs';
 import {Right} from '../../api/endpoints/models/right';
@@ -7,6 +6,7 @@ import {Phenomenon} from '../../api/endpoints/models/phenomenon';
 import {SensorsService} from '../../api/endpoints/services/sensors.service';
 import {InsertUnit} from '../../api/endpoints/models/insert-unit';
 import { InsertSensor } from '../../api/endpoints/models/insert-sensor';
+import {AuthSecurityService} from '../../../auth/services/auth-security.service';
 
 @Component({
   selector: 'app-nav-bar',
@@ -27,7 +27,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
   @Input() sensorTypes;
 
   constructor(
-    private authService: AuthService,
+    private authSecurityService: AuthSecurityService,
     private sensorService: SensorsService
   ) {
   }
@@ -39,18 +39,16 @@ export class NavBarComponent implements OnInit, OnDestroy {
   /**
    * Get user from state after logged
    */
-  setUser(){
-    this.authService.getUserState().subscribe(res => {
-      if(res){
-        this.loggedUser = res;
-      }
+  setUser(): void {
+    this.authSecurityService.profile$.subscribe((user: User) => {
+      this.loggedUser = user;
     });
   }
 
   /**
    * Show insert unit popup
    */
-  insertUnitPopup() {
+  insertUnitPopup(): void {
     this.sensorService.getPhenomenons().subscribe(
       response => this.phenomenons = response
     );
@@ -60,7 +58,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
   /**
    * Show data download popup
    */
-  downloadData() {
+  downloadData(): void {
     this.sensorService.getPhenomenons().subscribe(
       response => this.phenomenons = response
     );
@@ -68,7 +66,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
   }
 
   logOut(): void {
-    this.authService.doLogout();
+    this.authSecurityService.logout();
   }
 
   /**
@@ -81,7 +79,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
   /**
    * Show add user popup
    */
-  addUser() {
+  addUser(): void {
     this.showAddUserPopup = true;
   }
 
@@ -89,7 +87,7 @@ export class NavBarComponent implements OnInit, OnDestroy {
    * Emit inserted unit to add it to units
    * @param inserted inserted unit
    */
-  addUnit(inserted: any) {
+  addUnit(inserted: any): void {
     this.emitNewUnit.emit(inserted);
   }
 }

+ 7 - 0
src/app/user-registration/user-profile.ts

@@ -0,0 +1,7 @@
+import {JwtPayload} from 'jwt-decode';
+
+export interface JwtPayloadProfile extends JwtPayload {
+    name: string,
+    nickname: string,
+    clientId?: string,
+}

+ 44 - 0
src/app/user-registration/user-registration.component.html

@@ -0,0 +1,44 @@
+<div class="user-registration">
+    <div class="container">
+        <div class="card">
+            <div class="card-header">
+                <div class="login-logo">
+                    <img src="/assets/images/senslog-logo.svg" alt="Logo SensLog">
+                </div>
+            </div>
+            <div class="card-body">
+                <div class="user-profile">
+                    <p>username: {{userProfile.nickname}}</p>
+                    <p>name: {{userProfile.name}}</p>
+                </div>
+                <form [formGroup]="groupsForm" (ngSubmit)="assignUserToGroup()">
+                    <ng-container *ngIf="isExpired; else elseNotDone">
+                        <p>
+                            <select disabled formControlName="groupId">
+                                <option [ngValue]="null" disabled>Select Group</option>
+                            </select>
+                        </p>
+                        <div>
+                            <button disabled pButton type="submit" label="Complete registration" class="p-button-primary dark"></button>
+                        </div>
+                        <div>
+                            <p class="text-message">User registration was already expired.</p>
+                        </div>
+                    </ng-container>
+
+                    <ng-template #elseNotDone>
+                        <p>
+                            <select formControlName="groupId">
+                                <option [ngValue]="null" disabled>Select Group</option>
+                                <option *ngFor="let g of sensLogGroups" [ngValue]="g.id">{{g.group_name}}</option>
+                            </select>
+                        </p>
+                        <div>
+                            <button pButton type="submit" label="Complete registration" class="p-button-primary dark"></button>
+                        </div>
+                    </ng-template>
+                </form>
+            </div>
+        </div>
+    </div>
+</div>

+ 32 - 0
src/app/user-registration/user-registration.component.scss

@@ -0,0 +1,32 @@
+.user-registration {
+  height: calc(100% - 45px);
+  color: transparent;
+
+  .container {
+    height: 100%;
+    align-content: center;
+  }
+
+  .card-header {
+    padding: 0;
+  }
+
+  .card {
+    margin-top: auto;
+    margin-bottom: auto;
+    background: #f9f9f9;
+    border: 2px solid #174B97FF;
+  }
+
+  .card-body {
+    text-align: center;
+
+    .user-profile {
+
+    }
+  }
+}
+
+.text-message {
+  color: red;
+}

+ 54 - 0
src/app/user-registration/user-registration.component.ts

@@ -0,0 +1,54 @@
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute, Params} from '@angular/router';
+import {JwtPayloadProfile} from './user-profile';
+import {jwtDecode} from 'jwt-decode';
+import {FormBuilder, FormGroup} from '@angular/forms';
+import {GroupService} from '../shared/api/endpoints/services/group.service';
+import {Group} from '../shared/api/endpoints/models/group';
+import {AdministrationService} from '../shared/api/endpoints/services/administration.service';
+
+@Component({
+  selector: 'app-user-registration-component',
+  templateUrl: './user-registration.component.html',
+  styleUrls: ['./user-registration.component.scss']
+})
+export class UserRegistrationComponent implements OnInit {
+
+
+  protected groupsForm: FormGroup;
+  protected isExpired: boolean;
+  protected userProfile: JwtPayloadProfile;
+  protected userProfileRaw: string;
+  protected sensLogGroups: Group[] = [];
+
+  constructor(
+      private fb:FormBuilder,
+      private route: ActivatedRoute,
+      private groupService: GroupService,
+      private adminService: AdministrationService
+  ) {}
+
+  ngOnInit(): void {
+    this.groupService.getAllGroups().subscribe((groups: Group[]): void => {
+        this.sensLogGroups = groups;
+    })
+    this.route.queryParams.subscribe((params: Params): void => {
+        this.userProfileRaw = params.profile;
+        this.userProfile = jwtDecode<JwtPayloadProfile>(this.userProfileRaw);
+        const expiration: Date = new Date(this.userProfile.exp*1000);
+        this.isExpired = false; // expiration >= new Date();
+    });
+    this.groupsForm = this.fb.group({
+          groupId: [null]
+    });
+  }
+
+  assignUserToGroup(): void {
+      if (this.groupsForm.value.groupId) {
+          this.adminService.createUserByEncodedProfile({
+              profile: this.userProfileRaw,
+              groupId: this.groupsForm.value.groupId
+          }).subscribe();
+      }
+  }
+}

+ 1 - 1
src/environments/environment.ts

@@ -1,5 +1,5 @@
 export const environment = {
   production: false,
-  sensLogBaseUrl: 'https://sensor.lesprojekt.cz/senslog15',
+  sensLogBaseUrl: 'http://localhost:8080/DBService_war',
   hslayersAssetsPath: './hslayers-ng/assets'
 };