diff --git a/jambotron-ui/angular.json b/jambotron-ui/angular.json index 8624583..ba195ca 100644 --- a/jambotron-ui/angular.json +++ b/jambotron-ui/angular.json @@ -65,7 +65,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" diff --git a/jambotron-ui/src/app/app.config.ts b/jambotron-ui/src/app/app.config.ts index 6373f43..ffe0624 100644 --- a/jambotron-ui/src/app/app.config.ts +++ b/jambotron-ui/src/app/app.config.ts @@ -18,9 +18,11 @@ import 'prismjs'; import 'prismjs/components/prism-typescript.min.js'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; import 'prismjs/plugins/line-highlight/prism-line-highlight.js'; +import {authInterceptorProviders} from './helpers/auth.interceptor'; export const appConfig: ApplicationConfig = { providers: [ + authInterceptorProviders, provideBrowserGlobalErrorListeners(), provideAnimations(), provideZoneChangeDetection({ eventCoalescing: true }), diff --git a/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts index 11913d6..74325b8 100644 --- a/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts +++ b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts @@ -3,6 +3,9 @@ import {Tutorial} from '../../models/tutorial.model'; import {TutorialService} from '../../services/tutorial.service'; import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MarkdownComponent} from 'ngx-markdown'; +import {authInterceptorProviders} from '../../helpers/auth.interceptor'; +import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; +import {CustomHttpInterceptor} from '../../helpers/custom-http-interceptor'; @Component({ selector: 'app-tutorials.component', @@ -14,7 +17,12 @@ import {MarkdownComponent} from 'ngx-markdown'; ], templateUrl: './tutorials.component.html', styleUrl: './tutorials.component.scss', - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [authInterceptorProviders,{ + provide: HTTP_INTERCEPTORS, + useClass: CustomHttpInterceptor, + multi: true + }], }) export class TutorialsComponent { tutorials?: Tutorial[]; diff --git a/jambotron-ui/src/app/helpers/auth.interceptor.ts b/jambotron-ui/src/app/helpers/auth.interceptor.ts index 47204e7..6d52bee 100644 --- a/jambotron-ui/src/app/helpers/auth.interceptor.ts +++ b/jambotron-ui/src/app/helpers/auth.interceptor.ts @@ -12,15 +12,25 @@ export class AuthInterceptor implements HttpInterceptor { constructor(private token: TokenStorageService) { } intercept(req: HttpRequest, next: HttpHandler): Observable> { - let authReq = req; + + + req = req.clone({ + withCredentials: true, + }); + + return next.handle(req); + + + /*let authReq = req; const token = this.token.getToken(); if (token != null) { authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) }); } - return next.handle(authReq); + return next.handle(authReq);*/ + } } export const authInterceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } -]; \ No newline at end of file +]; diff --git a/jambotron-ui/src/app/main-module/main.component/main.component.ts b/jambotron-ui/src/app/main-module/main.component/main.component.ts index 4ea4697..86d226c 100644 --- a/jambotron-ui/src/app/main-module/main.component/main.component.ts +++ b/jambotron-ui/src/app/main-module/main.component/main.component.ts @@ -18,6 +18,9 @@ import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; import {MatTooltip} from '@angular/material/tooltip'; import {DialogSignupComponent} from '../../components/dialog-signup.component/dialog-signup.component'; import {MatLabel} from '@angular/material/input'; +import {HttpClient} from '@angular/common/http'; + +import { environment } from '../../../environments/environment'; @Component({ selector: 'app-main.component', @@ -62,12 +65,13 @@ export class MainComponent implements OnInit{ eventBusSub?: Subscription; + private environment = environment; private storageService: TokenStorageService = inject(TokenStorageService); private authService: AuthService = inject(AuthService); private eventBusService: EventBusService = inject(EventBusService); - constructor(private router: Router) { + constructor(private router: Router,private http: HttpClient) { } goToHome() { @@ -136,7 +140,8 @@ export class MainComponent implements OnInit{ console.log(res); this.storageService.clean(); - window.location.reload(); + //window.location.reload(); + this.router.navigate([environment.default_page]); }, error: err => { console.log(err); @@ -167,8 +172,9 @@ export class MainComponent implements OnInit{ this.dialogLoginData = result; this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe( - data => { - this.storageService.saveToken(data.accessToken); + data => { + + //this.storageService.saveToken(data.token); this.storageService.saveUser(data); this.isLoggedIn = true; diff --git a/jambotron-ui/src/app/main-module/main.module.ts b/jambotron-ui/src/app/main-module/main.module.ts index c5b199f..0f580af 100644 --- a/jambotron-ui/src/app/main-module/main.module.ts +++ b/jambotron-ui/src/app/main-module/main.module.ts @@ -4,11 +4,12 @@ import {mainRouting} from './main.routing'; import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; -import {HTTP_INTERCEPTORS} from '@angular/common/http'; +import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http'; import {CustomHttpInterceptor} from '../helpers/custom-http-interceptor'; import {AngularImageViewerModule} from '@hreimer/angular-image-viewer'; import {BrowserModule} from '@angular/platform-browser'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {authInterceptorProviders} from '../helpers/auth.interceptor'; @NgModule({ declarations: [ @@ -22,7 +23,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; ], exports: [RouterModule], - providers: [{ + providers: [authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{ provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true diff --git a/jambotron-ui/src/app/services/auth.service.ts b/jambotron-ui/src/app/services/auth.service.ts index e02cd4a..0a920d3 100644 --- a/jambotron-ui/src/app/services/auth.service.ts +++ b/jambotron-ui/src/app/services/auth.service.ts @@ -19,6 +19,8 @@ export class AuthService { username, password }, httpOptions); + + } register(username: string, email: string, password: string): Observable { diff --git a/jambotron-ui/src/app/services/tutorial.service.ts b/jambotron-ui/src/app/services/tutorial.service.ts index adbf3e4..9725c8c 100644 --- a/jambotron-ui/src/app/services/tutorial.service.ts +++ b/jambotron-ui/src/app/services/tutorial.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Tutorial } from '../models/tutorial.model'; +import {text} from 'node:stream/consumers'; const baseUrl = 'http://localhost:8080/api/tutorials'; @@ -39,4 +40,4 @@ export class TutorialService { findByTitle(title: any): Observable { return this.http.get(`${baseUrl}?title=${title}`); } -} \ No newline at end of file +} diff --git a/jambotron-ui/src/server.ts b/jambotron-ui/src/server.ts index e6546c4..5a5b814 100644 --- a/jambotron-ui/src/server.ts +++ b/jambotron-ui/src/server.ts @@ -62,6 +62,9 @@ if (isMainModule(import.meta.url)) { }); } + + + /** * Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions. */ diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java index c1b1b97..1e8401f 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java @@ -13,6 +13,8 @@ import com.jambotronGroup.jambotron.repository.UserRepository; import com.jambotronGroup.jambotron.security.jwt.JwtUtils; import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; @@ -34,6 +36,8 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/api/auth") public class AuthController { + + private static final Logger logger = LoggerFactory.getLogger(AuthController.class); @Autowired AuthenticationManager authenticationManager; @@ -65,12 +69,16 @@ public class AuthController { .map(item -> item.getAuthority()) .collect(Collectors.toList()); + logger.info("User {} authenticated successfully with roles: {}", userDetails.getUsername(), roles); + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .body(new UserInfoResponse( userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), - roles)); + roles, + jwtCookie.toString() + )); } @PostMapping("/signup") diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 02ee9fd..4ae9c22 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -2,10 +2,20 @@ package com.jambotronGroup.jambotron.controllers; import com.jambotronGroup.jambotron.model.Tutorial; +import com.jambotronGroup.jambotron.model.User; import com.jambotronGroup.jambotron.repository.TutorialRepository; +import com.jambotronGroup.jambotron.repository.UserRepository; +import com.jambotronGroup.jambotron.security.AuthenticationFacade; +import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; @@ -19,6 +29,11 @@ import java.util.Optional; @RequestMapping("/api") public class TutorialController { + @Autowired + AuthenticationFacade authenticationFacade; + @Autowired + UserRepository userRepository; + @Autowired TutorialRepository tutorialRepository; @@ -27,10 +42,29 @@ public class TutorialController { try { List tutorials = new ArrayList(); - if (title == null) - tutorialRepository.findAll().forEach(tutorials::add); - else - tutorialRepository.findByTitleContaining(title).forEach(tutorials::add); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + String name = authenticationFacade.getAuthentication().getName(); + + User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get(); + if (user.getRoles().stream().anyMatch(role -> role.getName().name().equals("ROLE_ADMIN"))) { + if (title == null) + tutorialRepository.findAll().forEach(tutorials::add); + else + tutorialRepository.findByTitleContaining(title).forEach(tutorials::add); + + } else { + + // If the user is not an admin, filter tutorials by user + tutorialRepository.findByUserId(user.getId()).forEach(tutorials::add); + if (tutorials.isEmpty()) { + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + return new ResponseEntity<>(tutorials, HttpStatus.OK); + } + + + if (tutorials.isEmpty()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); @@ -55,9 +89,15 @@ public class TutorialController { @PostMapping("/tutorials") public ResponseEntity createTutorial(@RequestBody Tutorial tutorial) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + + User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get(); + try { Tutorial _tutorial = tutorialRepository - .save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false)); + .save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user)); return new ResponseEntity<>(_tutorial, HttpStatus.CREATED); } catch (Exception e) { return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java index 0c92d84..1a773c3 100644 --- a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java +++ b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java @@ -21,14 +21,19 @@ public class Tutorial { @Column(name = "published") private boolean published; + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "userID", nullable = false) + private User user; + public Tutorial() { } - public Tutorial(String title, String description, boolean published) { + public Tutorial(String title, String description, boolean published, User user) { this.title = title; this.description = description; this.published = published; + this.user = user; } public long getId() { diff --git a/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java b/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java index 708470a..50ccb62 100644 --- a/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java +++ b/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java @@ -10,11 +10,14 @@ public class UserInfoResponse { private String email; private List roles; - public UserInfoResponse(Long id, String username, String email, List roles) { + private String token; + + public UserInfoResponse(Long id, String username, String email, List roles, String token) { this.id = id; this.username = username; this.email = email; this.roles = roles; + this.token = token; } public Long getId() { @@ -44,4 +47,12 @@ public class UserInfoResponse { public List getRoles() { return roles; } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } } diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java index df061a0..c11736b 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java @@ -10,6 +10,8 @@ import java.util.List; @Repository public interface TutorialRepository extends JpaRepository { + + List findByUserId(Long userId); List findByPublished(boolean published); List findByTitleContaining(String title); } \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java index 0b9d998..7701ee0 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java @@ -9,6 +9,8 @@ import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); Boolean existsByUsername(String username); diff --git a/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java new file mode 100644 index 0000000..e0aef45 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java @@ -0,0 +1,16 @@ +package com.jambotronGroup.jambotron.security; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class AuthenticationFacade implements IAuthenticationFacade { + + @Override + public Authentication getAuthentication() { + + return SecurityContextHolder.getContext().getAuthentication(); + } +} + diff --git a/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java new file mode 100644 index 0000000..6145de9 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java @@ -0,0 +1,7 @@ +package com.jambotronGroup.jambotron.security; + +import org.springframework.security.core.Authentication; + +public interface IAuthenticationFacade { + Authentication getAuthentication(); +} \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java index 271c865..5fc4c7c 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java @@ -43,10 +43,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri // authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); // } - @Override - public void addCorsMappings(CorsRegistry registry) { - // Do not add any mappings to enable complete disabling of CORS. - } + @Bean public DaoAuthenticationProvider authenticationProvider() { @@ -118,7 +115,8 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/main/**").permitAll() .requestMatchers("/api/test/**").permitAll() - .requestMatchers("/api/tutorials").permitAll() + + .requestMatchers("/api/tutorials").hasRole("ADMIN") .requestMatchers("/api/zhipuai/image/**").permitAll()