Skip to content
Open
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
41 changes: 40 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
Expand All @@ -47,7 +53,40 @@
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>

<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.coded.spring.ordering.Orders


import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class OnlineOrderController(
private val ordersService: OrdersService,

){
@GetMapping("/home")
fun onlineOrder() = "Start Ordering Food!"

@GetMapping("/orders/v1/orders")
fun getOrders(): List<Order> = ordersService.listOrders()

@PostMapping("/orders/v1/orders")
fun orderFood(@RequestBody request: CreateOrderRequest) = ordersService.createOrder(request.userId,request.restaurant)

}

data class CreateOrderRequest(
val userId: Long,
val restaurant:String,

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.coded.spring.ordering.Orders


import com.coded.spring.ordering.users.UserEntity
import jakarta.inject.Named
import jakarta.persistence.*
import org.springframework.data.jpa.repository.JpaRepository

@Named
interface OrdersRepository : JpaRepository<OrderEntity, Long>

@Entity
@Table(name = "orders")
data class OrderEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,

@ManyToOne
val user: UserEntity,
val restaurant:String,

// @OneToMany(mappedBy = "orderId")
// val items: List<ItemEntity>? = null


){
constructor() : this(null, UserEntity(),"")
}
34 changes: 34 additions & 0 deletions src/main/kotlin/com/coded/spring/ordering/Orders/OrdersService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.coded.spring.ordering.Orders



import jakarta.inject.Named
import com.coded.spring.ordering.users.UsersRepository

@Named
class OrdersService(
private val ordersRepository: OrdersRepository,
private val usersRepository: UsersRepository,
) {

fun listOrders(): List<Order> = ordersRepository.findAll().map {
Order(
userId = it.user.id,
restaurant = it.restaurant
)
}


fun createOrder(userId: Long,restaurant:String){
val user = usersRepository.findById(userId).get()
val newOrder = OrderEntity(user=user, restaurant = restaurant)
ordersRepository.save(newOrder)
}
}


data class Order(
val userId: Long?,
val restaurant: String

)
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.coded.spring.ordering.authentication


import com.coded.spring.ordering.authentication.jwt.JwtService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.security.authentication.*
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.web.bind.annotation.*


@RestController
@RequestMapping("/auth")
class AuthenticationController(
private val authenticationManager: AuthenticationManager,
private val userDetailsService: UserDetailsService,
private val jwtService: JwtService
) {

@PostMapping("/login")
fun login(@RequestBody request: AuthenticationRequest): ResponseEntity<*> {
val authToken = UsernamePasswordAuthenticationToken(request.username, request.password)
val authentication = authenticationManager.authenticate(authToken)
try {
authenticationManager.authenticate(authToken)
} catch (e: BadCredentialsException) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid username or password")
} catch (e: DisabledException) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User is disabled")
} catch (e: LockedException) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("User is locked")
}

val token = jwtService.generateToken(request.username)
return ResponseEntity.ok(AuthenticationResponse(token))
}
}

data class AuthenticationRequest(
val username: String,
val password: String
)

data class AuthenticationResponse(
val token: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.coded.spring.ordering.authentication

import com.coded.spring.ordering.users.UsersRepository
import org.springframework.security.core.userdetails.*
import org.springframework.stereotype.Service

@Service
class CustomUserDetailsService(
private val usersRepository: UsersRepository
) : 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,58 @@
package com.coded.spring.ordering.authentication


import com.coded.spring.ordering.authentication.jwt.JwtAuthenticationFilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter


@Configuration
@EnableWebSecurity
class SecurityConfig(
private val jwtAuthFilter: JwtAuthenticationFilter,
private val userDetailsService: UserDetailsService
) {

@Bean
fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
http.csrf { it.disable() }
.authorizeHttpRequests {
it.requestMatchers("/auth/**", "/users/v1/users").permitAll()
.anyRequest().authenticated()
}
.sessionManagement {
it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter::class.java)

return http.build()
}

@Bean
fun passwordEncoder(): PasswordEncoder = BCryptPasswordEncoder()

@Bean
fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =
config.authenticationManager

@Bean
fun authenticationProvider(): AuthenticationProvider {
val provider = DaoAuthenticationProvider()
provider.setUserDetailsService(userDetailsService)
provider.setPasswordEncoder(passwordEncoder())
return provider
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.coded.spring.ordering.authentication.jwt

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 ")) {
filterChain.doFilter(request, response)
return
}

val token = authHeader.substring(7)
val username = jwtService.extractUsername(token)

if (SecurityContextHolder.getContext().authentication == null) {
if (jwtService.isTokenValid(token, username)) {
val userDetails = userDetailsService.loadUserByUsername(username)
val authToken = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
SecurityContextHolder.getContext().authentication = authToken
}
}

filterChain.doFilter(request, response)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.coded.spring.ordering.authentication.jwt

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

@Component
class JwtService {

private val secretKey: SecretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256)
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
}
}
}
25 changes: 25 additions & 0 deletions src/main/kotlin/com/coded/spring/ordering/items/ItemsController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.coded.spring.ordering.items


import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
class ItemsController(
private val itemsService: ItemsService,

){


@PostMapping("/items/v1/items")
fun addItem(@RequestBody request: CreateItemRequest) = itemsService.addItem(request.orderId,request.name,request.quantity)

}

data class CreateItemRequest(
val name: String,
val quantity: Int,
val orderId: Long

)
Loading