Skip to content
This repository has been archived by the owner on Nov 1, 2022. It is now read-only.

FxA webchannel support and account manager integration #4221

Merged
merged 3 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ enum class AuthExceptionType(val msg: String) {
*/
class AuthException(type: AuthExceptionType, cause: Exception? = null) : Throwable(type.msg, cause)

/**
* An object that represents a login flow initiated by [OAuthAccount].
* @property state OAuth state parameter, identifying a specific authentication flow.
grigoryk marked this conversation as resolved.
Show resolved Hide resolved
* This string is randomly generated during [OAuthAccount.beginOAuthFlowAsync] and [OAuthAccount.beginPairingFlowAsync].
* @property url Url which needs to be loaded to go through the authentication flow identified by [state].
*/
data class AuthFlowUrl(val state: String, val url: String)

/**
* Facilitates testing consumers of FirefoxAccount.
*/
@SuppressWarnings("TooManyFunctions")
interface OAuthAccount : AutoCloseable {
fun beginOAuthFlowAsync(scopes: Set<String>): Deferred<String?>
fun beginPairingFlowAsync(pairingUrl: String, scopes: Set<String>): Deferred<String?>
fun beginOAuthFlowAsync(scopes: Set<String>): Deferred<AuthFlowUrl?>
fun beginPairingFlowAsync(pairingUrl: String, scopes: Set<String>): Deferred<AuthFlowUrl?>
fun getProfileAsync(ignoreCache: Boolean): Deferred<Profile?>
fun getProfileAsync(): Deferred<Profile?>
fun completeOAuthFlowAsync(code: String, state: String): Deferred<Boolean>
Expand All @@ -50,6 +58,43 @@ interface StatePersistenceCallback {
fun persist(data: String)
}

sealed class AuthType {
/**
* Account restored from hydrated state on disk.
*/
object Existing : AuthType()

/**
* Account created in response to a sign-in.
*/
object Signin : AuthType()

/**
* Account created in response to a sign-up.
*/
object Signup : AuthType()

/**
* Account created via pairing (similar to sign-in, but without requiring credentials).
*/
object Pairing : AuthType()

/**
* Account was created for an unknown external reason, identified by [action].
*/
data class OtherExternal(val action: String) : AuthType()

/**
* Account created via a shared account state from another app.
*/
object Shared : AuthType()

/**
* Existing account was recovered from an authentication problem.
*/
object Recovered : AuthType()
}

/**
* Observer interface which lets its users monitor account state changes and major events.
*/
Expand All @@ -61,10 +106,11 @@ interface AccountObserver {

/**
* Account was successfully authenticated.
*
* @param account An authenticated instance of a [OAuthAccount].
* @param newAccount True if an account was just signed in.
* @param authType Describes what kind of authentication event caused this invocation.
*/
fun onAuthenticated(account: OAuthAccount, newAccount: Boolean) = Unit
fun onAuthenticated(account: OAuthAccount, authType: AuthType) = Unit

/**
* Account's profile is now available.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
Establish communication with native application.
*/
let port = browser.runtime.connectNative("mozacWebchannel");

/*
Handle messages from native application, dispatch them to FxA via an event.
*/
port.onMessage.addListener((event) => {
window.dispatchEvent(new CustomEvent('WebChannelMessageToContent', {
detail: JSON.stringify(event)
}));
});

// We should use pagehide/pageshow events instead, to better handle page navigation events.
// See https://github.com/mozilla-mobile/android-components/issues/2984.
window.addEventListener("unload", (event) => { port.disconnect() }, false);
grigoryk marked this conversation as resolved.
Show resolved Hide resolved

/*
Handle messages from FxA. Messages are posted to the native application for processing.
*/
window.addEventListener('WebChannelMessageToChrome', function (e) {
const detail = JSON.parse(e.detail);
port.postMessage(detail);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"manifest_version": 2,
"name": "Mozilla Android Components - Firefox Accounts WebChannel",
"version": "1.0",
"content_scripts": [
{
"matches": ["https://acounts.firefox.com/*"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is missing lcip domains, we should probably include them still

"js": ["fxawebchannel.js"],
"run_at": "document_start"
}
],
"permissions": [
"mozillaAddons",
"geckoViewAddons",
"nativeMessaging"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.request.RequestInterceptor
import mozilla.components.service.fxa.FxaAuthData
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.service.fxa.toAuthType
import kotlin.coroutines.CoroutineContext

/**
Expand Down Expand Up @@ -66,10 +68,15 @@ class FirefoxAccountsAuthFeature(
val code = parsedUri.getQueryParameter("code")

if (code != null) {
val authType = parsedUri.getQueryParameter("action")!!.toAuthType()
val state = parsedUri.getQueryParameter("state") as String

// Notify the state machine about our success.
accountManager.finishAuthenticationAsync(code, state)
accountManager.finishAuthenticationAsync(FxaAuthData(
authType = authType,
code = code,
state = state
))

return RequestInterceptor.InterceptionResponse.Url(redirectUrl)
}
Expand Down
Loading