Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a3272e5
task 1 - welcome to online ordering
Alkandari-Y Apr 7, 2025
55aed9a
task 2 - Post an order
Alkandari-Y Apr 7, 2025
e165a76
working on relations and custom queries
Alkandari-Y Apr 9, 2025
30384a9
task 3 & 4 - ordering db and menu endpoint
Alkandari-Y Apr 10, 2025
81fae15
Merge pull request #1 from Alkandari-Y/splitting
Alkandari-Y Apr 10, 2025
b7a519c
added email, password and table migration and dtos for user object
Alkandari-Y Apr 10, 2025
d94f6f5
added query to menus, added validation to some create requests, refac…
Alkandari-Y Apr 13, 2025
b590c34
added validation - needs testing
Alkandari-Y Apr 13, 2025
3f6c7a1
removed unused comments
Alkandari-Y Apr 16, 2025
163fd8f
refactored and added spring security
Alkandari-Y Apr 16, 2025
18aa76a
User Authentication Task - Excluding authentication
Alkandari-Y Apr 17, 2025
781e809
profiles
Alkandari-Y Apr 17, 2025
f44c905
edited requests and the validations
Alkandari-Y Apr 17, 2025
4da57c6
reworked regex for profile create dto validation
Alkandari-Y Apr 18, 2025
543fffc
auth - jwt at login
Alkandari-Y Apr 20, 2025
2c68525
menus endpoint open to annonymous users
Alkandari-Y Apr 20, 2025
b1da166
fixed some endpoints urls
Alkandari-Y Apr 20, 2025
05878c5
create profile for auth use
Alkandari-Y Apr 20, 2025
d3bd78e
create order by auth jwt user
Alkandari-Y Apr 20, 2025
8514071
refactor
Alkandari-Y Apr 20, 2025
633a39a
added tests for hello world with token
Alkandari-Y Apr 21, 2025
76b701b
tests create order and hello world
Alkandari-Y Apr 22, 2025
e557714
added more asserts to create order test
Alkandari-Y Apr 22, 2025
43930cf
not sure where I left of but I remember having ide issues with versio…
Alkandari-Y Apr 27, 2025
b93d69b
added logging
Alkandari-Y Apr 27, 2025
bd2b35b
swagger task
Alkandari-Y Apr 29, 2025
bf943d6
caching menu items
Alkandari-Y Apr 29, 2025
ad0c690
micro services commit push
Alkandari-Y May 1, 2025
6387584
Configuration
Alkandari-Y May 1, 2025
8677701
updated swagger for welcome message endpoint
Alkandari-Y May 1, 2025
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
58 changes: 58 additions & 0 deletions authentication/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.coded.spring</groupId>
<artifactId>YousefTech</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>authentication</artifactId>


<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.coded.spring.ordering
package com.coded.authentication

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class Application
class AuthApplication

fun main(args: Array<String>) {
runApplication<Application>(*args)
runApplication<AuthApplication>(*args)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.coded.authentication

import com.coded.authentication.users.UserEntity
import com.coded.authentication.users.UserRepository
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import org.springframework.security.crypto.password.PasswordEncoder

@SpringBootApplication
class InitUserRunner {
@Bean
fun initUsers(userRepository: com.coded.authentication.users.UserRepository, passwordEncoder: PasswordEncoder) = CommandLineRunner {
val user = com.coded.authentication.users.UserEntity(
name = "admin user",
username = "adminUser",
password = passwordEncoder.encode("password123"),
email = "adminUser@ordering.com"
)
if (userRepository.findByUsername(user.username) == null) {
println("Creating user ${user.username}")
userRepository.save(user)
} else {
println("User ${user.username} already exists")
}
}
}

//fun main(args: Array<String>) {
// runApplication<Application>(*args).close()
//}

// COMMENT to avoid multiple main function reference during compilation
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.coded.authentication.auth


import com.coded.authentication.auth.dtos.JwtResponseDto
import com.coded.authentication.auth.dtos.LoginRequestDto
import com.coded.authentication.auth.dtos.ValidateTokenResponseDto
import com.coded.authentication.users.UserService
import com.coded.authentication.users.dtos.UserCreateRequestDto
import com.coded.authentication.users.dtos.toEntity
import io.swagger.v3.oas.annotations.tags.Tag
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.*
import java.security.Principal


@Tag(name="Auth Api")
@RestController
@RequestMapping("/api/v1/auth")
class AuthApiController(
private val authenticationManager: AuthenticationManager,
private val userDetailsService: UserDetailsService,
private val jwtService: com.coded.authentication.auth.JwtService,
private val userService: com.coded.authentication.users.UserService,
private val passwordEncoder: PasswordEncoder,
) {
@Operation(summary = "User login endpoint to receive JWT token")
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "Successful login",
content = [
Content(
schema = Schema(implementation = JwtResponseDto::class),
mediaType = "application/json")
]),
ApiResponse(
responseCode = "400",
description = "Bad request",
content = [
Content(mediaType = "application/json")
]),
)
@PostMapping(path = ["/login"])
fun login(@Valid @RequestBody loginRequestDto: LoginRequestDto): ResponseEntity<*> {
val authToken = UsernamePasswordAuthenticationToken(
loginRequestDto.username,
loginRequestDto.password
)
val authenticated = authenticationManager.authenticate(authToken)

if (authenticated.isAuthenticated.not()) {
throw UsernameNotFoundException("Invalid credentials")
}

val userDetails = userDetailsService.loadUserByUsername(loginRequestDto.username)
val token = jwtService.generateToken(userDetails.username)
return ResponseEntity(JwtResponseDto(token), HttpStatus.OK)
}


@Operation(summary = "Create a new user and receive a JWT token")
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "Successful registration",
content = [
Content(
schema = Schema(implementation = JwtResponseDto::class),
mediaType = "application/json")
]),
ApiResponse(
responseCode = "400",
description = "Bad request",
content = [
Content(mediaType = "application/json")
]),
)
@PostMapping(path = ["/register"])
fun createUser(
@Valid @RequestBody user: UserCreateRequestDto
): ResponseEntity<JwtResponseDto> {
val userEntity = userService.createUser(
user.copy(
password = passwordEncoder.encode(
user.password
)
).toEntity()
)
val token = jwtService.generateToken(userEntity.username)
return ResponseEntity(JwtResponseDto(token), HttpStatus.OK)
}


@PostMapping("/check-token")
fun checkToken(principal: Principal
): ValidateTokenResponseDto {
val user = userService.findByUserName(principal.name)
?: throw UsernameNotFoundException("User not found")
return ValidateTokenResponseDto(user.id!!)
}

}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.coded.authentication.auth

import com.coded.authentication.users.UserRepository
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService(
private val usersRepository: com.coded.authentication.users.UserRepository,
) : UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails {
val user = usersRepository.findByUsername(username)
?: throw UsernameNotFoundException("User not found")

return User.builder()
.username(user.username)
.password(user.password)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.coded.authentication.auth

import jakarta.servlet.FilterChain
import jakarta.servlet.http.*
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
private val jwtService: JwtService,
private val userDetailsService: UserDetailsService
) : OncePerRequestFilter() {

override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val authHeader = request.getHeader("Authorization")
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
logger.warn("Missing or malformed Authorization header")
filterChain.doFilter(request, response)
return
}

val token = authHeader.removePrefix("Bearer ").trim()
logger.info("Received JWT: $token")

try {
val username = jwtService.extractUsername(token)
logger.info("Extracted username from token: $username")

if (SecurityContextHolder.getContext().authentication == null) {
if (jwtService.isTokenValid(token, username)) {
logger.info("Token is valid for username: $username")

val userDetails = userDetailsService.loadUserByUsername(username)
val authToken = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
SecurityContextHolder.getContext().authentication = authToken
logger.info("SecurityContext set for user: $username")
} else {
logger.warn("Token is NOT valid for username: $username")
}
}
} catch (e: Exception) {
logger.error("JWT validation failed: ${e.message}", e)
}

filterChain.doFilter(request, response)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.coded.authentication.auth

import io.jsonwebtoken.*
import io.jsonwebtoken.security.Keys
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import java.util.*
import javax.crypto.SecretKey

@Component
class JwtService (
@Value("\${jwt-secret}")
private val secretKeyString: String
){

private val secretKey: SecretKey = Keys.hmacShaKeyFor(secretKeyString.encodeToByteArray())
private val expirationMs: Long = 1000 * 60 * 60

fun generateToken(username: String): String {
val now = Date()
val expiry = Date(now.time + expirationMs)

return Jwts.builder()
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiry)
.signWith(secretKey)
.compact()
}

fun extractUsername(token: String): String =
Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.body
.subject

fun isTokenValid(token: String, username: String): Boolean {
return try {
extractUsername(token) == username
} catch (e: Exception) {
false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.coded.authentication.auth.dtos

data class JwtResponseDto(
val token: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.coded.authentication.auth.dtos

import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size


data class LoginRequestDto(
@field:NotBlank
@field:Size(min = 1, max = 50)
val username: String,
@field:NotBlank
@field:Size(min = 6, max = 50)
val password: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.coded.authentication.auth.dtos

data class ValidateTokenResponseDto (
val userId: Long
)
Loading