blob: 6b79adb7696c37e343f74e3a95dd406cc4f12ab6 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "components/webauthn/ios/passkey_java_script_feature.h"
#import "base/no_destructor.h"
#import "base/values.h"
#import "components/webauthn/ios/passkey_tab_helper.h"
#import "ios/web/public/js_messaging/java_script_feature_util.h"
#import "ios/web/public/js_messaging/script_message.h"
#import "ios/web/public/js_messaging/web_frames_manager.h"
namespace {
constexpr char kScriptName[] = "passkey_controller";
constexpr char kHandlerName[] = "PasskeyInteractionHandler";
} // namespace
// static
PasskeyJavaScriptFeature* PasskeyJavaScriptFeature::GetInstance() {
static base::NoDestructor<PasskeyJavaScriptFeature> instance;
return instance.get();
}
PasskeyJavaScriptFeature::PasskeyJavaScriptFeature()
: web::JavaScriptFeature(
// This is a shim, so it needs to be in the page content world.
web::ContentWorld::kPageContentWorld,
{FeatureScript::CreateWithFilename(
kScriptName,
FeatureScript::InjectionTime::kDocumentStart,
// It's valid for passkey flows to happen not in a main frame,
// though it requires appropriate permissions policy to be set
// (https://w3c.github.io/webauthn/#sctn-permissions-policy).
FeatureScript::TargetFrames::kAllFrames,
FeatureScript::ReinjectionBehavior::kInjectOncePerWindow)},
{web::java_script_features::GetCommonJavaScriptFeature()}) {}
PasskeyJavaScriptFeature::~PasskeyJavaScriptFeature() = default;
void PasskeyJavaScriptFeature::SetAllowModalLogin(web::WebState* web_state,
bool allow_modal_login) {
if (!web_state) {
return;
}
web::WebFramesManager* web_frames_manager = GetWebFramesManager(web_state);
if (!web_frames_manager) {
return;
}
std::set<web::WebFrame*> web_frames = web_frames_manager->GetAllWebFrames();
base::Value::List parameters = base::Value::List().Append(allow_modal_login);
for (auto web_frame : web_frames) {
CallJavaScriptFunction(
web_frame, "passkey.setCanHandleModalPasskeyRequests", parameters);
}
}
std::optional<std::string>
PasskeyJavaScriptFeature::GetScriptMessageHandlerName() const {
return kHandlerName;
}
void PasskeyJavaScriptFeature::ScriptMessageReceived(
web::WebState* web_state,
const web::ScriptMessage& message) {
PasskeyTabHelper* passkey_tab_helper =
PasskeyTabHelper::FromWebState(web_state);
if (!passkey_tab_helper) {
// Passkey tab helper is not created in some WebState cases for which
// passkey flows should not be applicable either (e.g. Lens overlay).
// Return early in this case. If there is somehow a valid passkey flow that
// should happen, it will still be handled by invoking Credential Provider
// Extension logic in the controller.
return;
}
// This message is sent whenever a navigator.credentials get() or create() is
// called for a WebAuthn credential.
// Expected argument:
// event: (string) Describes a type of event.
//
// For some events there are more expected arguments described below.
base::Value* body = message.body();
if (!body || !body->is_dict()) {
return;
}
const base::Value::Dict& dict = body->GetDict();
const std::string* event = dict.FindString("event");
if (!event || event->empty()) {
return;
}
// For those events there are no more expected arguments.
if (*event == "getRequested" || *event == "createRequested" ||
*event == "createResolvedGpm" || *event == "createResolvedNonGpm") {
passkey_tab_helper->LogEventFromString(*event);
return;
}
// Expected arguments for "getResolved" event:
// credential_id: (string) base64url encoded identifer of the credential.
// rp_id: (string) The relying party's identifier.
if (*event == "getResolved") {
const std::string* credential_id = dict.FindString("credential_id");
const std::string* rp_id = dict.FindString("rp_id");
if (credential_id && !credential_id->empty() && rp_id && !rp_id->empty()) {
passkey_tab_helper->HandleGetResolvedEvent(*credential_id, *rp_id);
}
return;
}
// TODO(crbug.com/369629469): Handle other types of events.
}