Implement updated authentication

This commit is contained in:
Billy Brawner 2019-10-31 09:40:54 -06:00
parent 6f24feed7b
commit 74601bc18d
21 changed files with 64 additions and 73 deletions

View file

@ -20,8 +20,9 @@ android {
useSupportLibrary = true
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField "String", "API_URL", "\"http://192.168.86.92:8080/\""
// buildConfigField "String", "API_URL", "\"http://192.168.86.92:8080/\""
// buildConfigField "String", "API_URL", "\"http://10.0.2.2:8080/\""
buildConfigField "String", "API_URL", "\"https://budget-api.intra.wbrawner.com/\""
}
buildTypes {
release {
@ -38,7 +39,7 @@ android {
dependencies {
implementation project(':common')
implementation project(':budgetlib')
implementation project(':auth')
implementation project(':storage')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

View file

@ -1,8 +1,8 @@
package com.wbrawner.budget.di
import android.content.Context
import com.wbrawner.budget.auth.AuthModule
import com.wbrawner.budget.lib.network.NetworkModule
import com.wbrawner.budget.storage.StorageModule
import com.wbrawner.budget.ui.MainActivity
import com.wbrawner.budget.ui.SplashActivity
import com.wbrawner.budget.ui.SplashViewModelMapper
@ -29,7 +29,7 @@ import javax.inject.Named
import javax.inject.Singleton
@Singleton
@Component(modules = [AuthModule::class, AppModule::class, NetworkModule::class])
@Component(modules = [StorageModule::class, AppModule::class, NetworkModule::class])
interface AppComponent {
fun inject(fragment: OverviewFragment)
fun inject(fragment: LoginFragment)

View file

@ -2,9 +2,8 @@ package com.wbrawner.budget.ui
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.wbrawner.budget.auth.CredentialsProvider
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.common.user.User
import com.wbrawner.budget.common.user.UserRepository
import com.wbrawner.budget.di.ViewModelKey
import dagger.Binds
import dagger.Module
@ -12,14 +11,13 @@ import dagger.multibindings.IntoMap
import javax.inject.Inject
class SplashViewModel @Inject constructor(
private val credentialsProvider: CredentialsProvider,
private val budgetRepository: BudgetRepository
private val userRepository: UserRepository
) : ViewModel() {
val isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
suspend fun checkForExistingCredentials(): User? {
return try {
login(credentialsProvider.username, credentialsProvider.password)
userRepository.getProfile()
} catch (ignored: Exception) {
null
}
@ -27,9 +25,8 @@ class SplashViewModel @Inject constructor(
suspend fun login(username: String, password: String): User {
isLoading.value = true
credentialsProvider.saveCredentials(username, password)
return try {
budgetRepository.login(username, password)
userRepository.login(username, password)
} catch (e: java.lang.Exception) {
isLoading.value = false
throw e

View file

@ -1,25 +0,0 @@
package com.wbrawner.budget.auth
import android.content.SharedPreferences
interface CredentialsProvider {
val username: String
val password: String
fun saveCredentials(username: String, password: String)
}
private const val PREF_KEY_USERNAME = "PREF_KEY_USERNAME"
private const val PREF_KEY_PASSWORD = "PREF_KEY_PASSWORD"
class SharedPreferencesCredentialsProvider(private val sharedPreferences: SharedPreferences) : CredentialsProvider {
override val username: String = sharedPreferences.getString(PREF_KEY_USERNAME, null) ?: ""
override val password: String = sharedPreferences.getString(PREF_KEY_PASSWORD, null) ?: ""
override fun saveCredentials(username: String, password: String) {
sharedPreferences.edit()
.putString(PREF_KEY_USERNAME, username)
.putString(PREF_KEY_PASSWORD, password)
.apply()
}
}

View file

@ -25,7 +25,7 @@ android {
dependencies {
implementation project(':common')
implementation project(':auth')
implementation project(':storage')
// Retrofit
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
implementation "com.squareup.retrofit2:converter-moshi:$rootProject.ext.retrofit"

View file

@ -95,6 +95,9 @@ interface BudgetApiService {
@POST("users/login")
suspend fun login(@Body request: LoginRequest): User
@GET("users/me")
suspend fun getProfile(): User
@GET("users/search")
suspend fun searchUsers(@Query("query") query: String): List<User>

View file

@ -1,8 +1,9 @@
package com.wbrawner.budget.lib.network
import android.content.SharedPreferences
import android.util.Base64
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
import com.wbrawner.budget.auth.CredentialsProvider
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.common.category.CategoryRepository
import com.wbrawner.budget.common.transaction.TransactionRepository
@ -13,10 +14,13 @@ import com.wbrawner.budget.lib.repository.NetworkTransactionRepository
import com.wbrawner.budget.lib.repository.NetworkUserRepository
import dagger.Module
import dagger.Provides
import okhttp3.Credentials
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.nio.charset.Charset
import java.util.*
import javax.inject.Named
@ -28,25 +32,39 @@ class NetworkModule {
.build()
@Provides
fun provideOkHttpClient(credentialsProvider: CredentialsProvider): OkHttpClient = OkHttpClient.Builder()
.addInterceptor {
if (it.request().headers().get("Authorization") != null)
return@addInterceptor it.proceed(it.request())
val credentials = Credentials.basic(
credentialsProvider.username,
credentialsProvider.password
)
val newHeaders = it.request()
.headers()
.newBuilder()
.add("Authorization: $credentials")
.build()
val newRequest = it.request()
.newBuilder()
.headers(newHeaders)
.build()
it.proceed(newRequest)
fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar {
return object : CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
sharedPreferences.edit()
.putString(
url.host(),
cookies.joinToString(separator = ",") {
Base64.encode(it.toString().toByteArray(), 0)
.toString(charset = Charset.forName("UTF-8"))
}
)
.apply()
}
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
return sharedPreferences.getString(url.host(), "")
?.split(",")
?.mapNotNull {
Cookie.parse(
url,
Base64.decode(it, 0).toString(Charset.forName("UTF-8"))
)
}
?.toMutableList()
?: mutableListOf()
}
}
}
@Provides
fun provideOkHttpClient(cookieJar: CookieJar): OkHttpClient = OkHttpClient.Builder()
.cookieJar(cookieJar)
// TODO: Add Gander interceptor
.build()
@Provides

View file

@ -2,8 +2,6 @@ package com.wbrawner.budget.lib.repository
import com.wbrawner.budget.common.budget.Budget
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.common.user.LoginRequest
import com.wbrawner.budget.common.user.User
import com.wbrawner.budget.lib.network.BudgetApiService
import javax.inject.Inject
@ -21,9 +19,6 @@ class NetworkBudgetRepository @Inject constructor(private val apiService: Budget
override suspend fun delete(id: Long) = apiService.deleteBudget(id)
override suspend fun login(username: String, password: String): User =
apiService.login(LoginRequest(username, password))
override suspend fun getBalance(id: Long): Long = apiService.getBudgetBalance(id).balance
}

View file

@ -1,11 +1,18 @@
package com.wbrawner.budget.lib.repository
import com.wbrawner.budget.common.user.LoginRequest
import com.wbrawner.budget.common.user.User
import com.wbrawner.budget.common.user.UserRepository
import com.wbrawner.budget.lib.network.BudgetApiService
import javax.inject.Inject
class NetworkUserRepository @Inject constructor(private val apiService: BudgetApiService) : UserRepository {
override suspend fun login(username: String, password: String): User =
apiService.login(LoginRequest(username, password))
override suspend fun getProfile(): User = apiService.getProfile()
override suspend fun create(newItem: User): User = apiService.newUser(newItem)
override suspend fun findAll(accountId: Long?): Collection<User> = apiService.getUsers(accountId)

View file

@ -10,7 +10,7 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.2'
classpath 'io.fabric.tools:gradle:1.31.0'

View file

@ -1,9 +1,7 @@
package com.wbrawner.budget.common.budget
import com.wbrawner.budget.common.Repository
import com.wbrawner.budget.common.user.User
interface BudgetRepository : Repository<Budget, Long> {
suspend fun login(username: String, password: String): User
suspend fun getBalance(id: Long): Long
}

View file

@ -3,6 +3,8 @@ package com.wbrawner.budget.common.user
import com.wbrawner.budget.common.Repository
interface UserRepository : Repository<User, Long> {
suspend fun login(username: String, password: String): User
suspend fun getProfile(): User
suspend fun findAll(accountId: Long? = null): Collection<User>
suspend fun findAllByNameLike(query: String): Collection<User>
}

View file

@ -1 +1 @@
include ':app', ':budgetlib', ':common', ':auth'
include ':app', ':budgetlib', ':common', ':storage'

View file

@ -1,4 +1,4 @@
package com.wbrawner.budget.auth
package com.wbrawner.budget.storage
import android.content.Context
import android.content.SharedPreferences
@ -10,12 +10,7 @@ import javax.inject.Singleton
const val ENCRYPTED_SHARED_PREFS_FILE_NAME = "shared-prefs.encrypted"
@Module
class AuthModule {
@Singleton
@Provides
fun provideCredentialsProvider(sharedPreferences: SharedPreferences): CredentialsProvider =
SharedPreferencesCredentialsProvider(sharedPreferences)
class StorageModule {
@Provides
@Singleton
fun provideSharedPreferences(context: Context): SharedPreferences =