Implement updated authentication
This commit is contained in:
parent
6f24feed7b
commit
74601bc18d
21 changed files with 64 additions and 73 deletions
|
@ -20,8 +20,9 @@ android {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
}
|
}
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
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", "\"http://10.0.2.2:8080/\""
|
||||||
|
buildConfigField "String", "API_URL", "\"https://budget-api.intra.wbrawner.com/\""
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
|
@ -38,7 +39,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
implementation project(':budgetlib')
|
implementation project(':budgetlib')
|
||||||
implementation project(':auth')
|
implementation project(':storage')
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package com.wbrawner.budget.di
|
package com.wbrawner.budget.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.wbrawner.budget.auth.AuthModule
|
|
||||||
import com.wbrawner.budget.lib.network.NetworkModule
|
import com.wbrawner.budget.lib.network.NetworkModule
|
||||||
|
import com.wbrawner.budget.storage.StorageModule
|
||||||
import com.wbrawner.budget.ui.MainActivity
|
import com.wbrawner.budget.ui.MainActivity
|
||||||
import com.wbrawner.budget.ui.SplashActivity
|
import com.wbrawner.budget.ui.SplashActivity
|
||||||
import com.wbrawner.budget.ui.SplashViewModelMapper
|
import com.wbrawner.budget.ui.SplashViewModelMapper
|
||||||
|
@ -29,7 +29,7 @@ import javax.inject.Named
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = [AuthModule::class, AppModule::class, NetworkModule::class])
|
@Component(modules = [StorageModule::class, AppModule::class, NetworkModule::class])
|
||||||
interface AppComponent {
|
interface AppComponent {
|
||||||
fun inject(fragment: OverviewFragment)
|
fun inject(fragment: OverviewFragment)
|
||||||
fun inject(fragment: LoginFragment)
|
fun inject(fragment: LoginFragment)
|
||||||
|
|
|
@ -2,9 +2,8 @@ package com.wbrawner.budget.ui
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
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.User
|
||||||
|
import com.wbrawner.budget.common.user.UserRepository
|
||||||
import com.wbrawner.budget.di.ViewModelKey
|
import com.wbrawner.budget.di.ViewModelKey
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
@ -12,14 +11,13 @@ import dagger.multibindings.IntoMap
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SplashViewModel @Inject constructor(
|
class SplashViewModel @Inject constructor(
|
||||||
private val credentialsProvider: CredentialsProvider,
|
private val userRepository: UserRepository
|
||||||
private val budgetRepository: BudgetRepository
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
|
val isLoading: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
|
|
||||||
suspend fun checkForExistingCredentials(): User? {
|
suspend fun checkForExistingCredentials(): User? {
|
||||||
return try {
|
return try {
|
||||||
login(credentialsProvider.username, credentialsProvider.password)
|
userRepository.getProfile()
|
||||||
} catch (ignored: Exception) {
|
} catch (ignored: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
@ -27,9 +25,8 @@ class SplashViewModel @Inject constructor(
|
||||||
|
|
||||||
suspend fun login(username: String, password: String): User {
|
suspend fun login(username: String, password: String): User {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
credentialsProvider.saveCredentials(username, password)
|
|
||||||
return try {
|
return try {
|
||||||
budgetRepository.login(username, password)
|
userRepository.login(username, password)
|
||||||
} catch (e: java.lang.Exception) {
|
} catch (e: java.lang.Exception) {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
throw e
|
throw e
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,7 +25,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
implementation project(':auth')
|
implementation project(':storage')
|
||||||
// Retrofit
|
// Retrofit
|
||||||
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
|
api "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit"
|
||||||
implementation "com.squareup.retrofit2:converter-moshi:$rootProject.ext.retrofit"
|
implementation "com.squareup.retrofit2:converter-moshi:$rootProject.ext.retrofit"
|
||||||
|
|
|
@ -95,6 +95,9 @@ interface BudgetApiService {
|
||||||
@POST("users/login")
|
@POST("users/login")
|
||||||
suspend fun login(@Body request: LoginRequest): User
|
suspend fun login(@Body request: LoginRequest): User
|
||||||
|
|
||||||
|
@GET("users/me")
|
||||||
|
suspend fun getProfile(): User
|
||||||
|
|
||||||
@GET("users/search")
|
@GET("users/search")
|
||||||
suspend fun searchUsers(@Query("query") query: String): List<User>
|
suspend fun searchUsers(@Query("query") query: String): List<User>
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package com.wbrawner.budget.lib.network
|
package com.wbrawner.budget.lib.network
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Base64
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
|
import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter
|
||||||
import com.wbrawner.budget.auth.CredentialsProvider
|
|
||||||
import com.wbrawner.budget.common.budget.BudgetRepository
|
import com.wbrawner.budget.common.budget.BudgetRepository
|
||||||
import com.wbrawner.budget.common.category.CategoryRepository
|
import com.wbrawner.budget.common.category.CategoryRepository
|
||||||
import com.wbrawner.budget.common.transaction.TransactionRepository
|
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 com.wbrawner.budget.lib.repository.NetworkUserRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import okhttp3.Credentials
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.CookieJar
|
||||||
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import java.nio.charset.Charset
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
|
||||||
|
@ -28,25 +32,39 @@ class NetworkModule {
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideOkHttpClient(credentialsProvider: CredentialsProvider): OkHttpClient = OkHttpClient.Builder()
|
fun provideCookieJar(sharedPreferences: SharedPreferences): CookieJar {
|
||||||
.addInterceptor {
|
return object : CookieJar {
|
||||||
if (it.request().headers().get("Authorization") != null)
|
override fun saveFromResponse(url: HttpUrl, cookies: MutableList<Cookie>) {
|
||||||
return@addInterceptor it.proceed(it.request())
|
sharedPreferences.edit()
|
||||||
val credentials = Credentials.basic(
|
.putString(
|
||||||
credentialsProvider.username,
|
url.host(),
|
||||||
credentialsProvider.password
|
cookies.joinToString(separator = ",") {
|
||||||
)
|
Base64.encode(it.toString().toByteArray(), 0)
|
||||||
val newHeaders = it.request()
|
.toString(charset = Charset.forName("UTF-8"))
|
||||||
.headers()
|
}
|
||||||
.newBuilder()
|
)
|
||||||
.add("Authorization: $credentials")
|
.apply()
|
||||||
.build()
|
|
||||||
val newRequest = it.request()
|
|
||||||
.newBuilder()
|
|
||||||
.headers(newHeaders)
|
|
||||||
.build()
|
|
||||||
it.proceed(newRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
@ -2,8 +2,6 @@ package com.wbrawner.budget.lib.repository
|
||||||
|
|
||||||
import com.wbrawner.budget.common.budget.Budget
|
import com.wbrawner.budget.common.budget.Budget
|
||||||
import com.wbrawner.budget.common.budget.BudgetRepository
|
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 com.wbrawner.budget.lib.network.BudgetApiService
|
||||||
import javax.inject.Inject
|
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 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
|
override suspend fun getBalance(id: Long): Long = apiService.getBudgetBalance(id).balance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
package com.wbrawner.budget.lib.repository
|
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.User
|
||||||
import com.wbrawner.budget.common.user.UserRepository
|
import com.wbrawner.budget.common.user.UserRepository
|
||||||
import com.wbrawner.budget.lib.network.BudgetApiService
|
import com.wbrawner.budget.lib.network.BudgetApiService
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class NetworkUserRepository @Inject constructor(private val apiService: BudgetApiService) : UserRepository {
|
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 create(newItem: User): User = apiService.newUser(newItem)
|
||||||
|
|
||||||
override suspend fun findAll(accountId: Long?): Collection<User> = apiService.getUsers(accountId)
|
override suspend fun findAll(accountId: Long?): Collection<User> = apiService.getUsers(accountId)
|
||||||
|
|
|
@ -10,7 +10,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
classpath 'com.google.gms:google-services:4.3.2'
|
classpath 'com.google.gms:google-services:4.3.2'
|
||||||
classpath 'io.fabric.tools:gradle:1.31.0'
|
classpath 'io.fabric.tools:gradle:1.31.0'
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package com.wbrawner.budget.common.budget
|
package com.wbrawner.budget.common.budget
|
||||||
|
|
||||||
import com.wbrawner.budget.common.Repository
|
import com.wbrawner.budget.common.Repository
|
||||||
import com.wbrawner.budget.common.user.User
|
|
||||||
|
|
||||||
interface BudgetRepository : Repository<Budget, Long> {
|
interface BudgetRepository : Repository<Budget, Long> {
|
||||||
suspend fun login(username: String, password: String): User
|
|
||||||
suspend fun getBalance(id: Long): Long
|
suspend fun getBalance(id: Long): Long
|
||||||
}
|
}
|
|
@ -3,6 +3,8 @@ package com.wbrawner.budget.common.user
|
||||||
import com.wbrawner.budget.common.Repository
|
import com.wbrawner.budget.common.Repository
|
||||||
|
|
||||||
interface UserRepository : Repository<User, Long> {
|
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 findAll(accountId: Long? = null): Collection<User>
|
||||||
suspend fun findAllByNameLike(query: String): Collection<User>
|
suspend fun findAllByNameLike(query: String): Collection<User>
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
include ':app', ':budgetlib', ':common', ':auth'
|
include ':app', ':budgetlib', ':common', ':storage'
|
||||||
|
|
0
auth/.gitignore → storage/.gitignore
vendored
0
auth/.gitignore → storage/.gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
package com.wbrawner.budget.auth
|
package com.wbrawner.budget.storage
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
@ -10,12 +10,7 @@ import javax.inject.Singleton
|
||||||
const val ENCRYPTED_SHARED_PREFS_FILE_NAME = "shared-prefs.encrypted"
|
const val ENCRYPTED_SHARED_PREFS_FILE_NAME = "shared-prefs.encrypted"
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
class AuthModule {
|
class StorageModule {
|
||||||
@Singleton
|
|
||||||
@Provides
|
|
||||||
fun provideCredentialsProvider(sharedPreferences: SharedPreferences): CredentialsProvider =
|
|
||||||
SharedPreferencesCredentialsProvider(sharedPreferences)
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideSharedPreferences(context: Context): SharedPreferences =
|
fun provideSharedPreferences(context: Context): SharedPreferences =
|
Loading…
Reference in a new issue