Implement Firebase Google sign-in with enum-based auth contracts.

Refactor AuthService to use AuthProvider and User, add Firebase-backed auth wiring for login/manage flows, and fix app-level Google services configuration so Credential Manager sign-in works reliably.

Made-with: Cursor
This commit is contained in:
2026-03-01 21:15:09 +01:00
parent 90792c601c
commit 91a9521f3e
15 changed files with 269 additions and 48 deletions

View File

@@ -1,10 +1,11 @@
package org.db3.airmq.sdk.auth
import org.db3.airmq.sdk.auth.model.Auth
import org.db3.airmq.sdk.auth.model.AuthProvider
import org.db3.airmq.sdk.auth.model.User
interface AuthService {
suspend fun getCurrentSession(): Auth?
suspend fun getCurrentSession(): User?
suspend fun isAuthenticated(): Boolean
suspend fun signIn(provider: String, token: String? = null): Result<Auth>
suspend fun signIn(provider: AuthProvider, token: String): Result<User>
suspend fun signOut(): Result<Unit>
}

View File

@@ -0,0 +1,60 @@
package org.db3.airmq.sdk.auth
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.GoogleAuthProvider
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
import org.db3.airmq.sdk.auth.model.AuthProvider
import org.db3.airmq.sdk.auth.model.User
@Singleton
class FirebaseAuthService @Inject constructor(
private val firebaseAuth: FirebaseAuth
) : AuthService {
override suspend fun getCurrentSession(): User? = firebaseAuth.currentUser?.toUser()
override suspend fun isAuthenticated(): Boolean = firebaseAuth.currentUser != null
override suspend fun signIn(provider: AuthProvider, token: String): Result<User> = runCatching {
require(token.isNotBlank()) { "ID token is required for Google sign-in." }
when (provider) {
AuthProvider.GOOGLE -> signInWithGoogle(token)
}
}
override suspend fun signOut(): Result<Unit> = runCatching {
firebaseAuth.signOut()
}
private suspend fun signInWithGoogle(token: String): User {
val credential = GoogleAuthProvider.getCredential(token, null)
val authResult = firebaseAuth.signInWithCredential(credential).awaitResult()
val firebaseUser = authResult.user ?: error("Google sign-in completed without a Firebase user.")
return firebaseUser.toUser()
}
private fun FirebaseUser.toUser(): User = User(
userId = uid,
email = email,
displayName = displayName,
isAuthenticated = true
)
}
private suspend fun <T> Task<T>.awaitResult(): T = suspendCancellableCoroutine { continuation ->
addOnSuccessListener { result ->
continuation.resume(result)
}
addOnFailureListener { exception ->
continuation.resumeWithException(exception)
}
addOnCanceledListener {
continuation.cancel()
}
}

View File

@@ -0,0 +1,5 @@
package org.db3.airmq.sdk.auth.model
enum class AuthProvider {
GOOGLE
}

View File

@@ -1,6 +1,6 @@
package org.db3.airmq.sdk.auth.model
data class Auth(
data class User(
val userId: String,
val email: String?,
val displayName: String?,

View File

@@ -1,12 +1,15 @@
package org.db3.airmq.sdk.di
import com.apollographql.apollo.ApolloClient
import com.google.firebase.auth.FirebaseAuth
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import org.db3.airmq.sdk.auth.AuthService
import org.db3.airmq.sdk.auth.FirebaseAuthService
import org.db3.airmq.sdk.map.MapServiceImpl
import org.db3.airmq.sdk.map.MapService
import org.db3.airmq.sdk.settings.SettingsService
@@ -26,11 +29,19 @@ object SDKModule {
.addInterceptor(ApolloLoggingInterceptor())
.build()
}
@Provides
@Singleton
fun provideFirebaseAuth(): FirebaseAuth = FirebaseAuth.getInstance()
}
@Module
@InstallIn(SingletonComponent::class)
abstract class SDKBindModule {
@Binds
@Singleton
abstract fun bindAuthService(impl: FirebaseAuthService): AuthService
@Binds
@Singleton
abstract fun bindMapService(impl: MapServiceImpl): MapService