From 5ca6132db71783c0a1bb2dbec406847001a0df69 Mon Sep 17 00:00:00 2001 From: Dimitrije Gasic Date: Wed, 1 Oct 2025 21:33:35 +0200 Subject: [PATCH] Update key management and styling --- .../controller/KeyManagementController.java | 2 +- .../java/ftn/security/minikms/dto/KeyDTO.java | 3 - .../minikms/service/KeyManagementService.java | 7 +- .../src/main/resources/application.properties | 4 +- MiniKmsClient/src/app/api.service.ts | 26 +++++ MiniKmsClient/src/app/app.component.css | 17 ++++ MiniKmsClient/src/app/app.component.html | 31 +++--- MiniKmsClient/src/app/app.module.ts | 11 ++- .../src/app/auth/auth.interceptor.spec.ts | 17 ++++ .../src/app/auth/auth.interceptor.ts | 35 +++++++ .../src/app/login/login.component.scss | 4 +- .../src/app/login/login.component.ts | 7 +- .../manage-keys/manage-keys.component.html | 57 +++++++++-- .../manage-keys/manage-keys.component.scss | 39 ++++++-- .../app/manage-keys/manage-keys.component.ts | 97 ++++++++++++++----- MiniKmsClient/src/index.html | 2 +- MiniKmsClient/src/styles.scss | 1 + 17 files changed, 295 insertions(+), 65 deletions(-) create mode 100644 MiniKmsClient/src/app/auth/auth.interceptor.spec.ts create mode 100644 MiniKmsClient/src/app/auth/auth.interceptor.ts diff --git a/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java index 5a765ae..8bf6892 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java +++ b/MiniKms/src/main/java/ftn/security/minikms/controller/KeyManagementController.java @@ -31,7 +31,7 @@ public ResponseEntity createKey(@RequestBody KeyDTO dto, Principal principal) var username = principal.getName(); try { - var created = keyService.createKey(dto.getAlias(), dto.getKeyType(), dto.getAllowedOperations(), username); + var created = keyService.createKey(dto.getAlias(), dto.getKeyType(), username); return ResponseEntity.status(HttpStatus.CREATED).body(mapper.toDto(created)); } catch (InvalidParameterException e) { return ResponseEntity.badRequest().body(e.getMessage()); diff --git a/MiniKms/src/main/java/ftn/security/minikms/dto/KeyDTO.java b/MiniKms/src/main/java/ftn/security/minikms/dto/KeyDTO.java index 35e33b3..c0dc88e 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/dto/KeyDTO.java +++ b/MiniKms/src/main/java/ftn/security/minikms/dto/KeyDTO.java @@ -1,10 +1,8 @@ package ftn.security.minikms.dto; -import ftn.security.minikms.enumeration.KeyOperation; import ftn.security.minikms.enumeration.KeyType; import lombok.*; -import java.util.List; import java.util.UUID; @Data @@ -13,5 +11,4 @@ public class KeyDTO { private UUID id; private String alias; private KeyType keyType; - private List allowedOperations; } diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java b/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java index d657fe5..5be4c5f 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/KeyManagementService.java @@ -41,9 +41,14 @@ KeyType.HMAC, new HMACService() ); } - public KeyMetadata createKey(String alias, KeyType keyType, List allowedOperations, String username) + public KeyMetadata createKey(String alias, KeyType keyType, String username) throws InvalidParameterException, GeneralSecurityException { var user = findUserByUsername(username); + var allowedOperations = switch (keyType) { + case SYMMETRIC -> List.of(KeyOperation.ENCRYPT); + case ASYMMETRIC -> List.of(KeyOperation.ENCRYPT, KeyOperation.SIGN); + case HMAC -> List.of(KeyOperation.SIGN); + }; var metadata = metadataRepository.save(KeyMetadata.of(alias, keyType, allowedOperations, user)); return createNewKeyVersion(metadata, 1); } diff --git a/MiniKms/src/main/resources/application.properties b/MiniKms/src/main/resources/application.properties index 5dbf986..db959fe 100644 --- a/MiniKms/src/main/resources/application.properties +++ b/MiniKms/src/main/resources/application.properties @@ -22,8 +22,8 @@ server.ssl.key-alias=minikms # Debugging #spring.jpa.show-sql=true -#logging.level.org.springframework.security=DEBUG -#logging.level.io.jsonwebtoken=DEBUG +logging.level.org.springframework.security=DEBUG +logging.level.io.jsonwebtoken=DEBUG # 1 hour jwt.expiration=3600000 diff --git a/MiniKmsClient/src/app/api.service.ts b/MiniKmsClient/src/app/api.service.ts index b5b6b2e..409f9f5 100644 --- a/MiniKmsClient/src/app/api.service.ts +++ b/MiniKmsClient/src/app/api.service.ts @@ -12,6 +12,16 @@ export interface LoginResponse { token: string; } +export interface KeyMetadata { + keyId: string; + alias: string; + primaryVersion: number; + keyType: string; + allowedOperations?: string[]; + createdAt?: Date; + rotatedAt?: Date; +} + @Injectable({ providedIn: 'root' }) @@ -23,4 +33,20 @@ export class ApiService { login(credentials: LoginRequest): Observable { return this.http.post(`${this.baseUrl}/auth`, credentials); } + + getKeys(): Observable { + return this.http.get(`${this.baseUrl}/keys`); + } + + createKey(alias: string, keyType: string): Observable { + return this.http.post(`${this.baseUrl}/keys/create`, { alias, keyType }); + } + + rotateKey(id: string): Observable { + return this.http.post(`${this.baseUrl}/keys/rotate`, { id }); + } + + deleteKey(id: string): Observable { + return this.http.delete(`${this.baseUrl}/keys/${id}`); + } } diff --git a/MiniKmsClient/src/app/app.component.css b/MiniKmsClient/src/app/app.component.css index 7ea1284..d0c83a2 100644 --- a/MiniKmsClient/src/app/app.component.css +++ b/MiniKmsClient/src/app/app.component.css @@ -1,3 +1,20 @@ +.app-wrapper { + height: 100%; + display: flex; + flex-direction: column; + + mat-toolbar { + position: sticky; + top: 0; + z-index: 1000; + flex-shrink: 0; + } +} + +.content { + flex: 1 0 auto; +} + .spacer { flex: 1 1 auto; } diff --git a/MiniKmsClient/src/app/app.component.html b/MiniKmsClient/src/app/app.component.html index a682627..1e7dd38 100644 --- a/MiniKmsClient/src/app/app.component.html +++ b/MiniKmsClient/src/app/app.component.html @@ -1,35 +1,36 @@ +
+ Mini KMS Client - + - + - + - \ No newline at end of file +
+ +
+ +
\ No newline at end of file diff --git a/MiniKmsClient/src/app/app.module.ts b/MiniKmsClient/src/app/app.module.ts index 7586420..0c492fb 100644 --- a/MiniKmsClient/src/app/app.module.ts +++ b/MiniKmsClient/src/app/app.module.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { ReactiveFormsModule, FormsModule } from '@angular/forms'; -import { HttpClientModule } from '@angular/common/http'; +import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; @@ -10,6 +10,7 @@ import { LoginComponent } from './login/login.component'; import { ManageKeysComponent } from './manage-keys/manage-keys.component'; import { CryptoComponent } from './crypto/crypto.component'; import { MaterialModule } from './common/material/material.module'; +import { AuthInterceptorService } from './auth/auth.interceptor'; @NgModule({ declarations: [ @@ -27,7 +28,13 @@ import { MaterialModule } from './common/material/material.module'; AppRoutingModule, MaterialModule ], - providers: [], + providers: [ + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptorService, + multi: true + } + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/MiniKmsClient/src/app/auth/auth.interceptor.spec.ts b/MiniKmsClient/src/app/auth/auth.interceptor.spec.ts new file mode 100644 index 0000000..50f38c8 --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.interceptor.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpInterceptorFn } from '@angular/common/http'; + +import { authInterceptor } from './auth.interceptor'; + +describe('authInterceptor', () => { + const interceptor: HttpInterceptorFn = (req, next) => + TestBed.runInInjectionContext(() => authInterceptor(req, next)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/auth/auth.interceptor.ts b/MiniKmsClient/src/app/auth/auth.interceptor.ts new file mode 100644 index 0000000..9167c3d --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.interceptor.ts @@ -0,0 +1,35 @@ +import { HttpInterceptorFn, HttpErrorResponse, HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { catchError, Observable, throwError } from 'rxjs'; + +export const authInterceptor: HttpInterceptorFn = (req, next) => { + const router = inject(Router); + const token = localStorage.getItem('token'); + + let authReq = req; + if (token) { + authReq = req.clone({ + setHeaders: { + Authorization: `Bearer ${token}` + } + }); + } + + return next(authReq).pipe( + catchError((error: HttpErrorResponse) => { + if (error.status === 401 || error.status === 403) { + localStorage.removeItem('token'); + router.navigate(['/login']); + } + return throwError(() => error); + }) + ); +}; + +@Injectable() +export class AuthInterceptorService implements HttpInterceptor { + intercept(req: HttpRequest, next: HttpHandler): Observable> { + return authInterceptor(req, next.handle.bind(next)); + } +} diff --git a/MiniKmsClient/src/app/login/login.component.scss b/MiniKmsClient/src/app/login/login.component.scss index e1f07d8..6f2272e 100644 --- a/MiniKmsClient/src/app/login/login.component.scss +++ b/MiniKmsClient/src/app/login/login.component.scss @@ -1,8 +1,9 @@ .login-container { - min-height: 100vh; + height: 100%; display: flex; align-items: center; justify-content: center; + box-sizing: border-box; padding: 24px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } @@ -14,6 +15,7 @@ width: 100%; max-width: 420px; box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15); + margin-bottom: 100px; } .login-header { diff --git a/MiniKmsClient/src/app/login/login.component.ts b/MiniKmsClient/src/app/login/login.component.ts index f1372ec..bed451b 100644 --- a/MiniKmsClient/src/app/login/login.component.ts +++ b/MiniKmsClient/src/app/login/login.component.ts @@ -51,7 +51,12 @@ export class LoginComponent implements OnInit { }, error: (error) => { console.error('Login error:', error); - this.snackBar.open('Invalid credentials', 'Close', { duration: 3000 }); + if (error.status === 0) { + this.snackBar.open('Server is unreachable', 'Close', { duration: 3000 }); + } else if (error.status === 401 || error.status === 403) { + this.snackBar.open('Invalid credentials', 'Close', { duration: 3000 }); + } + this.setLoadingState(false); } }); diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.html b/MiniKmsClient/src/app/manage-keys/manage-keys.component.html index b682bd4..86c6b76 100644 --- a/MiniKmsClient/src/app/manage-keys/manage-keys.component.html +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.html @@ -1,29 +1,34 @@
Alias - + Key Type - + {{ type }} - + + +
+ +
- + - + @@ -38,19 +43,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Key ID {{element.keyId}} {{element.id}} {{element.keyType}} Current Version {{element.primaryVersion}} Allowed Operations {{element.allowedOperations?.join(', ')}} Created At {{element.createdAt | date:'short'}} Rotated At {{element.rotatedAt ? (element.rotatedAt | date:'short') : 'Never' }} Actions - - + No keys available +
diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss b/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss index 45e5640..d7c8f43 100644 --- a/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss @@ -1,6 +1,6 @@ :host { display: block; - min-height: 100vh; + height: 100%; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; box-sizing: border-box; @@ -13,14 +13,14 @@ margin: 20px auto; width: 60%; background: white; - padding: 16px; + padding: 16px 36px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); - padding-top: 36px; } .control { flex: 1; + margin-top: 20px; } button.add-btn { @@ -28,10 +28,37 @@ button.add-btn { } .full-width-table { - width: 80%; - margin: 0 auto; - background: white; + margin-top: 50px; + background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.15); +} + +.no-data-cell { + text-align: center; + padding: 40px; + color: #666; + font-style: italic; +} + +.no-data-row { + height: 80px; +} + +.actions-cell { + padding-left: 5px; + padding-right: 16px; +} + +.loading-indicator { + display: flex; + align-items: center; + gap: 8px; + margin-left: 16px; + + span { + color: #666; + font-size: 14px; + } } \ No newline at end of file diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts b/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts index a7899db..9f743ac 100644 --- a/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts @@ -1,11 +1,6 @@ import { Component } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; - -export interface KeyRecord { - keyId: string; - alias: string; - keyType: string; -} +import { ApiService, KeyMetadata } from '../api.service'; @Component({ selector: 'app-manage-keys', @@ -13,28 +8,86 @@ export interface KeyRecord { styleUrl: './manage-keys.component.scss' }) export class ManageKeysComponent { -keyTypes: string[] = ['AES', 'RSA', 'HMAC']; + keyTypes: string[] = ['Symmetric', 'Asymmetric', 'HMAC']; + displayedColumns: string[] = ['id', 'alias', 'currentVersion', 'keyType', 'allowedOperations', 'createdAt', 'rotatedAt', 'actions']; + dataSource = new MatTableDataSource(); + + // Form values + alias: string = ''; + selectedKeyType: string = ''; + + // Loading states + isLoading = false; + isCreating = false; - displayedColumns: string[] = ['keyId', 'alias', 'keyType', 'actions']; - dataSource = new MatTableDataSource([ - { keyId: '1', alias: 'Key One', keyType: 'AES' }, - { keyId: '2', alias: 'Key Two', keyType: 'RSA' }, - { keyId: '3', alias: 'Key Three', keyType: 'AES' }, - { keyId: '4', alias: 'Key Four', keyType: 'HMAC' } - ]); + constructor(private apiService: ApiService) {} + + ngOnInit() { + this.loadKeys(); + } + + loadKeys() { + this.isLoading = true; + this.apiService.getKeys().subscribe({ + next: keys => { + console.log(keys); + this.dataSource.data = keys; + this.isLoading = false; + }, + error: () => { + this.isLoading = false; + } + }); + } + + isNoData = (index: number, item: any) => { + return this.dataSource.data.length === 0; + }; addKey() { - console.log('Add key clicked'); - // TODO: implement + if (!this.alias || !this.selectedKeyType) { + console.log('Please fill in all fields'); + return; + } + + this.isCreating = true; + this.apiService.createKey(this.alias, this.selectedKeyType).subscribe({ + next: key => { + this.isCreating = false; + console.log('Key created', key); + this.loadKeys(); + }, + error: () => { + this.isCreating = false; + } + }); } - deleteKey(key: KeyRecord) { - console.log('Delete key', key); - // TODO: implement + deleteKey(id: string) { + console.log('Deleting key', id); + this.isLoading = true; + this.apiService.deleteKey(id).subscribe({ + next: () => { + console.log('Key deleted', id); + this.loadKeys(); + }, + error: (error) => { + console.error('Error deleting key', error); + } + }); } - rotateKey(key: KeyRecord) { - console.log('Rotate key', key); - // TODO: implement + rotateKey(id: string) { + console.log('Rotating key', id); + this.isLoading = true; + this.apiService.rotateKey(id).subscribe({ + next: (response) => { + console.log('Key rotated', response); + this.loadKeys(); + }, + error: (error) => { + console.error('Error rotating key', error); + } + }); } } diff --git a/MiniKmsClient/src/index.html b/MiniKmsClient/src/index.html index b6ea134..5d1d521 100644 --- a/MiniKmsClient/src/index.html +++ b/MiniKmsClient/src/index.html @@ -2,7 +2,7 @@ - MiniKmsClient + Mini KMS diff --git a/MiniKmsClient/src/styles.scss b/MiniKmsClient/src/styles.scss index 015f911..369cf8e 100644 --- a/MiniKmsClient/src/styles.scss +++ b/MiniKmsClient/src/styles.scss @@ -3,6 +3,7 @@ html, body { height: 100%; margin: 0; + box-sizing: border-box; font-family: 'Inter', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;