Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,22 +17,31 @@
@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;
}

@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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions MiniKms/src/main/resources/cert.crt
Original file line number Diff line number Diff line change
@@ -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-----
16 changes: 16 additions & 0 deletions MiniKmsClient/src/app/api.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
26 changes: 26 additions & 0 deletions MiniKmsClient/src/app/api.service.ts
Original file line number Diff line number Diff line change
@@ -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<LoginResponse> {
return this.http.post<LoginResponse>(`${this.baseUrl}/auth`, credentials);
}
}
15 changes: 15 additions & 0 deletions MiniKmsClient/src/app/app.component.css
Original file line number Diff line number Diff line change
@@ -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;
}
17 changes: 17 additions & 0 deletions MiniKmsClient/src/app/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
23 changes: 23 additions & 0 deletions MiniKmsClient/src/app/auth/auth.guard.ts
Original file line number Diff line number Diff line change
@@ -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;
};
16 changes: 16 additions & 0 deletions MiniKmsClient/src/app/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
74 changes: 74 additions & 0 deletions MiniKmsClient/src/app/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
46 changes: 46 additions & 0 deletions MiniKmsClient/src/app/common/material/material.module.ts
Original file line number Diff line number Diff line change
@@ -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 { }
1 change: 1 addition & 0 deletions MiniKmsClient/src/app/crypto/crypto.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>crypto works!</p>
Empty file.
23 changes: 23 additions & 0 deletions MiniKmsClient/src/app/crypto/crypto.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CryptoComponent } from './crypto.component';

describe('CryptoComponent', () => {
let component: CryptoComponent;
let fixture: ComponentFixture<CryptoComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [CryptoComponent]
})
.compileComponents();

fixture = TestBed.createComponent(CryptoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
10 changes: 10 additions & 0 deletions MiniKmsClient/src/app/crypto/crypto.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-crypto',
templateUrl: './crypto.component.html',
styleUrl: './crypto.component.scss'
})
export class CryptoComponent {

}
Loading
Loading