Skip to content

Commit

Permalink
Merge pull request #101 from gaeun5744/develop
Browse files Browse the repository at this point in the history
[REFACTOR] 로그인 및 토큰 재발급 api 연결
  • Loading branch information
gaeun5744 authored Oct 17, 2023
2 parents f21d226 + c9dcdbd commit ace0f9e
Show file tree
Hide file tree
Showing 31 changed files with 530 additions and 54 deletions.
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

0 comments on commit ace0f9e

Please sign in to comment.