Skip to content

Commit

Permalink
Merge branch 'master' into signup_new_user
Browse files Browse the repository at this point in the history
  • Loading branch information
JacobCube authored Oct 30, 2024
2 parents e51997d + 45b69eb commit aa0c563
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 43 deletions.
1 change: 1 addition & 0 deletions .firebaserc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
10 changes: 7 additions & 3 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ jobs:
with:
distribution: 'zulu'
java-version: 17
- name: Build
uses: eskatos/gradle-command-action@v3
- name: Set up Node.js 20
uses: actions/setup-node@v4
with:
arguments: build
node-version: 20
- name: Install Firebase CLI
run: npm install -g firebase-tools
- name: Build
run: firebase emulators:exec --project my-firebase-project --import=src/test/resources/firebase_data './gradlew build'
11 changes: 11 additions & 0 deletions firebase.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"emulators": {
"auth": {
"port": 9099
},
"ui": {
"enabled": true
},
"singleProjectMode": true
}
}
44 changes: 31 additions & 13 deletions src/main/java/com/google/firebase/auth/FirebaseAuth.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ import java.util.concurrent.TimeUnit

internal val jsonParser = Json { ignoreUnknownKeys = true }

class UrlFactory(
private val app: FirebaseApp,
private val emulatorUrl: String? = null
) {
fun buildUrl(uri: String): String {
return "${emulatorUrl ?: "https://"}$uri?key=${app.options.apiKey}"
}
}

@Serializable
class FirebaseUserImpl internal constructor(
@Transient
Expand All @@ -53,14 +62,17 @@ class FirebaseUserImpl internal constructor(
val refreshToken: String,
val expiresIn: Int,
val createdAt: Long,
override val email: String?
override val email: String?,
@Transient
private val urlFactory: UrlFactory = UrlFactory(app)
) : FirebaseUser() {

constructor(
app: FirebaseApp,
data: JsonObject,
isAnonymous: Boolean = data["isAnonymous"]?.jsonPrimitive?.booleanOrNull ?: false,
email: String? = data.getOrElse("email") { null }?.jsonPrimitive?.contentOrNull
email: String? = data.getOrElse("email") { null }?.jsonPrimitive?.contentOrNull,
urlFactory: UrlFactory = UrlFactory(app)
) : this(
app = app,
isAnonymous = isAnonymous,
Expand All @@ -69,18 +81,19 @@ class FirebaseUserImpl internal constructor(
refreshToken = data["refreshToken"]?.jsonPrimitive?.contentOrNull ?: data.getValue("refresh_token").jsonPrimitive.content,
expiresIn = data["expiresIn"]?.jsonPrimitive?.intOrNull ?: data.getValue("expires_in").jsonPrimitive.int,
createdAt = data["createdAt"]?.jsonPrimitive?.longOrNull ?: System.currentTimeMillis(),
email = email
email = email,
urlFactory = urlFactory
)

val claims: Map<String, Any?> by lazy {
internal val claims: Map<String, Any?> by lazy {
jsonParser
.parseToJsonElement(String(Base64.getUrlDecoder().decode(idToken.split(".")[1])))
.jsonObject
.run { value as Map<String, Any?>? }
.orEmpty()
}

val JsonElement.value get(): Any? = when (this) {
internal val JsonElement.value get(): Any? = when (this) {
is JsonNull -> null
is JsonArray -> map { it.value }
is JsonObject -> jsonObject.mapValues { (_, it) -> it.value }
Expand All @@ -92,7 +105,7 @@ class FirebaseUserImpl internal constructor(
val source = TaskCompletionSource<Void>()
val body = RequestBody.create(FirebaseAuth.getInstance(app).json, JsonObject(mapOf("idToken" to JsonPrimitive(idToken))).toString())
val request = Request.Builder()
.url("https://www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("www.googleapis.com/identitytoolkit/v3/relyingparty/deleteAccount"))
.post(body)
.build()
FirebaseAuth.getInstance(app).client.newCall(request).enqueue(object : Callback {
Expand Down Expand Up @@ -211,9 +224,9 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
)
} else {
if(response.body()?.use { it.string() }?.also { responseBody ->
user = setResult(responseBody)
source.setResult(AuthResult { user })
} == null) {
user = setResult(responseBody)
source.setResult(AuthResult { user })
} == null) {
source.setException(
createAuthInvalidUserException("accounts", request, response)
)
Expand Down Expand Up @@ -279,6 +292,8 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
}
}

private var urlFactory = UrlFactory(app)

fun signInAnonymously(): Task<AuthResult> {
val source = enqueueAuthPost(
url = "https://identitytoolkit.googleapis.com/v1/accounts:signUp",
Expand Down Expand Up @@ -358,8 +373,8 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
?.contentOrNull
?: "UNKNOWN_ERROR",
"$action API returned an error, " +
"with url [${request.method()}] ${request.url()} ${request.body()} -- " +
"response [${response.code()}] ${response.message()} $body"
"with url [${request.method()}] ${request.url()} ${request.body()} -- " +
"response [${response.code()}] ${response.message()} $body"
)
}

Expand Down Expand Up @@ -406,7 +421,7 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
).toString()
)
val request = Request.Builder()
.url("https://securetoken.googleapis.com/v1/token?key=" + app.options.apiKey)
.url(urlFactory.buildUrl("securetoken.googleapis.com/v1/token"))
.post(body)
.tag(REFRESH_TOKEN_TAG)
.build()
Expand Down Expand Up @@ -568,5 +583,8 @@ class FirebaseAuth constructor(val app: FirebaseApp) : InternalAuthProvider {
fun signInWithEmailLink(email: String, link: String): Task<AuthResult> = TODO()

fun setLanguageCode(value: String): Nothing = TODO()
fun useEmulator(host: String, port: Int): Unit = TODO()

fun useEmulator(host: String, port: Int) {
urlFactory = UrlFactory(app, "http://$host:$port/")
}
}
47 changes: 47 additions & 0 deletions src/test/kotlin/AuthTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseAuthInvalidUserException
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertThrows
import org.junit.Test

class AuthTest : FirebaseTest() {
private fun createAuth(): FirebaseAuth {
return FirebaseAuth(app).apply {
useEmulator("localhost", 9099)
}
}

@Test
fun `should authenticate via anonymous auth`() = runTest {
val auth = createAuth()

auth.signInAnonymously().await()

assertEquals(true, auth.currentUser?.isAnonymous)
}

@Test
fun `should authenticate via email and password`() = runTest {
val auth = createAuth()

auth.signInWithEmailAndPassword("[email protected]", "securepassword").await()

assertEquals(false, auth.currentUser?.isAnonymous)
}

@Test
fun `should throw exception on invalid password`() {
val auth = createAuth()

val exception = assertThrows(FirebaseAuthInvalidUserException::class.java) {
runBlocking {
auth.signInWithEmailAndPassword("[email protected]", "wrongpassword").await()
}
}

assertEquals("INVALID_PASSWORD", exception.errorCode)
}
}
24 changes: 24 additions & 0 deletions src/test/kotlin/FirebaseTest.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import android.app.Application
import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.google.firebase.FirebasePlatform
import com.google.firebase.initialize
import org.junit.Before
import java.io.File

abstract class FirebaseTest {
protected val app: FirebaseApp get() {
val options = FirebaseOptions.Builder()
.setProjectId("my-firebase-project")
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
.build()

return Firebase.initialize(Application(), options)
}

@Before
fun beforeEach() {
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
val storage = mutableMapOf<String, String>()
override fun store(key: String, value: String) = storage.set(key, value)
override fun retrieve(key: String) = storage[key]
override fun clear(key: String) { storage.remove(key) }
override fun log(msg: String) = println(msg)
override fun getDatabasePath(name: String) = File("./build/$name")
})
FirebaseApp.clearInstancesForTest()
}
}
31 changes: 4 additions & 27 deletions src/test/kotlin/FirestoreTest.kt
Original file line number Diff line number Diff line change
@@ -1,42 +1,19 @@
import android.app.Application
import com.google.firebase.Firebase
import com.google.firebase.FirebaseOptions
import com.google.firebase.FirebasePlatform
import com.google.firebase.firestore.firestore
import com.google.firebase.initialize
import kotlinx.coroutines.tasks.await
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.io.File

class FirestoreTest : FirebaseTest() {
@Before
fun initialize() {
FirebasePlatform.initializeFirebasePlatform(object : FirebasePlatform() {
val storage = mutableMapOf<String, String>()
override fun store(key: String, value: String) = storage.set(key, value)
override fun retrieve(key: String) = storage[key]
override fun clear(key: String) { storage.remove(key) }
override fun log(msg: String) = println(msg)
override fun getDatabasePath(name: String) = File("./build/$name")
})
val options = FirebaseOptions.Builder()
.setProjectId("my-firebase-project")
.setApplicationId("1:27992087142:android:ce3b6448250083d1")
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw")
// setDatabaseURL(...)
// setStorageBucket(...)
.build()
Firebase.initialize(Application(), options)
Firebase.firestore.disableNetwork()
}

@Test
fun testFirestore(): Unit = runTest {
val firestore = Firebase.firestore(app)
firestore.disableNetwork().await()

val data = Data("jim")
val doc = Firebase.firestore.document("sally/jim")
val doc = firestore.document("sally/jim")
doc.set(data)
assertEquals(data, doc.get().await().toObject(Data::class.java))
}
Expand Down
29 changes: 29 additions & 0 deletions src/test/resources/firebase_data/auth_export/accounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"kind": "identitytoolkit#DownloadAccountResponse",
"users": [
{
"localId": "Ijat10t0F1gvH1VrClkkSqEcId1p",
"lastLoginAt": "1728509249920",
"displayName": "",
"photoUrl": "",
"emailVerified": true,
"email": "[email protected]",
"salt": "fakeSaltHsRxYqy9iKVQRLwz8975",
"passwordHash": "fakeHash:salt=fakeSaltHsRxYqy9iKVQRLwz8975:password=securepassword",
"passwordUpdatedAt": 1728509249921,
"validSince": "1728509249",
"mfaInfo": [],
"createdAt": "1728509249920",
"providerUserInfo": [
{
"providerId": "password",
"email": "[email protected]",
"federatedId": "[email protected]",
"rawId": "[email protected]",
"displayName": "",
"photoUrl": ""
}
]
}
]
}
8 changes: 8 additions & 0 deletions src/test/resources/firebase_data/auth_export/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"signIn": {
"allowDuplicateEmails": false
},
"emailPrivacyConfig": {
"enableImprovedEmailPrivacy": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"version": "13.3.1",
"auth": {
"version": "13.3.1",
"path": "auth_export"
}
}

0 comments on commit aa0c563

Please sign in to comment.