Browse Source

jwt validation, authorization, accessible views in Router are set based on role from token

mateuszsudra 2 years ago
parent
commit
1114d0ddb5

+ 2 - 2
boat-reservation-logic/src/main/java/pl/sudra/domain/User.java

@@ -16,7 +16,7 @@ public class User {
     @Column(unique = true)
     @NotNull
     @NotBlank(message = "Username is required")
-    @Size(message = "Username must be between 6 and 20 characters", min = 6, max = 20)
+    @Size(message = "Username must be between 5 and 20 characters", min = 5, max = 20)
     private String username;
 
     //    @JsonIgnore
@@ -29,7 +29,7 @@ public class User {
     @Email(regexp = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$", message = "Invalid email address")
     private String email;
 
-    private String role = "USER";
+    private String role = "ADMIN";
 
     public User() {
     }

+ 95 - 8
boat-reservation-logic/src/main/java/pl/sudra/securityController/CustomInterceptor.java

@@ -2,29 +2,116 @@ package pl.sudra.securityController;
 
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpStatus;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.ModelAndView;
 
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 public class CustomInterceptor implements HandlerInterceptor {
+    // endpoints
+    List<String> noAuth = List.of(
+            "/hello",
+            "/login",
+            "/register",
+            "/getAllBoats"
+    );
+    List<String> customer_and_above = List.of(
+            "/createReservation",
+            "/findReservations"
+    );
+    List<String> manager_and_above = Stream.concat(customer_and_above.stream(),
+                    List.of(
+                            "/addBoat"
+                    ).stream())
+            .collect(Collectors.toList());
+
+    List<String> admin = Stream.concat(manager_and_above.stream(),
+                    List.of(
+                            "/generateReservations"
+                    ).stream())
+            .collect(Collectors.toList());
+
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
-        String token = request.getHeader("Authorization");
-        System.out.println("pre");
-        System.out.println(JwtTokenUtil.validateToken(token));
-        return HandlerInterceptor.super.preHandle(request, response, handler);
+        if (Objects.equals(request.getMethod(), "OPTIONS")) {
+            response.setHeader("Access-Control-Allow-Origin", "*");
+            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
+            response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
+            response.setHeader("Access-Control-Max-Age", "3600");
+            return true;
+        }
+        System.out.println("test: " + request.getHeader("access-control-request-headers"));
+        System.out.println("test here: " + request.getHeader("access-control-request-headers.authorization"));
+
+        String endpoint = request.getRequestURI();
+//        System.out.println(request.getHeaderNames().toString());
+        Enumeration<String> headerNames = request.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+            String headerName = headerNames.nextElement();
+            String headerValue = request.getHeader(headerName);
+            System.out.println(headerName + ": " + headerValue);
+        }
+        // if endpoint require no authorization
+        if (noAuth.contains(endpoint)) {
+            return HandlerInterceptor.super.preHandle(request, response, handler);
+        }
+        // authorization required
+        else {
+            try {
+                String token = request.getHeader("Authorization").split(" ")[1];
+
+                // is Token valid and not expired?
+                if (JwtTokenUtil.validateToken(token)) {
+                    if (customer_and_above.contains(endpoint) &&
+                            Objects.equals(JwtTokenUtil.getRoleFromToken(token), "CLIENT")) {
+                        System.out.println("customer_and_above");
+                        return HandlerInterceptor.super.preHandle(request, response, handler);
+                    } else if (manager_and_above.contains(endpoint) &&
+                            Objects.equals(JwtTokenUtil.getRoleFromToken(token), "MANAGER")) {
+                        return HandlerInterceptor.super.preHandle(request, response, handler);
+                    } else if (admin.contains(endpoint) &&
+                            Objects.equals(JwtTokenUtil.getRoleFromToken(token), "ADMIN")) {
+                        return HandlerInterceptor.super.preHandle(request, response, handler);
+                    } else {
+                        response.setStatus(HttpStatus.FORBIDDEN.value());
+                        response.getWriter().write("Unauthorized access");
+                        return false;
+                    }
+                }
+                // token not valid
+                else {
+                    response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                    response.getWriter().write("Invalid token");
+                    return false;
+                }
+            } catch (NullPointerException e) {
+                System.out.println(e);
+                response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                response.getWriter().write("Empty header");
+
+            } catch (Exception e) {
+                System.out.println(e);
+                response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                response.getWriter().write("Missing authorization header");
+                return false;
+            }
+        }
+        return false;
     }
 
     @Override
     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
-        System.out.println("post");
-        System.out.println(request);
         HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
     }
 
     @Override
     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
-        System.out.println("after");
-        System.out.println(request);
         HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
     }
 }

+ 16 - 6
boat-reservation-logic/src/main/java/pl/sudra/securityController/JwtTokenUtil.java

@@ -2,6 +2,7 @@ package pl.sudra.securityController;
 
 import com.sun.jdi.event.ExceptionEvent;
 import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 import org.springframework.stereotype.Component;
@@ -15,7 +16,8 @@ public class JwtTokenUtil {
 
     private static final String SECRET_KEY = "SuperSecureKey2023".repeat(5); // Replace with your own secret key
     //    private static final long EXPIRATION_TIME = 86400000; // 24 hours in milliseconds
-    private static final long EXPIRATION_TIME = 1000 * 30; // 30 sec
+//    private static final long EXPIRATION_TIME = 1000 * 30; // 30 sec
+    private static final long EXPIRATION_TIME = 1000 * 60 * 30; // 30 min
 
     public static String generateToken(String username, Long id, String role) {
         Date now = new Date();
@@ -36,12 +38,17 @@ public class JwtTokenUtil {
     }
 
     public static boolean validateToken(String token) {
-//        final String username = this.getUsernameFromToken(token);
-//        return (username.equals(user.getUsername()) && !isTokenExpired(token));
         try {
-            Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token);
+            Jwts.parserBuilder()
+                    .setSigningKey(SECRET_KEY)
+                    .build()
+                    .parseClaimsJws(token);
             return true;
-        } catch (Exception e) {
+        } catch (ExpiredJwtException e) {
+            System.out.println(e);
+            return false;
+        } catch (Exception e){
+            System.out.println(e);
             return false;
         }
     }
@@ -55,6 +62,9 @@ public class JwtTokenUtil {
     public String getUsernameFromToken(String token) {
         return getClaimFromToken(token, Claims::getSubject);
     }
+    public static String getRoleFromToken(String token) {
+        return getAllClaimsFromToken(token).get("sub_role").toString();
+    }
 
 
     public Date getExpirationDateFromToken(String token) {
@@ -66,7 +76,7 @@ public class JwtTokenUtil {
         return claimsResolver.apply(claims);
     }
 
-    private Claims getAllClaimsFromToken(String token) {
+    private static Claims getAllClaimsFromToken(String token) {
         return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
     }
 }

+ 0 - 1
boat-reservation-logic/src/main/java/pl/sudra/securityController/SecurityController.java

@@ -63,7 +63,6 @@ public class SecurityController {
 
             return ResponseEntity.badRequest().body(errorResponse);
         }
-        System.out.println("Creating reservation");
 
         String hashedPassword = passwordEncoder.encode(user.getPassword());
         user.setPassword(hashedPassword);

+ 42 - 12
boat-reservation-view/src/app/app.module.ts

@@ -1,7 +1,7 @@
 import {APP_INITIALIZER, InjectionToken, NgModule} from '@angular/core';
 import {BrowserModule} from '@angular/platform-browser';
 
-import {RouterModule} from '@angular/router';
+import {RouterModule, Routes} from '@angular/router';
 import {AppComponent} from "./app.component";
 import {BoatsViewComponent} from "./boats-component/boats-view.component";
 import {PageNotFoundComponent} from './page-not-found/page-not-found.component';
@@ -16,12 +16,50 @@ import {ReservationViewComponent} from './reservation-view/reservation-view.comp
 import {MapViewComponent} from './map-view/map-view.component';
 import {AddBoatComponent} from './add-boat-component/add-boat.component';
 import {FormsModule, ReactiveFormsModule} from "@angular/forms";
+import {NotFoundComponent} from './not-found/not-found.component';
 
 export function setupTranslateServiceFactory(
   service: TranslateService): Function {
   return () => service.use('en');
 }
 
+export function setRoutes() {
+  const routes: Routes = [
+    {path: '', component: HomeViewComponent},
+    {path: 'boats', component: BoatsViewComponent},
+    {path: 'login', component: LoginViewComponent},
+    {path: 'register', component: RegisterViewComponent}
+  ];
+
+  const role = sessionStorage.getItem("role");
+
+  if (role) {
+    if (["CLIENT", "MANAGER", "ADMIN"].includes(role.toUpperCase())) {
+      routes.push(
+        {path: 'reservation', component: ReservationViewComponent}
+      );
+    }
+
+    if (["MANAGER", "ADMIN"].includes(role.toUpperCase())) {
+      routes.push(
+        {path: 'add-boat', component: AddBoatComponent})
+    }
+
+    if (["ADMIN"].includes(role.toUpperCase())) {
+      routes.push(
+        // just for now to differentiate roles
+        {path: 'map', component: MapViewComponent}
+      );
+    }
+  }
+
+  routes.push(
+    {path: '**', component: NotFoundComponent}
+  );
+  return routes;
+}
+
+
 @NgModule({
   declarations: [
     AppComponent,
@@ -34,20 +72,12 @@ export function setupTranslateServiceFactory(
     RegisterViewComponent,
     ReservationViewComponent,
     MapViewComponent,
-    AddBoatComponent
+    AddBoatComponent,
+    NotFoundComponent
   ],
   imports: [
     BrowserModule,
-    RouterModule.forRoot([
-      {path: '', component: HomeViewComponent},
-      {path: 'boats', component: BoatsViewComponent},
-      {path: 'login', component: LoginViewComponent},
-      {path: 'register', component: RegisterViewComponent},
-      {path: 'map', component: MapViewComponent},
-      {path: 'reservation', component: ReservationViewComponent},
-      {path: 'add-boat', component: AddBoatComponent},
-      {path: '**', component: PageNotFoundComponent},
-    ]),
+    RouterModule.forRoot(setRoutes()),
     ReactiveFormsModule,
     FormsModule,
     HttpClientModule

+ 5 - 1
boat-reservation-view/src/app/auth-service/auth.service.ts

@@ -2,12 +2,14 @@ import {Injectable} from '@angular/core';
 import {HttpClient, HttpHeaders} from "@angular/common/http";
 // import {JwtHelperService, JWT_OPTIONS} from "@auth0/angular-jwt";
 import jwt_decode from 'jwt-decode';
+import {Router} from "@angular/router";
 
 @Injectable({
   providedIn: 'root'
 })
 export class AuthService {
-  constructor(private http: HttpClient
+  constructor(private http: HttpClient,
+              private router: Router
   ) {
   }
 
@@ -38,6 +40,8 @@ export class AuthService {
         sessionStorage.setItem('username', decoded.sub)
         sessionStorage.setItem('username_id', decoded.sub_id)
         sessionStorage.setItem('role', decoded.sub_role)
+
+        window.location.href = "http://localhost:1410/"
       },
       (error) => {
         console.error('Error making POST request:', error);

+ 3 - 1
boat-reservation-view/src/app/login-view/login-view.component.ts

@@ -1,5 +1,6 @@
 import {Component} from '@angular/core';
 import {AuthService} from "../auth-service/auth.service";
+import {Router} from "@angular/router";
 
 @Component({
   selector: 'app-login-view',
@@ -9,7 +10,8 @@ import {AuthService} from "../auth-service/auth.service";
 export class LoginViewComponent {
   formData: any = {};
 
-  constructor(private authService: AuthService) {
+  constructor(private authService: AuthService,
+              private router: Router) {
   }
 
   login() {

+ 7 - 0
boat-reservation-view/src/app/navbar/navbar.component.html

@@ -36,6 +36,13 @@
         LOGIN
       </a>
     </td>
+    <td>
+      <a class="navbar-button"
+         (click)="logout()"
+         routerLinkActive="activebutton">
+        LOGOUT
+      </a>
+    </td>
   </tr>
 </table>
 </body>

+ 8 - 1
boat-reservation-view/src/app/navbar/navbar.component.ts

@@ -1,4 +1,5 @@
-import { Component } from '@angular/core';
+import {Component} from '@angular/core';
+import {Router} from "@angular/router";
 
 @Component({
   selector: 'app-navbar',
@@ -6,5 +7,11 @@ import { Component } from '@angular/core';
   styleUrls: ['./navbar.component.css']
 })
 export class NavbarComponent {
+  constructor(private router: Router) {
+  }
 
+  logout() {
+    sessionStorage.clear()
+    window.location.href = "http://localhost:1410/"
+  }
 }

+ 0 - 0
boat-reservation-view/src/app/not-found/not-found.component.css


+ 1 - 0
boat-reservation-view/src/app/not-found/not-found.component.html

@@ -0,0 +1 @@
+<p>404 Not found!</p>

+ 10 - 0
boat-reservation-view/src/app/not-found/not-found.component.ts

@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-not-found',
+  templateUrl: './not-found.component.html',
+  styleUrls: ['./not-found.component.css']
+})
+export class NotFoundComponent {
+
+}

+ 7 - 2
boat-reservation-view/src/app/reservation-service/reservation.service.ts

@@ -20,14 +20,19 @@ export class ReservationService {
   findReservations(boat_id: number, date: string): Observable<Reservation[]> {
     const url = 'http://localhost:2137/findReservations?boat_id=' + boat_id + '&date=' + date;
 
-    return this.http.get<Reservation[]>(url);
+    const headers = new HttpHeaders()
+      .set('Content-Type', 'application/json')
+      .set('Authorization', sessionStorage.getItem("jwtToken")!);
+
+    return this.http.get<Reservation[]>(url, {headers});
   }
 
   createReservation(reservation_info: object): any {
     const url = 'http://localhost:2137/createReservation';
 
     const headers = new HttpHeaders()
-      .set('Content-Type', 'application/json');
+      .set('Content-Type', 'application/json')
+      .set('Authorization', sessionStorage.getItem("jwtToken")!);
 
     this.http.post(url, reservation_info, {headers}).subscribe(
       (response) => {