Skip to content

Commit

Permalink
feat: Ktor implementation for auth service (#1755)
Browse files Browse the repository at this point in the history
* deps for ktor

* first iteration of Ktor auth service

* login service completed

* ktor interceptor to manage headers & tokems

* removed serialization deps from network module

* initialized User model with default values

* refactored code to pass spotless check

* revert def values in user model
  • Loading branch information
PratyushSingh07 committed Sep 3, 2024
1 parent acca07a commit af3384d
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import org.mifospay.core.data.base.UseCaseThreadPoolScheduler
import org.mifospay.core.data.fineract.repository.FineractRepository
import org.mifospay.core.network.FineractApiManager
import org.mifospay.core.network.SelfServiceApiManager
import org.mifospay.core.network.services.KtorAuthenticationService

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -36,7 +37,12 @@ class DataModule {
fun providesFineractRepository(
fineractApiManager: FineractApiManager,
selfServiceApiManager: SelfServiceApiManager,
ktorAuthenticationService: KtorAuthenticationService,
): FineractRepository {
return FineractRepository(fineractApiManager, selfServiceApiManager)
return FineractRepository(
fineractApiManager,
selfServiceApiManager,
ktorAuthenticationService,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,34 @@ package org.mifospay.core.data.domain.usecase.user

import com.mifospay.core.model.domain.user.User
import com.mifospay.core.model.entity.authentication.AuthenticationPayload
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mifospay.core.data.base.UseCase
import org.mifospay.core.data.fineract.repository.FineractRepository
import org.mifospay.core.data.util.Constants
import rx.Subscriber
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import javax.inject.Inject

class AuthenticateUser @Inject constructor(
private val apiRepository: FineractRepository,
) : UseCase<AuthenticateUser.RequestValues, AuthenticateUser.ResponseValue>() {

override fun executeUseCase(requestValues: RequestValues) {
apiRepository
.loginSelf(AuthenticationPayload(requestValues.username, requestValues.password))
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(object : Subscriber<User>() {
override fun onCompleted() {}
override fun onError(e: Throwable) {
useCaseCallback.onError(Constants.ERROR_LOGGING_IN)
}

override fun onNext(user: User) {
CoroutineScope(Dispatchers.IO).launch {
try {
val user = apiRepository.loginSelf(
AuthenticationPayload(requestValues.username, requestValues.password),
)
withContext(Dispatchers.Main) {
useCaseCallback.onSuccess(ResponseValue(user))
}
})
} catch (e: Exception) {
withContext(Dispatchers.Main) {
useCaseCallback.onError(Constants.ERROR_LOGGING_IN)
}
}
}
}

data class RequestValues(val username: String, val password: String) : UseCase.RequestValues
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import org.mifospay.core.data.util.Constants
import org.mifospay.core.network.FineractApiManager
import org.mifospay.core.network.GenericResponse
import org.mifospay.core.network.SelfServiceApiManager
import org.mifospay.core.network.services.KtorAuthenticationService
import rx.Observable
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -55,6 +56,7 @@ import javax.inject.Singleton
class FineractRepository @Inject constructor(
private val fineractApiManager: FineractApiManager,
private val selfApiManager: SelfServiceApiManager,
private val ktorAuthenticationService: KtorAuthenticationService,
) {
fun createClient(newClient: NewClient): Observable<CreateClient.ResponseValue> {
return fineractApiManager.clientsApi.createClient(newClient)
Expand Down Expand Up @@ -273,8 +275,8 @@ class FineractRepository @Inject constructor(
}

// self user apis
fun loginSelf(payload: AuthenticationPayload): Observable<User> {
return selfApiManager.authenticationApi.authenticate(payload)
suspend fun loginSelf(payload: AuthenticationPayload): User {
return ktorAuthenticationService.authenticate(payload)
}

fun getSelfClientDetails(clientId: Long): Observable<Client> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@
*/
package com.mifospay.core.model.domain.user

import android.os.Parcelable
import com.mifospay.core.model.entity.Role
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
@Serializable
data class User(
val username: String,
val userId: Long = 0,
Expand All @@ -23,7 +22,7 @@ data class User(
val officeName: String,
val roles: List<Role>,
val permissions: List<String>,
val clients: List<Long>,
val clients: List<Long> = emptyList(),
val shouldRenewPassword: Boolean,
val isTwoFactorAuthenticationRequired: Boolean,
) : Parcelable
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ package com.mifospay.core.model.entity

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
@Serializable
data class Role(
var id: String? = null,
var name: String? = null,
var description: String? = null,
val disabled: Boolean,
) : Parcelable
)
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ package com.mifospay.core.model.entity

import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
@Serializable
data class UserWithRole(
var id: String? = null,
var username: String? = null,
var firstname: String? = null,
var lastname: String? = null,
var email: String? = null,
var selectedRoles: List<Role>? = ArrayList(),
) : Parcelable
)
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,21 @@ package com.mifospay.core.model.entity.authentication
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Parcelize
class AuthenticationPayload(
//@Parcelize
//class AuthenticationPayload(
// @SerializedName("username")
// val userName: String,
// @SerializedName("password")
// val password: String,
//) : Parcelable


@Serializable
data class AuthenticationPayload(
@SerializedName("username")
val userName: String,
val username: String,
@SerializedName("password")
val password: String,
) : Parcelable
val password: String
)
15 changes: 13 additions & 2 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.android.build.api.dsl.Packaging

/*
* Copyright 2024 Mifos Initiative
*
Expand Down Expand Up @@ -31,8 +33,6 @@ dependencies {
api(projects.core.model)
api(projects.core.datastore)

implementation(libs.kotlinx.serialization.json)

implementation(libs.squareup.okhttp)
implementation(libs.squareup.logging.interceptor)

Expand All @@ -44,5 +44,16 @@ dependencies {
implementation(libs.reactivex.rxjava.android)
implementation(libs.reactivex.rxjava)

implementation(libs.jetbrains.kotlin.stdlib)
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.android)
implementation(libs.ktor.client.serialization)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.json)
implementation(libs.ktor.client.websockets)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.logback.classic)

testImplementation(libs.kotlinx.coroutines.test)
}
5 changes: 4 additions & 1 deletion core/network/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

-keep class io.ktor.** { *; }
-keep class kotlinx.serialization.** { *; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.mifospay.core.network

import io.ktor.client.HttpClient
import io.ktor.client.plugins.HttpClientPlugin
import io.ktor.client.request.HttpRequestPipeline
import io.ktor.client.request.header
import io.ktor.util.AttributeKey
import org.mifospay.core.datastore.PreferencesHelper

class KtorInterceptor(
private val preferencesHelper: PreferencesHelper
) {
class Config {
lateinit var preferencesHelper: PreferencesHelper
}

companion object Plugin : HttpClientPlugin<Config, KtorInterceptor> {
const val HEADER_TENANT = "Fineract-Platform-TenantId"
const val HEADER_AUTH = "Authorization"
const val DEFAULT = "venus"

override val key: AttributeKey<KtorInterceptor> = AttributeKey("KtorInterceptor")

override fun install(plugin: KtorInterceptor, scope: HttpClient) {
scope.requestPipeline.intercept(HttpRequestPipeline.State) {
val authToken = plugin.preferencesHelper.token
context.header(HEADER_TENANT, DEFAULT)
if (!authToken.isNullOrEmpty()) {
context.header(HEADER_AUTH, authToken)
}
}
}

override fun prepare(block: Config.() -> Unit): KtorInterceptor {
val config = Config().apply(block)
return KtorInterceptor(config.preferencesHelper)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,19 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import org.mifospay.core.datastore.PreferencesHelper
import org.mifospay.core.network.BaseURL
import org.mifospay.core.network.FineractApiManager
import org.mifospay.core.network.KtorInterceptor
import org.mifospay.core.network.MifosWalletOkHttpClient
import org.mifospay.core.network.SelfServiceApiManager
import org.mifospay.core.network.localAssets.LocalAssetManager
Expand All @@ -29,6 +38,7 @@ import org.mifospay.core.network.services.ClientService
import org.mifospay.core.network.services.DocumentService
import org.mifospay.core.network.services.InvoiceService
import org.mifospay.core.network.services.KYCLevel1Service
import org.mifospay.core.network.services.KtorAuthenticationService
import org.mifospay.core.network.services.NotificationService
import org.mifospay.core.network.services.RegistrationService
import org.mifospay.core.network.services.RunReportService
Expand Down Expand Up @@ -146,6 +156,37 @@ class NetworkModule {
)
}

@Provides
@Singleton
fun provideHttpClient(preferencesHelper: PreferencesHelper): HttpClient {
return HttpClient(Android) {
install(WebSockets)
install(KtorInterceptor) {
this.preferencesHelper = preferencesHelper
}
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
isLenient = true
},
)
}
install(HttpTimeout) {
requestTimeoutMillis = 15000
}
install(Logging) {
level = LogLevel.ALL
}
}
}

@Provides
@Singleton
fun provideAuthenticationService(client: HttpClient): KtorAuthenticationService {
return KtorAuthenticationService(client)
}

// -----Fineract API Service---------//

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.mifospay.core.network.services

import com.mifospay.core.model.domain.user.User
import com.mifospay.core.model.entity.authentication.AuthenticationPayload
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import org.mifospay.core.network.BaseURL
import javax.inject.Inject

class KtorAuthenticationService @Inject constructor(
private val client: HttpClient,
) {

suspend fun authenticate(authPayload: AuthenticationPayload): User {
return client.post("${BaseURL().selfServiceUrl}authentication") {
contentType(ContentType.Application.Json)
setBody(authPayload)
}.body()
}
}
Loading

0 comments on commit af3384d

Please sign in to comment.