-
Notifications
You must be signed in to change notification settings - Fork 1
feat: implement role based access control rbac #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MaximilianAnzinger
merged 62 commits into
main
from
feature/40-feat-implement-role-based-access-control-rbac
Apr 21, 2026
Merged
Changes from 56 commits
Commits
Show all changes
62 commits
Select commit
Hold shift + click to select a range
650775a
style: change font and polish UI
markstockhausen 79e45a2
style: update font weight
markstockhausen 430e1c2
style: conform to Prettier formatting
markstockhausen 73a402b
Implement optimized Scheduling Service and Pipeline Logic
markstockhausen 8850cf6
style: update comments
markstockhausen fe676c3
Implement AI suggestions
markstockhausen b199432
Update Axios, Optimise scheduling pipeline
markstockhausen 67d6e58
Optimise pipeline logic and clean up code
markstockhausen 60d7f66
Optimise pipeline
markstockhausen 6251a3a
Update logic for bidirectional relationship types
markstockhausen e683410
Streamline implementation
markstockhausen 141bebd
Conform to Prettier formatting
markstockhausen 64c4f20
Implement AI suggestions
markstockhausen d863e77
Further AI suggestions
markstockhausen 846fd3d
Merge branch 'main' into feature/implement-scheduling-service
markstockhausen 7916901
feat: add dashboard with contribution heatmap, badges, and session suβ¦
markstockhausen 4b03244
style: fix Prettier formatting
markstockhausen 2190551
Update heatmap
markstockhausen 785d9fa
Conform to Prettier formatting
markstockhausen 2704820
Fix security audit
markstockhausen 32ccd39
feat: Add initial version of onboarding
markstockhausen 99f89e1
fix onboarding flow
markstockhausen 4a8eeba
conform to Prettier formatting
markstockhausen 49e98c2
Update profile setup
markstockhausen c4a25a2
add simple auth implementation
vtotalova cf2faae
Merge branch 'main' into feature/36-security-implement-secure-authentβ¦
vtotalova 513e8c4
Fix skip feature, update package-lock.json
markstockhausen 47042eb
Add milestone celebration toasts during mapping sessions
markstockhausen 23c8d8b
Update screenshot on main page, extract CompetencyNetworkViz into reuβ¦
markstockhausen 65c98b0
implement auth
vtotalova f4f7497
fix issues
vtotalova 7303eea
fix issues
vtotalova 2d85326
Merge remote-tracking branch 'origin/main' into feature/onboarding
markstockhausen f1de300
Merge branch 'main' into feature/36-security-implement-secure-authentβ¦
vtotalova f3de42b
Merge branch 'main' into feature/36-security-implement-secure-authentβ¦
vtotalova 6f48404
Fix package-lock.json
markstockhausen 333175a
Merge branch 'main' into feature/onboarding
markstockhausen 305892f
Conform to Prettier formatting
markstockhausen ed96f32
Merge branch 'feature/onboarding' of https://github.com/ls1intum/memoβ¦
markstockhausen 44e8467
Fix package-lock.json
markstockhausen 0cb944d
Fix package-lock.json
markstockhausen 3d6d590
fix merging of main issues
vtotalova f05a802
Implement AI suggestions
markstockhausen 5c0dcff
Merge branch 'feature/onboarding' into feature/36-security-implement-β¦
vtotalova 6b55740
fix auth process after merging
vtotalova 15c09ab
implement auth in the onboarding flow
vtotalova 93fb29b
Merge branch 'main' into feature/36-security-implement-secure-authentβ¦
vtotalova a072b81
fix merge conflict issues
vtotalova 781c6fe
update onboarding with policy link
vtotalova 72bebe5
add fast fail if login email not authorized
vtotalova 1fc4738
remove duplicates
vtotalova 32b1769
fix warnings
vtotalova d37c5d8
fix quality issues in package
vtotalova fd6dc02
add admin page
vtotalova 172c52a
refine admin panel
vtotalova db5fbd2
Merge branch 'main' into feature/40-feat-implement-role-based-access-β¦
vtotalova 6290d0a
fix npm packeges
vtotalova 75cca56
add night mode support
vtotalova de0a1f8
fix formating
vtotalova 246cf78
Merge branch 'main' into feature/40-feat-implement-role-based-access-β¦
vtotalova e55c739
fix npm packages
vtotalova 95ab67e
integrate copilot feedback
vtotalova File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| sequenceDiagram | ||
| participant B as Browser | ||
| participant KC as Keycloak | ||
| participant Mail as Email (Mailpit dev) | ||
| participant API as Spring Boot | ||
| participant DB as PostgreSQL | ||
|
|
||
| B->>KC: login() redirect + PKCE | ||
| KC-->>B: login / register form | ||
|
|
||
| alt First-time registration | ||
| B->>KC: submit university email + password | ||
| KC->>Mail: send verification email | ||
| B->>KC: click verification link | ||
| else Returning user | ||
| B->>KC: submit credentials | ||
| end | ||
|
|
||
| KC-->>B: JWT access token | ||
|
|
||
| B->>API: GET /api/auth/me + Bearer JWT | ||
| API->>KC: validate via JWK Set | ||
| API->>API: check email domain in allowlist | ||
|
|
||
| alt Domain not allowed | ||
| API-->>B: 403 Forbidden | ||
| B->>B: domainError = true (Access denied) | ||
| else Domain allowed | ||
| API->>DB: findOrCreate user by sub | ||
| API-->>B: {id, role} | ||
| B->>B: isAuthenticated = true | ||
| end |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <!doctype html> | ||
| <html> | ||
| <body> | ||
| <script> | ||
| parent.postMessage(location.href, location.origin); | ||
| </script> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
server/src/main/java/de/tum/cit/memo/config/MemoProperties.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package de.tum.cit.memo.config; | ||
|
|
||
| import java.util.List; | ||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
|
||
| @ConfigurationProperties(prefix = "memo") | ||
| @Getter | ||
| @Setter | ||
| public class MemoProperties { | ||
|
|
||
| /** | ||
| * Allowed email domains for user access. If empty, all domains are permitted. | ||
| * Example: mytum.de, in.tum.de, tum.de | ||
| */ | ||
| private List<String> allowedEmailDomains = List.of(); | ||
| } |
104 changes: 104 additions & 0 deletions
104
server/src/main/java/de/tum/cit/memo/controller/AdminController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| package de.tum.cit.memo.controller; | ||
|
|
||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import de.tum.cit.memo.dto.CompetencyImportRow; | ||
| import de.tum.cit.memo.dto.ImportResult; | ||
| import de.tum.cit.memo.service.CompetencyService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import java.io.BufferedReader; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/admin") | ||
| @RequiredArgsConstructor | ||
| @Tag(name = "Admin", description = "Admin-only operations") | ||
| @SecurityRequirement(name = "bearer-jwt") | ||
| @SuppressWarnings("null") | ||
| public class AdminController { | ||
|
|
||
| private final CompetencyService competencyService; | ||
| private final ObjectMapper objectMapper; | ||
|
|
||
| @PostMapping("/competencies/import") | ||
| @Operation(summary = "Bulk import competencies from JSON array") | ||
| public ResponseEntity<ImportResult> importJson( | ||
| @Valid @RequestBody List<CompetencyImportRow> rows | ||
| ) { | ||
| return ResponseEntity.ok(competencyService.bulkImportCompetencies(rows)); | ||
| } | ||
|
|
||
| @PostMapping(value = "/competencies/import/file", consumes = "multipart/form-data") | ||
| @Operation(summary = "Bulk import competencies from uploaded CSV or JSON file") | ||
| public ResponseEntity<ImportResult> importFile( | ||
| @RequestParam("file") MultipartFile file | ||
| ) throws IOException { | ||
| String originalFilename = file.getOriginalFilename(); | ||
| List<CompetencyImportRow> rows; | ||
| if (originalFilename != null && originalFilename.endsWith(".csv")) { | ||
| rows = parseCsv(file); | ||
| } else { | ||
| rows = parseJson(file); | ||
| } | ||
| return ResponseEntity.ok(competencyService.bulkImportCompetencies(rows)); | ||
| } | ||
|
|
||
| private List<CompetencyImportRow> parseCsv(MultipartFile file) throws IOException { | ||
| List<CompetencyImportRow> rows = new ArrayList<>(); | ||
| try (BufferedReader reader = new BufferedReader( | ||
| new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { | ||
| String header = reader.readLine(); | ||
| if (header == null) { | ||
| return rows; | ||
| } | ||
| String line; | ||
| while ((line = reader.readLine()) != null) { | ||
| String[] parts = splitCsvLine(line); | ||
| String title = parts.length > 0 ? parts[0].trim() : ""; | ||
| String description = parts.length > 1 ? parts[1].trim() : null; | ||
| rows.add(new CompetencyImportRow(title, description)); | ||
| } | ||
| } | ||
| return rows; | ||
| } | ||
|
|
||
| private String[] splitCsvLine(String line) { | ||
| List<String> fields = new ArrayList<>(); | ||
| StringBuilder current = new StringBuilder(); | ||
| boolean inQuotes = false; | ||
| for (int i = 0; i < line.length(); i++) { | ||
| char c = line.charAt(i); | ||
| if (c == '"') { | ||
| inQuotes = !inQuotes; | ||
| } else if (c == ',' && !inQuotes) { | ||
| fields.add(current.toString()); | ||
| current = new StringBuilder(); | ||
| } else { | ||
| current.append(c); | ||
| } | ||
| } | ||
| fields.add(current.toString()); | ||
| return fields.toArray(new String[0]); | ||
| } | ||
|
vtotalova marked this conversation as resolved.
Outdated
|
||
|
|
||
| private List<CompetencyImportRow> parseJson(MultipartFile file) throws IOException { | ||
| return objectMapper.readValue( | ||
| file.getInputStream(), | ||
| objectMapper.getTypeFactory().constructCollectionType(List.class, CompetencyImportRow.class) | ||
| ); | ||
| } | ||
| } | ||
58 changes: 58 additions & 0 deletions
58
server/src/main/java/de/tum/cit/memo/controller/AuthController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package de.tum.cit.memo.controller; | ||
|
|
||
| import de.tum.cit.memo.config.MemoProperties; | ||
| import de.tum.cit.memo.entity.User; | ||
| import de.tum.cit.memo.service.UserService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.security.SecurityRequirement; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.security.oauth2.jwt.Jwt; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/auth") | ||
| @RequiredArgsConstructor | ||
| @Tag(name = "Auth", description = "Authentication and user sync") | ||
| @SecurityRequirement(name = "bearer-jwt") | ||
| public class AuthController { | ||
|
|
||
| private final UserService userService; | ||
|
|
||
| private final MemoProperties memoProperties; | ||
|
|
||
| @GetMapping("/me") | ||
| @Operation(summary = "Get or create current authenticated user") | ||
| public ResponseEntity<Map<String, String>> me(@AuthenticationPrincipal Jwt jwt) { | ||
| List<String> allowedDomains = memoProperties.getAllowedEmailDomains(); | ||
| if (!allowedDomains.isEmpty()) { | ||
| String email = jwt.getClaimAsString("email"); | ||
| if (email == null || !isAllowedDomain(email, allowedDomains)) { | ||
| return ResponseEntity.status(HttpStatus.FORBIDDEN) | ||
| .body(Map.of("error", "Email domain not permitted. Please use a university email address.")); | ||
| } | ||
| } | ||
| String sub = jwt.getSubject(); | ||
| User user = userService.findOrCreateBySubject(sub); | ||
| return ResponseEntity.ok(Map.of( | ||
| "id", user.getId(), | ||
| "role", user.getRole().name() | ||
| )); | ||
| } | ||
|
|
||
| private static boolean isAllowedDomain(String email, List<String> allowedDomains) { | ||
| int atIndex = email.indexOf('@'); | ||
| if (atIndex < 0) { | ||
| return false; | ||
| } | ||
| String domain = email.substring(atIndex + 1).toLowerCase(java.util.Locale.ROOT); | ||
| return allowedDomains.contains(domain); | ||
| } | ||
| } |
17 changes: 17 additions & 0 deletions
17
server/src/main/java/de/tum/cit/memo/dto/CompetencyImportRow.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package de.tum.cit.memo.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Data; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Data | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CompetencyImportRow { | ||
|
|
||
| @NotBlank(message = "Title is required") | ||
| private String title; | ||
|
|
||
| private String description; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package de.tum.cit.memo.dto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record ImportResult(int imported, int skipped, List<String> errors) {} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.