From 5e2716b3f227784a3dba4bf68f60fb4f055f349f Mon Sep 17 00:00:00 2001 From: Dimitrije Gasic Date: Wed, 1 Oct 2025 16:28:41 +0200 Subject: [PATCH] Add routing, auth components and home component --- .../minikms/config/SecurityConfig.java | 2 +- .../minikms/controller/AuthController.java | 18 +++- .../minikms/service/auth/JwtService.java | 5 +- MiniKms/src/main/resources/cert.crt | 21 +++++ MiniKmsClient/src/app/api.service.spec.ts | 16 ++++ MiniKmsClient/src/app/api.service.ts | 26 ++++++ MiniKmsClient/src/app/app.component.css | 15 ++++ MiniKmsClient/src/app/auth/auth.guard.spec.ts | 17 ++++ MiniKmsClient/src/app/auth/auth.guard.ts | 23 +++++ .../src/app/auth/auth.service.spec.ts | 16 ++++ MiniKmsClient/src/app/auth/auth.service.ts | 74 +++++++++++++++ .../app/common/material/material.module.ts | 46 ++++++++++ .../src/app/crypto/crypto.component.html | 1 + .../src/app/crypto/crypto.component.scss | 0 .../src/app/crypto/crypto.component.spec.ts | 23 +++++ .../src/app/crypto/crypto.component.ts | 10 +++ .../src/app/login/login.component.html | 52 +++++++++++ .../src/app/login/login.component.scss | 53 +++++++++++ .../src/app/login/login.component.spec.ts | 23 +++++ .../src/app/login/login.component.ts | 90 +++++++++++++++++++ .../manage-keys/manage-keys.component.html | 1 + .../manage-keys/manage-keys.component.scss | 0 .../manage-keys/manage-keys.component.spec.ts | 23 +++++ .../app/manage-keys/manage-keys.component.ts | 10 +++ MiniKmsClient/src/environments/environment.ts | 4 + 25 files changed, 563 insertions(+), 6 deletions(-) create mode 100644 MiniKms/src/main/resources/cert.crt create mode 100644 MiniKmsClient/src/app/api.service.spec.ts create mode 100644 MiniKmsClient/src/app/api.service.ts create mode 100644 MiniKmsClient/src/app/app.component.css create mode 100644 MiniKmsClient/src/app/auth/auth.guard.spec.ts create mode 100644 MiniKmsClient/src/app/auth/auth.guard.ts create mode 100644 MiniKmsClient/src/app/auth/auth.service.spec.ts create mode 100644 MiniKmsClient/src/app/auth/auth.service.ts create mode 100644 MiniKmsClient/src/app/common/material/material.module.ts create mode 100644 MiniKmsClient/src/app/crypto/crypto.component.html create mode 100644 MiniKmsClient/src/app/crypto/crypto.component.scss create mode 100644 MiniKmsClient/src/app/crypto/crypto.component.spec.ts create mode 100644 MiniKmsClient/src/app/crypto/crypto.component.ts create mode 100644 MiniKmsClient/src/app/login/login.component.html create mode 100644 MiniKmsClient/src/app/login/login.component.scss create mode 100644 MiniKmsClient/src/app/login/login.component.spec.ts create mode 100644 MiniKmsClient/src/app/login/login.component.ts create mode 100644 MiniKmsClient/src/app/manage-keys/manage-keys.component.html create mode 100644 MiniKmsClient/src/app/manage-keys/manage-keys.component.scss create mode 100644 MiniKmsClient/src/app/manage-keys/manage-keys.component.spec.ts create mode 100644 MiniKmsClient/src/app/manage-keys/manage-keys.component.ts create mode 100644 MiniKmsClient/src/environments/environment.ts diff --git a/MiniKms/src/main/java/ftn/security/minikms/config/SecurityConfig.java b/MiniKms/src/main/java/ftn/security/minikms/config/SecurityConfig.java index 67fe541..41e4de7 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/config/SecurityConfig.java +++ b/MiniKms/src/main/java/ftn/security/minikms/config/SecurityConfig.java @@ -82,7 +82,7 @@ public CorsConfigurationSource corsConfigurationSource() { var config = new CorsConfiguration(); config.setAllowedOrigins(List.of("http://localhost:4200", "http://localhost")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); - config.setAllowedHeaders(List.of("Authorization", "Content-Type")); + config.setAllowedHeaders(List.of("*")); config.setAllowCredentials(false); var source = new UrlBasedCorsConfigurationSource(); diff --git a/MiniKms/src/main/java/ftn/security/minikms/controller/AuthController.java b/MiniKms/src/main/java/ftn/security/minikms/controller/AuthController.java index bc5d0a4..7e59723 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/controller/AuthController.java +++ b/MiniKms/src/main/java/ftn/security/minikms/controller/AuthController.java @@ -2,6 +2,7 @@ import ftn.security.minikms.dto.AuthDTO; import ftn.security.minikms.dto.TokenDTO; +import ftn.security.minikms.repository.UserRepository; import ftn.security.minikms.service.auth.JwtService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -16,11 +17,16 @@ @RestController @RequestMapping("/api/v1/auth") public class AuthController { + private final UserRepository userRepository; private final AuthenticationManager authManager; private final JwtService jwtService; @Autowired - public AuthController(AuthenticationManager authManager, JwtService jwtService) { + public AuthController( + UserRepository userRepository, + AuthenticationManager authManager, + JwtService jwtService) { + this.userRepository = userRepository; this.authManager = authManager; this.jwtService = jwtService; } @@ -28,10 +34,14 @@ public AuthController(AuthenticationManager authManager, JwtService jwtService) @PostMapping public ResponseEntity auth(@RequestBody AuthDTO dto) { try { - var auth = authManager.authenticate( + var username = authManager.authenticate( new UsernamePasswordAuthenticationToken(dto.getUsername(), dto.getPassword()) - ); - var token = jwtService.generateToken(auth.getName()); + ).getName(); + + var user = userRepository.findByUsername(username).orElseThrow(() -> + new IllegalStateException("Authenticated user not found in database")); + + var token = jwtService.generateToken(username, user.getId(), user.getRole()); return ResponseEntity.ok(new TokenDTO(token)); } catch (AuthenticationException e) { return ResponseEntity.status(401).body("Invalid credentials"); diff --git a/MiniKms/src/main/java/ftn/security/minikms/service/auth/JwtService.java b/MiniKms/src/main/java/ftn/security/minikms/service/auth/JwtService.java index 619b9c7..a3961f6 100644 --- a/MiniKms/src/main/java/ftn/security/minikms/service/auth/JwtService.java +++ b/MiniKms/src/main/java/ftn/security/minikms/service/auth/JwtService.java @@ -1,5 +1,6 @@ package ftn.security.minikms.service.auth; +import ftn.security.minikms.enumeration.UserRole; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; @@ -21,9 +22,11 @@ public JwtService( this.expirationMillis = expirationMillis; } - public String generateToken(String username) { + public String generateToken(String username, Long userId, UserRole role) { return Jwts.builder() .subject(username) + .claim("userId", userId) + .claim("role", role) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + expirationMillis)) .signWith(secretKey) diff --git a/MiniKms/src/main/resources/cert.crt b/MiniKms/src/main/resources/cert.crt new file mode 100644 index 0000000..a346f0a --- /dev/null +++ b/MiniKms/src/main/resources/cert.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZTCCAk2gAwIBAgIELtKdhjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJS +UzESMBAGA1UECBMJVm9qdm9kaW5hMREwDwYDVQQHEwhOb3ZpIFNhZDEMMAoGA1UE +ChMDZnRuMQwwCgYDVQQLEwNyYnMxETAPBgNVBAMTCE1pbmkgS01TMB4XDTI1MDkz +MDIyMzY1OVoXDTM1MDkyODIyMzY1OVowYzELMAkGA1UEBhMCUlMxEjAQBgNVBAgT +CVZvanZvZGluYTERMA8GA1UEBxMITm92aSBTYWQxDDAKBgNVBAoTA2Z0bjEMMAoG +A1UECxMDcmJzMREwDwYDVQQDEwhNaW5pIEtNUzCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOhGoOWpZed7DtrlCbpQ3AR1JbPp414KWKkejGlLhi1HkvjV +JzwSk1fRS36tBBeaU550MlrklQUsQmkHITQZzcYRRTa4GjM1+/rSidWxfI180q6A +Fn9c5bz6DpJDTedYFGYfqRj8tX5Xou2I5vMmlFXqJ93hYlLh2dWrjOXw3l+Bazgh +rnTg2hB+0kZtXhQcMsSKCee8i//m4nKXDfN4WDUl6BqqXoTO6pGBgVRtwBzwmur7 +YJzBTRkH+gQGrmFuE65+NAXRdfMvuJ/kSNlMBw32pdzIb50xkBFmZG/EjfCiFR4m +8SeEzsi3QAVwEaY/GHnPV2A7ZvxvFqN5Bx0nd0ECAwEAAaMhMB8wHQYDVR0OBBYE +FIFUNkbjYu6bIDkRz5QApRXzyHGWMA0GCSqGSIb3DQEBCwUAA4IBAQDWgrq54dy+ +ssO9Ll1iYEGr++XPWSu2/57fGdaVXz8GNyv4LgflRPyAtmbIfoFZCtf6pJxzY/io +phmCj7y95tgryyxtgmDFN+c6fhQd/zeicRUw+JlbiCKDngjmTV0ui3DTCim3tHse +6TUvqCtxGs74tbiZ5QpIugS01HBdzUvihONenNTtnl+kc+1hf7kv5atlT4ACXT+F +oaPYLc71xAlOXLLV/Js2/xGloG/4YsXniydSexm/ioAHfUBczyKPPmiQD7p4e+Db +kYyD+VozQDcGJuqJUVA+k28BhP5ZC2WFypXz7Yz8PU1XfXTDg1Urm3xGV95OzI+K +mZUoD7yw9gMX +-----END CERTIFICATE----- diff --git a/MiniKmsClient/src/app/api.service.spec.ts b/MiniKmsClient/src/app/api.service.spec.ts new file mode 100644 index 0000000..c0310ae --- /dev/null +++ b/MiniKmsClient/src/app/api.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ApiService } from './api.service'; + +describe('ApiService', () => { + let service: ApiService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ApiService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/api.service.ts b/MiniKmsClient/src/app/api.service.ts new file mode 100644 index 0000000..b5b6b2e --- /dev/null +++ b/MiniKmsClient/src/app/api.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { environment } from '../environments/environment'; + +export interface LoginRequest { + username: string; + password: string; +} + +export interface LoginResponse { + token: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class ApiService { + private baseUrl = environment.apiUrl; + + constructor(private http: HttpClient) { } + + login(credentials: LoginRequest): Observable { + return this.http.post(`${this.baseUrl}/auth`, credentials); + } +} diff --git a/MiniKmsClient/src/app/app.component.css b/MiniKmsClient/src/app/app.component.css new file mode 100644 index 0000000..7ea1284 --- /dev/null +++ b/MiniKmsClient/src/app/app.component.css @@ -0,0 +1,15 @@ +.spacer { + flex: 1 1 auto; +} + +.active-link { + background-color: rgba(255, 255, 255, 0.2); +} + +nav a, nav button { + margin-left: 8px; +} + +nav button { + color: white; +} \ No newline at end of file diff --git a/MiniKmsClient/src/app/auth/auth.guard.spec.ts b/MiniKmsClient/src/app/auth/auth.guard.spec.ts new file mode 100644 index 0000000..4ae275e --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.guard.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { CanActivateFn } from '@angular/router'; + +import { authGuard } from './auth.guard'; + +describe('authGuard', () => { + const executeGuard: CanActivateFn = (...guardParameters) => + TestBed.runInInjectionContext(() => authGuard(...guardParameters)); + + beforeEach(() => { + TestBed.configureTestingModule({}); + }); + + it('should be created', () => { + expect(executeGuard).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/auth/auth.guard.ts b/MiniKmsClient/src/app/auth/auth.guard.ts new file mode 100644 index 0000000..c94f9e8 --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.guard.ts @@ -0,0 +1,23 @@ +import { CanActivateFn, Router } from '@angular/router'; +import { inject } from '@angular/core'; +import { AuthService } from './auth.service'; + +export const authGuard: CanActivateFn = (route, state) => { + const router = inject(Router); + const authService = inject(AuthService); + + if (!authService.isAuthenticated()) { + router.navigate(['/login']); + return false; + } + + const requiredRoles = route.data?.['roles'] as string[]; + if (requiredRoles && requiredRoles.length > 0) { + if (!authService.hasAnyRole(requiredRoles)) { + router.navigate(['/login']); + return false; + } + } + + return true; +}; diff --git a/MiniKmsClient/src/app/auth/auth.service.spec.ts b/MiniKmsClient/src/app/auth/auth.service.spec.ts new file mode 100644 index 0000000..f1251ca --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthService } from './auth.service'; + +describe('AuthService', () => { + let service: AuthService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AuthService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/auth/auth.service.ts b/MiniKmsClient/src/app/auth/auth.service.ts new file mode 100644 index 0000000..02c2ae3 --- /dev/null +++ b/MiniKmsClient/src/app/auth/auth.service.ts @@ -0,0 +1,74 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; + +export interface User { + id: string; + username: string; + role: string; +} + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor(private router: Router) { } + + login(token: string): void { + localStorage.setItem('token', token); + const user = this.decodeToken(token); + if (user) { + localStorage.setItem('user', JSON.stringify(user)); + } + } + + logout(): void { + localStorage.removeItem('token'); + localStorage.removeItem('user'); + this.router.navigate(['/login']); + } + + isAuthenticated(): boolean { + const token = localStorage.getItem('token'); + if (!token) return false; + + try { + const payload = JSON.parse(atob(token.split('.')[1])); + return payload.exp * 1000 > Date.now(); + } catch { + return false; + } + } + + getToken(): string | null { + return localStorage.getItem('token'); + } + + getCurrentUser(): User | null { + const userStr = localStorage.getItem('user'); + return userStr ? JSON.parse(userStr) : null; + } + + hasRole(role: string): boolean { + const user = this.getCurrentUser(); + return user ? user.role === role : false; + } + + hasAnyRole(roles: string[]): boolean { + const user = this.getCurrentUser(); + return user ? roles.some(role => user.role === role) : false; + } + + private decodeToken(token: string): User | null { + try { + const payload = JSON.parse(atob(token.split('.')[1])); + return { + id: payload.userId, + username: payload.sub, + role: payload.role?.toLowerCase() || 'unknown' + }; + } catch { + return null; + } + } +} diff --git a/MiniKmsClient/src/app/common/material/material.module.ts b/MiniKmsClient/src/app/common/material/material.module.ts new file mode 100644 index 0000000..9a407eb --- /dev/null +++ b/MiniKmsClient/src/app/common/material/material.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; + +import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MatInputModule } from '@angular/material/input'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatTableModule } from '@angular/material/table'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatSelectModule } from '@angular/material/select'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; + +const MaterialModules = [ + MatButtonModule, + MatCardModule, + MatInputModule, + MatFormFieldModule, + MatToolbarModule, + MatIconModule, + MatDialogModule, + MatSnackBarModule, + MatProgressSpinnerModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatSelectModule, + MatCheckboxModule, + MatRadioModule, + MatDatepickerModule, + MatNativeDateModule +]; + +@NgModule({ + declarations: [], + imports: MaterialModules, + exports: MaterialModules +}) +export class MaterialModule { } diff --git a/MiniKmsClient/src/app/crypto/crypto.component.html b/MiniKmsClient/src/app/crypto/crypto.component.html new file mode 100644 index 0000000..25ec4ed --- /dev/null +++ b/MiniKmsClient/src/app/crypto/crypto.component.html @@ -0,0 +1 @@ +

crypto works!

diff --git a/MiniKmsClient/src/app/crypto/crypto.component.scss b/MiniKmsClient/src/app/crypto/crypto.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/MiniKmsClient/src/app/crypto/crypto.component.spec.ts b/MiniKmsClient/src/app/crypto/crypto.component.spec.ts new file mode 100644 index 0000000..f8a710b --- /dev/null +++ b/MiniKmsClient/src/app/crypto/crypto.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CryptoComponent } from './crypto.component'; + +describe('CryptoComponent', () => { + let component: CryptoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CryptoComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CryptoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/crypto/crypto.component.ts b/MiniKmsClient/src/app/crypto/crypto.component.ts new file mode 100644 index 0000000..026de30 --- /dev/null +++ b/MiniKmsClient/src/app/crypto/crypto.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-crypto', + templateUrl: './crypto.component.html', + styleUrl: './crypto.component.scss' +}) +export class CryptoComponent { + +} diff --git a/MiniKmsClient/src/app/login/login.component.html b/MiniKmsClient/src/app/login/login.component.html new file mode 100644 index 0000000..763b6ff --- /dev/null +++ b/MiniKmsClient/src/app/login/login.component.html @@ -0,0 +1,52 @@ + diff --git a/MiniKmsClient/src/app/login/login.component.scss b/MiniKmsClient/src/app/login/login.component.scss new file mode 100644 index 0000000..0d58328 --- /dev/null +++ b/MiniKmsClient/src/app/login/login.component.scss @@ -0,0 +1,53 @@ +.login-container { + display: flex; + justify-content: center; + align-items: center; + min-height: calc(100vh - 64px); + padding: 20px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); +} + +.login-card { + width: 100%; + max-width: 400px; + padding: 24px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); +} + +.login-form { + display: flex; + flex-direction: column; + gap: 20px; + margin-top: 16px; +} + +.full-width { + width: 100%; +} + +.login-button { + margin-top: 8px; + height: 48px; + font-size: 16px; +} + +.button-content { + display: flex; + align-items: center; + gap: 8px; +} + +mat-card-header { + text-align: center; + margin-bottom: 16px; +} + +mat-card-title { + font-size: 24px; + font-weight: 500; +} + +mat-card-subtitle { + color: #666; + margin-top: 8px; +} \ No newline at end of file diff --git a/MiniKmsClient/src/app/login/login.component.spec.ts b/MiniKmsClient/src/app/login/login.component.spec.ts new file mode 100644 index 0000000..f1c475b --- /dev/null +++ b/MiniKmsClient/src/app/login/login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LoginComponent } from './login.component'; + +describe('LoginComponent', () => { + let component: LoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [LoginComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(LoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/login/login.component.ts b/MiniKmsClient/src/app/login/login.component.ts new file mode 100644 index 0000000..e6cb2f0 --- /dev/null +++ b/MiniKmsClient/src/app/login/login.component.ts @@ -0,0 +1,90 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { AuthService } from '../auth/auth.service'; +import { ApiService, LoginRequest } from '../api.service'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +@Component({ + selector: 'app-login', + templateUrl: './login.component.html', + styleUrl: './login.component.scss' +}) +export class LoginComponent implements OnInit { + loginForm!: FormGroup; + loading = false; + hidePassword = true; + + constructor( + private fb: FormBuilder, + private authService: AuthService, + private apiService: ApiService, + private router: Router, + private snackBar: MatSnackBar + ) {} + + ngOnInit(): void { + this.loginForm = this.fb.group({ + username: ['', [Validators.required]], + password: ['', [Validators.required, Validators.minLength(3)]] + }); + } + + onSubmit(): void { + if (this.loginForm.valid) { + this.setLoadingState(true); + const credentials: LoginRequest = this.loginForm.value; + + this.apiService.login(credentials).subscribe({ + next: (response) => { + this.authService.login(response.token); + + // Navigate based on user role + const user = this.authService.getCurrentUser(); + if (user?.role === 'manager') { + this.router.navigate(['/manage-keys']); + } else { + this.router.navigate(['/crypto']); + } + + this.snackBar.open('Login successful!', 'Close', { duration: 3000 }); + this.setLoadingState(false); + }, + error: (error) => { + console.error('Login error:', error); + this.snackBar.open('Invalid credentials', 'Close', { duration: 3000 }); + this.setLoadingState(false); + } + }); + } else { + this.markFormGroupTouched(); + } + } + + private setLoadingState(loading: boolean): void { + this.loading = loading; + if (loading) { + this.loginForm.disable(); + } else { + this.loginForm.enable(); + } + } + + private markFormGroupTouched(): void { + Object.keys(this.loginForm.controls).forEach(field => { + const control = this.loginForm.get(field); + control?.markAsTouched({ onlySelf: true }); + }); + } + + getErrorMessage(field: string): string { + const control = this.loginForm.get(field); + if (control?.hasError('required')) { + return `${field.charAt(0).toUpperCase() + field.slice(1)} is required`; + } + if (control?.hasError('minlength')) { + return `${field.charAt(0).toUpperCase() + field.slice(1)} must be at least 3 characters`; + } + return ''; + } +} diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.html b/MiniKmsClient/src/app/manage-keys/manage-keys.component.html new file mode 100644 index 0000000..012e432 --- /dev/null +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.html @@ -0,0 +1 @@ +

manage-keys works!

diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss b/MiniKmsClient/src/app/manage-keys/manage-keys.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.spec.ts b/MiniKmsClient/src/app/manage-keys/manage-keys.component.spec.ts new file mode 100644 index 0000000..c79ac4d --- /dev/null +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ManageKeysComponent } from './manage-keys.component'; + +describe('ManageKeysComponent', () => { + let component: ManageKeysComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ManageKeysComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ManageKeysComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts b/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts new file mode 100644 index 0000000..f016840 --- /dev/null +++ b/MiniKmsClient/src/app/manage-keys/manage-keys.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-manage-keys', + templateUrl: './manage-keys.component.html', + styleUrl: './manage-keys.component.scss' +}) +export class ManageKeysComponent { + +} diff --git a/MiniKmsClient/src/environments/environment.ts b/MiniKmsClient/src/environments/environment.ts new file mode 100644 index 0000000..3e34ac5 --- /dev/null +++ b/MiniKmsClient/src/environments/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiUrl: 'https://localhost:8443/api/v1' +}; \ No newline at end of file