ladybird/Libraries/LibWeb/CredentialManagement/PasswordCredentialOperations.cpp

131 lines
6.5 KiB
C++
Raw Permalink Normal View History

/*
* Copyright (c) 2025, Kenneth Myhra <kennethmyhra@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/CredentialManagement/PasswordCredentialOperations.h>
#include <LibWeb/HTML/HTMLFormElement.h>
#include <LibWeb/XHR/FormData.h>
namespace Web::CredentialManagement {
// https://www.w3.org/TR/credential-management-1/#abstract-opdef-create-a-passwordcredential-from-an-htmlformelement
WebIDL::ExceptionOr<GC::Ref<PasswordCredential>> create_password_credential(JS::Realm& realm, GC::Ref<HTML::HTMLFormElement> form, URL::Origin origin)
{
// 1. Let data be a new PasswordCredentialData dictionary.
PasswordCredentialData data;
// 2. Set datas origin members value to origins value.
// 3. Let formData be the result of executing the FormData constructor on form.
auto form_data = TRY(XHR::FormData::construct_impl(realm, form));
// 4. Let elements be a list of all the submittable elements whose form owner is form, in tree order.
auto elements = form->get_submittable_elements();
// 5. Let newPasswordObserved be false.
bool new_password_observed = false;
// 6. For each field in elements, run the following steps:
for (auto const& field : elements) {
// 1. If field does not have an autocomplete attribute, then skip to the next field.
if (auto attr = field->attribute(HTML::AttributeNames::autocomplete); !attr.has_value() || attr->is_empty())
continue;
// 2. Let name be the value of fields name attribute.
// 3. If formDatas has() method returns false when executed on name, then skip to the next field.
auto name = field->attribute(HTML::AttributeNames::name);
if (!name.has_value() || !form_data->has(name.value()))
continue;
// 4. If fields autocomplete attributes value contains one or more autofill detail tokens (tokens), then:
// 1. For each token in tokens:
for (auto tokens = field->attribute(HTML::AttributeNames::autocomplete); auto& token : MUST(tokens->split(' '))) {
// 1. If token is an ASCII case-insensitive match for one of the following strings, run the associated steps:
// - "new-password"
// Set datas password members value to the result of executing formDatas get() method on name,
// and newPasswordObserved to true.
if (token.equals_ignoring_ascii_case("new-password"sv)) {
if (auto password = form_data->get(name.value()); password.has<String>()) {
data.password = password.get<String>();
new_password_observed = true;
}
}
// - "current-password"
// If newPasswordObserved is false, set datas password members value to the result of executing
// formDatas get() method on name.
// Note: By checking that newPasswordObserved is false, new-password fields take precedence over
// current-password fields.
if (!new_password_observed && token.equals_ignoring_ascii_case("current-password"sv)) {
if (auto password = form_data->get(name.value()); password.has<String>())
data.password = password.get<String>();
}
// - "photo"
// Set datas iconURL members value to the result of executing formDatas get() method on name.
if (token.equals_ignoring_ascii_case("photo"sv)) {
if (auto photo = form_data->get(name.value()); photo.has<String>())
data.icon_url = photo.get<String>();
}
// - "name"
// - "nickname"
// Set datas name members value to the result of executing formDatas get() method on name.
if (token.equals_ignoring_ascii_case("name"sv)) {
if (auto name_ = form_data->get(name.value()); name_.has<String>())
data.name = name_.get<String>();
}
if (token.equals_ignoring_ascii_case("nickname"sv)) {
if (auto nickname = form_data->get(name.value()); nickname.has<String>())
data.name = nickname.get<String>();
}
// - "username"
// Set datas id members value to the result of executing formDatas get() method on name.
if (token.equals_ignoring_ascii_case("username"sv)) {
if (auto username = form_data->get(name.value()); username.has<String>()) {
auto id = username.get<String>();
data.id = id;
}
}
}
}
// 7. Let c be the result of executing Create a PasswordCredential from PasswordCredentialData on data.
// If that threw an exception, rethrow that exception.
// 8. Assert: c is a PasswordCredential.
// 9. Return c.
return create_password_credential(realm, data, move(origin));
}
// https://www.w3.org/TR/credential-management-1/#abstract-opdef-create-a-passwordcredential-from-passwordcredentialdata
WebIDL::ExceptionOr<GC::Ref<PasswordCredential>> create_password_credential(JS::Realm& realm, PasswordCredentialData const& data, URL::Origin origin)
{
// 1. Let c be a new PasswordCredential object.
// 2. If any of the following are the empty string, throw a TypeError exception:
// - datas id members value
// - datas origin members value
// NOTE: origin cannot be an empty string at this time since it is retrieved from the current settings object
// in the constructor.
// - datas password members value
if (data.id.is_empty())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "'id' must not be empty."sv };
if (data.password.is_empty())
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "'password' must not be empty."sv };
// 3. Set cs properties as follows:
// - password
// - datas password members value
// - id
// - datas id members value
// - iconUrl
// - datas iconURL members value
// - name
// - datas name members value
// - [[origin]]
// - datas origin members value.
// NOTE: origin is retrieved from the current settings object in the constructor.
// 4. Return c.
return realm.create<PasswordCredential>(realm, data, move(origin));
}
}