Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] 로그인 및 토큰 재발급 api 연결 #101

Merged
merged 31 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ea3cafa
[FIX/#100] Entity 수정
gaeun5744 Sep 29, 2023
2bc0030
[ADD/#100] Dto 추가
gaeun5744 Sep 29, 2023
505b161
[ADD/#100] Service 추가
gaeun5744 Sep 29, 2023
91531fb
[ADD/#100] 로그인 관련 data Entity 추가
gaeun5744 Sep 30, 2023
babeea7
[DELETE/#100] 불필요한 파일 제거
gaeun5744 Oct 1, 2023
620e005
[FEAT/#100] Auth 데이터 레이어 구현
gaeun5744 Oct 1, 2023
309f45a
[RENAME/#100] 함수 이름 변경
gaeun5744 Oct 1, 2023
f078655
[RENAME/#100] api DI 추가
gaeun5744 Oct 1, 2023
d10e5a6
[FIX/#100] 오타 수정
gaeun5744 Oct 2, 2023
ff03ad6
[FEAT/#100] 함수 DI로 주입 구현
gaeun5744 Oct 2, 2023
6e77f9a
[FIX/#100] 긴 message도 암호화 할 수 있도록 수정
gaeun5744 Oct 3, 2023
6afa25d
[ADD/#100] Dto 추가 및 수정
gaeun5744 Oct 3, 2023
0abffce
[FIX/#100] 추가한 Dto에 맞춰 코드 수정
gaeun5744 Oct 3, 2023
c176695
[FEAT/#100] 로그인 api 구현
gaeun5744 Oct 4, 2023
2997fad
[FIX/#100] 로그 메시지 수정
gaeun5744 Oct 4, 2023
0459b42
[RENAME/#100] parameter name 명확히 수정
gaeun5744 Oct 4, 2023
751275b
[FEAT/#100] data class 값 초기화 구현
gaeun5744 Oct 5, 2023
21f8d4b
[FEAT/#100] UserDataSource 구현
gaeun5744 Oct 5, 2023
dead02e
[FEAT/#100] UserRepo 구현
gaeun5744 Oct 7, 2023
8b45a79
[RENAME/#40] useCase 파라미터명 수정
gaeun5744 Oct 8, 2023
545f99d
[FIX/#100] UserDataStore object -> class 수정
gaeun5744 Oct 8, 2023
eb4eb0c
[DELETE/#100] 불필요한 함수 제거
gaeun5744 Oct 8, 2023
09d9b51
[FIX/#100] 데이터 저장 파일 이름 수정
gaeun5744 Oct 10, 2023
e598b9b
[FEAT/#100] 로그인시 유저 정보 저장 구현
gaeun5744 Oct 10, 2023
ace4691
[FEAT/#100] userInfo 여부에 따른 자동 로그인 구현
gaeun5744 Oct 10, 2023
b65dc6d
[FEAT/#100] 토큰 재발급 로직 구현
gaeun5744 Oct 12, 2023
0f6344a
[ADD/#100] 토큰 재발급 interceptor를 위한 annotation 추가
gaeun5744 Oct 13, 2023
41e8a1f
[FIX/#100] 토큰 재발급을 위한 service 분리
gaeun5744 Oct 14, 2023
4a18115
[ADD/#100] 토큰 재발급 Retrofit 추가
gaeun5744 Oct 14, 2023
651e6bb
[FEAT/#100] 토큰 재발급 기능 구현
gaeun5744 Oct 15, 2023
c9dcdbd
Merge pull request #17 from gaeun5744/refactor/#100-login-api
gaeun5744 Oct 15, 2023
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
@@ -0,0 +1,39 @@
package com.puzzling.puzzlingaos.data.datasource.local

import androidx.datastore.core.Serializer
import com.puzzling.puzzlingaos.data.entity.User
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import org.apache.commons.lang3.SerializationException
import java.io.InputStream
import java.io.OutputStream
import javax.inject.Inject

class UserDataSource @Inject constructor() : Serializer<User> {
override val defaultValue: User
get() = User()

override suspend fun readFrom(input: InputStream): User {
return try {
Json.decodeFromString(
deserializer = User.serializer(),
string = input.readBytes().decodeToString(),
)
} catch (e: SerializationException) {
e.printStackTrace()
defaultValue
}
}

override suspend fun writeTo(t: User, output: OutputStream) {
withContext(Dispatchers.IO) {
output.write(
Json.encodeToString(
serializer = User.serializer(),
value = t,
).encodeToByteArray(),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.puzzling.puzzlingaos.data.datasource.remote

import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto
import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto

interface AuthDataSource {
suspend fun login(socialPlatform: String): ResponseLoginDto

suspend fun getToken(): ResponseTokenDto
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.puzzling.puzzlingaos.data.datasource.remote.impl

import com.puzzling.puzzlingaos.data.datasource.remote.AuthDataSource
import com.puzzling.puzzlingaos.data.model.request.RequestLoginDto
import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto
import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto
import com.puzzling.puzzlingaos.data.service.AuthService
import com.puzzling.puzzlingaos.data.service.ReIssueTokenService
import javax.inject.Inject

class AuthDataSourceImpl @Inject constructor(
private val authService: AuthService,
private val reIssueTokenService: ReIssueTokenService,
) : AuthDataSource {

override suspend fun login(socialPlatform: String): ResponseLoginDto =
authService.login(RequestLoginDto(socialPlatform))

override suspend fun getToken(): ResponseTokenDto =
reIssueTokenService.getToken()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.puzzling.puzzlingaos.data.entity

import kotlinx.serialization.Serializable

@Serializable
data class CurrentProject(
val projectId: Int,
val projectName: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class Token(
val accessToken: String? = null,
val refreshToken: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.puzzling.puzzlingaos.data.entity

import kotlinx.serialization.Serializable

@Serializable
data class User(
val memberId: Int? = null,
val name: String? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.puzzling.puzzlingaos.data.model.request

import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable

@Serializable
data class RequestLoginDto(
@SerializedName("socialPlatform")
val socialPlatform: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.puzzling.puzzlingaos.data.model.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseLoginDto(
@SerialName("status")
val status: Int,
@SerialName("success")
val success: Boolean,
@SerialName("message")
val message: String,
@SerialName("data")
val data: LoginData,

) {
@Serializable
data class LoginData(
@SerialName("name")
val name: String,
@SerialName("memberId")
val memberId: Int,
@SerialName("projectId")
val projectId: Int?,
@SerialName("projectName")
val projectName: String?,
@SerialName("accessToken")
val accessToken: String,
@SerialName("refreshToken")
val refreshToken: String,
@SerialName("isNewUser")
val isNewUser: Boolean,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.puzzling.puzzlingaos.data.model.response

import com.puzzling.puzzlingaos.data.entity.Token
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ResponseTokenDto(
@SerialName("status")
val status: Int,
@SerialName("success")
val success: Boolean,
@SerialName("message")
val message: String,
@SerialName("date")
val data: NewToken?,
) {
@Serializable
data class NewToken(
@SerialName("accessToken")
val accessToken: String,

)

fun getToken() = Token(data?.accessToken)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.puzzling.puzzlingaos.data.repository

import android.util.Log
import androidx.datastore.core.DataStore
import com.puzzling.puzzlingaos.data.datasource.remote.AuthDataSource
import com.puzzling.puzzlingaos.data.entity.Token
import com.puzzling.puzzlingaos.data.entity.User
import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto
import com.puzzling.puzzlingaos.domain.repository.AuthRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class AuthRepositoryImpl @Inject constructor(
private val tokenDataStore: DataStore<Token>,
private val userDataSource: DataStore<User>,
private val authDataSource: AuthDataSource,
) : AuthRepository {
override suspend fun login(socialPlatform: String): Result<ResponseLoginDto> = runCatching {
authDataSource.login(socialPlatform)
}.onSuccess { result ->
tokenDataStore.updateData { Token(result.data.accessToken, result.data.refreshToken) }
userDataSource.updateData { User(result.data.memberId, result.data.name) }
Log.d("AuthRepoImpl", "$result")
Log.d("AuthRepoImpl", "AuthRepoImpl get 토큰 ${userDataSource.data.first()}")
}.onFailure {
Log.d("AuthRepoImpl", "$it 에러")
}

override suspend fun getToken(): Result<Token> = runCatching {
authDataSource.getToken().getToken()
}.onSuccess { token ->
tokenDataStore.updateData { Token(token.accessToken, token.refreshToken) }
Log.d("AuthRepoImpl", "${tokenDataStore.data.first()}")
}.onFailure {
Log.d("AuthRepoImpl", "$it 에러")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import javax.inject.Inject

class TokenRepositoryImpl @Inject constructor(private val dataStore: DataStore<Token>) :
TokenRepository {
override suspend fun setToken(token: String) {
dataStore.updateData { Token(token) }
override suspend fun setToken(accessToken: String) {
dataStore.updateData { Token(accessToken) }
}

override suspend fun getToken(): Token = dataStore.data.first()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.puzzling.puzzlingaos.data.repository

import androidx.datastore.core.DataStore
import com.puzzling.puzzlingaos.data.entity.User
import com.puzzling.puzzlingaos.domain.repository.UserRepository
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class UserRepositoryImpl @Inject constructor(private val dataStore: DataStore<User>) :
UserRepository {
override suspend fun setUserInfo(userInfo: User) {
dataStore.updateData { User(userInfo.memberId, userInfo.name) }
}

override suspend fun getUserInfo(): User = dataStore.data.first()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.puzzling.puzzlingaos.data.service

import com.puzzling.puzzlingaos.data.model.request.RequestLoginDto
import com.puzzling.puzzlingaos.data.model.response.ResponseLoginDto
import retrofit2.http.Body
import retrofit2.http.POST

interface AuthService {
@POST("api/v1/auth")
suspend fun login(
@Body socialPlatform: RequestLoginDto,
): ResponseLoginDto
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.os.Build
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import androidx.annotation.RequiresApi
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.security.KeyStore
Expand All @@ -20,62 +22,90 @@ class CryptoService @Inject constructor() {
load(null)
}

private val encryptCipher = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.ENCRYPT_MODE, getKey())
}
private val encryptCipher
get() = Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.ENCRYPT_MODE, getKey())
}

private fun getDecryptCipher(iv: ByteArray): Cipher {
private fun getDecryptCipherForIv(iv: ByteArray): Cipher {
return Cipher.getInstance(TRANSFORMATION).apply {
init(Cipher.DECRYPT_MODE, getKey(), IvParameterSpec(iv))
}
}

private fun getKey(): SecretKey {
val existingKey = keyStore.getEntry("secret", null) as? KeyStore.SecretKeyEntry
val existingKey = keyStore.getEntry(ALIAS, null) as? KeyStore.SecretKeyEntry
return existingKey?.secretKey ?: createKey()
}

private fun createKey(): SecretKey {
return KeyGenerator.getInstance(ALGORITHM).apply {
init(
KeyGenParameterSpec.Builder(
"secret",
ALIAS,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
).setBlockModes(BLOCK_MODE).setEncryptionPaddings(
PADDING,
).setUserAuthenticationRequired(false) // 지문인식 같은거 false
.setRandomizedEncryptionRequired(true).build(),
)
.setKeySize(KEY_SIZE * 8) // key size in bits
.setBlockModes(BLOCK_MODE)
.setEncryptionPaddings(PADDING)
.setUserAuthenticationRequired(false)
.setRandomizedEncryptionRequired(true)
.build(),
)
}.generateKey()
}

fun encrypt(bytes: ByteArray, outputStream: OutputStream): ByteArray {
val encryptedBytes = encryptCipher.doFinal(bytes)
fun encrypt(bytes: ByteArray, outputStream: OutputStream) {
val cipher = encryptCipher
val iv = cipher.iv
outputStream.use {
it.write(encryptCipher.iv.size)
it.write(encryptCipher.iv)
it.write(encryptedBytes.size)
it.write(encryptedBytes)
it.write(iv)
// write the payload in chunks to make sure to support larger data amounts (this would otherwise fail silently and result in corrupted data being read back)
// //////////////////////////////////
val inputStream = ByteArrayInputStream(bytes)
val buffer = ByteArray(CHUNK_SIZE)
while (inputStream.available() > CHUNK_SIZE) {
inputStream.read(buffer)
val ciphertextChunk = cipher.update(buffer)
it.write(ciphertextChunk)
}
// the last chunk must be written using doFinal() because this takes the padding into account
val remainingBytes = inputStream.readBytes()
val lastChunk = cipher.doFinal(remainingBytes)
it.write(lastChunk)
// ////////////////////////////////
}

return encryptedBytes
}

fun decrypt(inputStream: InputStream): ByteArray {
return inputStream.use {
val ivSize = it.read()
val iv = ByteArray(ivSize)
val iv = ByteArray(KEY_SIZE)
it.read(iv)
val cipher = getDecryptCipherForIv(iv)
val outputStream = ByteArrayOutputStream()

val encryptedBytesSize = it.read()
val encryptedBytes = ByteArray(encryptedBytesSize)
it.read(encryptedBytes)
// read the payload in chunks to make sure to support larger data amounts (this would otherwise fail silently and result in corrupted data being read back)
// //////////////////////////////////
val buffer = ByteArray(CHUNK_SIZE)
while (inputStream.available() > CHUNK_SIZE) {
inputStream.read(buffer)
val ciphertextChunk = cipher.update(buffer)
outputStream.write(ciphertextChunk)
}
// the last chunk must be read using doFinal() because this takes the padding into account
val remainingBytes = inputStream.readBytes()
val lastChunk = cipher.doFinal(remainingBytes)
outputStream.write(lastChunk)
// ////////////////////////////////

getDecryptCipher(iv).doFinal(encryptedBytes)
outputStream.toByteArray()
}
}

companion object {
private const val CHUNK_SIZE = 1024 * 4 // bytes
private const val KEY_SIZE = 16 // bytes
private const val ALIAS = "my_alias"
private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.puzzling.puzzlingaos.data.service

import com.puzzling.puzzlingaos.data.model.response.ResponseTokenDto
import retrofit2.http.GET

interface ReIssueTokenService {
@GET("api/v1/auth/token")
suspend fun getToken(): ResponseTokenDto
}
Loading
Loading