Implement user-specific tutorial access and enhance authentication handling

This commit is contained in:
liosha84
2025-07-06 11:00:17 +03:00
parent 140de83d0a
commit 3fdb1b4640
18 changed files with 153 additions and 25 deletions
+7 -1
View File
@@ -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"
+2
View File
@@ -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 }),
@@ -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[];
@@ -12,15 +12,25 @@ export class AuthInterceptor implements HttpInterceptor {
constructor(private token: TokenStorageService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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 }
];
];
@@ -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;
@@ -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
@@ -19,6 +19,8 @@ export class AuthService {
username,
password
}, httpOptions);
}
register(username: string, email: string, password: string): Observable<any> {
@@ -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<Tutorial[]> {
return this.http.get<Tutorial[]>(`${baseUrl}?title=${title}`);
}
}
}
+3
View File
@@ -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.
*/
@@ -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")
@@ -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<Tutorial> tutorials = new ArrayList<Tutorial>();
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<Tutorial> 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);
@@ -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() {
@@ -10,11 +10,14 @@ public class UserInfoResponse {
private String email;
private List<String> roles;
public UserInfoResponse(Long id, String username, String email, List<String> roles) {
private String token;
public UserInfoResponse(Long id, String username, String email, List<String> 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<String> getRoles() {
return roles;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
@@ -10,6 +10,8 @@ import java.util.List;
@Repository
public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
List<Tutorial> findByUserId(Long userId);
List<Tutorial> findByPublished(boolean published);
List<Tutorial> findByTitleContaining(String title);
}
@@ -9,6 +9,8 @@ import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Boolean existsByUsername(String username);
@@ -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();
}
}
@@ -0,0 +1,7 @@
package com.jambotronGroup.jambotron.security;
import org.springframework.security.core.Authentication;
public interface IAuthenticationFacade {
Authentication getAuthentication();
}
@@ -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()