Skip to content

Commit

Permalink
✨ Sign Up and Sign In
Browse files Browse the repository at this point in the history
  • Loading branch information
alpakar02 committed Jan 2, 2025
1 parent c2596ec commit 39f4852
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 88 deletions.
100 changes: 36 additions & 64 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ permissions:
contents: read

jobs:
CI-CD:
CI:
runs-on: ubuntu-latest
steps:

Expand All @@ -29,89 +29,61 @@ jobs:
java-version: '17'
distribution: 'temurin'

# gradlew에 실행 권한 부여
- name: Grant execute permission for gradlew
run: chmod +x gradlew

# 린트 체크 실행
- name: Lint Check
run: ./gradlew ktlintCheck

# 환경별 yml 파일 생성(1) - application.yml
- name: make application.yml
if: |
contains(github.ref, 'main') ||
contains(github.ref, 'feat/*')
run: |
mkdir ./src/main/resources # resources 폴더 생성
cd ./src/main/resources # resources 폴더로 이동
touch ./application.yml # application.yml 생성
echo "${{ secrets.YML }}" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
shell: bash

# 환경별 yml 파일 생성(2) - dev
- name: make application-dev.yml
if: contains(github.ref, 'feat/*')
run: |
cd ./src/main/resources
touch ./application-dev.yml
echo "${{ secrets.YML_DEV }}" > ./application-dev.yml
shell: bash

# 환경별 yml 파일 생성(3) - prod
- name: make application-prod.yml
if: contains(github.ref, 'main')
run: |
cd ./src/main/resources
touch ./application-prod.yml
echo "${{ secrets.YML_PROD }}" > ./application-prod.yml
shell: bash

# gradle build
- name: Build with Gradle
run: ./gradlew build -x test
run: ./gradlew build

# docker build & push to production
- name: Docker build & push to prod
# Docker 로그인
- name: Docker login
if: contains(github.ref, 'main')
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile-dev -t ${{ secrets.DOCKER_USERNAME }}/docker-test-prod .
docker push ${{ secrets.DOCKER_USERNAME }}/docker-test-prod
# docker build & push to develop
- name: Docker build & push to dev
if: contains(github.ref, 'feat/*')
# Docker 이미지 빌드 및 푸시
- name: Docker build & push
if: contains(github.ref, 'main')
run: |
docker build -f ./Dockerfile -t ${{ secrets.DOCKER_USERNAME }}/karrot .
docker push ${{ secrets.DOCKER_USERNAME }}/karrot
# AccessToken 발급시
- name: Run Application with JWT Secret Key
env:
JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -f Dockerfile-dev -t ${{ secrets.DOCKER_USERNAME }}/docker-test-dev .
docker push ${{ secrets.DOCKER_USERNAME }}/docker-test-dev
echo "Starting application with JWT Secret Key..."
./gradlew run
## deploy to production
CD:
runs-on: ubuntu-latest
needs: CI
if: contains(github.ref, 'main')
steps:

# 다시 checkout
- name: Checkout again
uses: actions/checkout@v3

# EC2에 배포
- name: Deploy to prod
uses: appleboy/ssh-action@master
id: deploy-prod
if: contains(github.ref, 'main')
with:
host: ${{ secrets.HOST_PROD }} # EC2 퍼블릭 IPv4 DNS
username: ubuntu
key: ${{ secrets.PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
sudo docker ps
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/docker-test-prod
sudo docker run -d -p 8082:8082 ${{ secrets.DOCKER_USERNAME }}/docker-test-prod
sudo docker image prune -f
## deploy to develop
- name: Deploy to dev
uses: appleboy/ssh-action@master
id: deploy-dev
if: contains(github.ref, 'develop')
with:
host: ${{ secrets.HOST_DEV }} # EC2 퍼블릭 IPv4 DNS
username: ${{ secrets.USERNAME }} # ubuntu
password: ${{ secrets.PASSWORD }}
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script: |
sudo docker ps
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/docker-test-dev
sudo docker run -d -p 8081:8081 ${{ secrets.DOCKER_USERNAME }}/docker-test-dev
sudo docker stop $(sudo docker ps -q)
sudo docker rm $(sudo docker ps -aq)
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/karrot
sudo docker run -d -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/karrot
sudo docker image prune -f
17 changes: 4 additions & 13 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
# Use a base image for running the application
FROM openjdk:17-jdk-slim

# Set the working directory in the container
WORKDIR /app

# Copy the pre-built JAR file into the container
COPY /app/build/libs/app-0.0.1-SNAPSHOT.jar app.jar

# Expose port 8080 for the Spring application
FROM openjdk:17
ARG JAR_FILE=build/libs/karrot-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} /app.jar
EXPOSE 8080

# Specify the entry point for the application
ENTRYPOINT ["java", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "/app.jar"]
17 changes: 17 additions & 0 deletions src/main/kotlin/com/toyProject7/karrot/DomainException.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.toyProject7.karrot

import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode

open class DomainException(
// client 와 약속된 Application Error 에 대한 코드 필요 시 Enum 으로 관리하자.
val errorCode: Int,
// HTTP Status Code, 비어있다면 500 이다.
val httpErrorCode: HttpStatusCode = HttpStatus.INTERNAL_SERVER_ERROR,
val msg: String,
cause: Throwable? = null,
) : RuntimeException(msg, cause) {
override fun toString(): String {
return "DomainException(msg='$msg', errorCode=$errorCode, httpErrorCode=$httpErrorCode)"
}
}
5 changes: 3 additions & 2 deletions src/main/kotlin/com/toyProject7/karrot/user/AuthUser.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.toyProject7.karrot.user

class AuthUser {
}
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthUser
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
package com.toyProject7.karrot.user

class UserAccessTokenUtil {
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import java.nio.charset.StandardCharsets
import java.util.Date

object UserAccessTokenUtil {
private val SECRET_KEY = System.getenv("JWT_SECRET_KEY")
?.let { Keys.hmacShaKeyFor(it.toByteArray(StandardCharsets.UTF_8)) }
?: throw IllegalStateException("JWT_SECRET_KEY is not set!")

private const val JWT_EXPIRATION_TIME = 1000 * 60 * 60 * 2 // 2 hours

fun generateAccessToken(username: String): String {
val now = Date()
val expiryDate = Date(now.time + JWT_EXPIRATION_TIME)
return Jwts.builder()
.signWith(SECRET_KEY)
.setSubject(username)
.setIssuedAt(now)
.setExpiration(expiryDate)
.compact()
}

fun validateAccessTokenGetUserId(accessToken: String): String? {
return try {
val claims =
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(accessToken)
.body
if (claims.expiration.before(Date())) null else claims.subject
} catch (e: Exception) {
println("Token validation failed. Please try again.")
null
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@
package com.toyProject7.karrot.user

class UserArgumentResolver {
import com.toyProject7.karrot.user.controller.User
import com.toyProject7.karrot.user.service.UserService
import org.springframework.core.MethodParameter
import org.springframework.stereotype.Component
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.method.support.ModelAndViewContainer

@Component
class UserArgumentResolver(
private val userService: UserService,
) : HandlerMethodArgumentResolver {
override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.parameterType == User::class.java
}

override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?,
): User? {
return runCatching {
val accessToken =
requireNotNull(
webRequest.getHeader("Authorization")?.split(" ")?.let {
if (it.getOrNull(0) == "Bearer") it.getOrNull(1) else null
},
)
userService.authenticate(accessToken)
}.getOrElse {
if (parameter.hasParameterAnnotation(AuthUser::class.java)) {
throw AuthenticateException()
} else {
null
}
}
}
}
54 changes: 52 additions & 2 deletions src/main/kotlin/com/toyProject7/karrot/user/UserException.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,54 @@
package com.toyProject7.karrot.user

class UserException {
}
import com.toyProject7.karrot.DomainException
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatusCode

sealed class UserException(
errorCode: Int,
httpStatusCode: HttpStatusCode,
msg: String,
cause: Throwable? = null,
) : DomainException(errorCode, httpStatusCode, msg, cause)

class SignUpUserIdConflictException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.CONFLICT,
msg = "Username conflict",
)

class SignUpNicknameConflictException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.CONFLICT,
msg = "Nickname conflict",
)

class SignUpBadUserIdException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.BAD_REQUEST,
msg = "Bad userId, User ID must be 5-20 characters",
)

class SignUpBadPasswordException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.BAD_REQUEST,
msg = "Bad password, password must be 8-16 characters",
)

class SignInUserNotFoundException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.UNAUTHORIZED,
msg = "User not found",
)

class SignInInvalidPasswordException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.UNAUTHORIZED,
msg = "Invalid password",
)

class AuthenticateException : UserException(
errorCode = 0,
httpStatusCode = HttpStatus.UNAUTHORIZED,
msg = "Authenticate failed",
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ data class User(
val userId: String,
val location: String,
val temperature: Double,
val email: String,
) {
companion object {
fun fromEntity(entity: UserEntity): User {
Expand All @@ -17,6 +18,7 @@ data class User(
userId = entity.userId,
location = entity.location,
temperature = entity.temperature,
email = entity.email,
)
}
}
Expand Down
Loading

0 comments on commit 39f4852

Please sign in to comment.