From 1ab99aa8a16cbe45b377723e8a72cfdbc84b03b5 Mon Sep 17 00:00:00 2001 From: Szilard Szaloki Date: Mon, 20 Apr 2026 13:32:10 -0600 Subject: [PATCH] Account: implements the `/v2/verify/init` endpoint (#35678) --- components/brave_account/endpoints/BUILD.gn | 3 + .../brave_account/endpoints/verify_init.h | 25 ++++ .../endpoints/verify_init_bodies.idl | 18 +++ .../endpoints/verify_init_unittest.cc | 126 ++++++++++++++++++ 4 files changed, 172 insertions(+) create mode 100644 components/brave_account/endpoints/verify_init.h create mode 100644 components/brave_account/endpoints/verify_init_bodies.idl create mode 100644 components/brave_account/endpoints/verify_init_unittest.cc diff --git a/components/brave_account/endpoints/BUILD.gn b/components/brave_account/endpoints/BUILD.gn index 6ca4ffd8fc3..6a846a1b2db 100644 --- a/components/brave_account/endpoints/BUILD.gn +++ b/components/brave_account/endpoints/BUILD.gn @@ -16,6 +16,7 @@ source_set("endpoints") { "service_token.h", "verify_complete.h", "verify_delete.h", + "verify_init.h", "verify_resend.h", ] @@ -37,6 +38,7 @@ generated_types("generated_api_types") { "service_token_bodies.idl", "verify_complete_bodies.idl", "verify_delete_bodies.idl", + "verify_init_bodies.idl", "verify_resend_bodies.idl", ] @@ -59,6 +61,7 @@ source_set("unit_tests") { "service_token_unittest.cc", "verify_complete_unittest.cc", "verify_delete_unittest.cc", + "verify_init_unittest.cc", "verify_resend_unittest.cc", ] diff --git a/components/brave_account/endpoints/verify_init.h b/components/brave_account/endpoints/verify_init.h new file mode 100644 index 00000000000..dab753b81f4 --- /dev/null +++ b/components/brave_account/endpoints/verify_init.h @@ -0,0 +1,25 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_VERIFY_INIT_H_ +#define BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_VERIFY_INIT_H_ + +#include "brave/components/brave_account/endpoint_client/brave_endpoint.h" +#include "brave/components/brave_account/endpoint_client/request_types.h" +#include "brave/components/brave_account/endpoint_client/response.h" +#include "brave/components/brave_account/endpoints/error_body.h" +#include "brave/components/brave_account/endpoints/verify_init_bodies.h" + +namespace brave_account::endpoints { + +using VerifyInit = endpoint_client::BraveEndpoint< + "accounts.bsg", + "/v2/verify/init", + endpoint_client::POST, + endpoint_client::Response>; + +} // namespace brave_account::endpoints + +#endif // BRAVE_COMPONENTS_BRAVE_ACCOUNT_ENDPOINTS_VERIFY_INIT_H_ diff --git a/components/brave_account/endpoints/verify_init_bodies.idl b/components/brave_account/endpoints/verify_init_bodies.idl new file mode 100644 index 00000000000..a94b7fefc7c --- /dev/null +++ b/components/brave_account/endpoints/verify_init_bodies.idl @@ -0,0 +1,18 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +// Schema for the /v2/verify/init request and response. +namespace endpoints { + dictionary VerifyInitRequestBody { + DOMString email; + DOMString intent; + DOMString locale; + DOMString service; + }; + + dictionary VerifyInitSuccessBody { + DOMString verificationToken; + }; +}; diff --git a/components/brave_account/endpoints/verify_init_unittest.cc b/components/brave_account/endpoints/verify_init_unittest.cc new file mode 100644 index 00000000000..5ba29421235 --- /dev/null +++ b/components/brave_account/endpoints/verify_init_unittest.cc @@ -0,0 +1,126 @@ +/* Copyright (c) 2026 The Brave Authors. All rights reserved. + * 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 https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_account/endpoints/verify_init.h" + +#include "base/no_destructor.h" +#include "base/types/expected.h" +#include "brave/components/brave_account/endpoints/endpoint_test.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace brave_account::endpoints { + +bool operator==(const VerifyInit::Response::SuccessBody& lhs, + const VerifyInit::Response::SuccessBody& rhs) { + return lhs.verification_token == rhs.verification_token; +} + +namespace { + +using VerifyInitTestCase = EndpointTestCase; + +const VerifyInitTestCase* Success() { + static const base::NoDestructor kSuccess( + {.test_name = "success", + .http_status_code = net::HTTP_OK, + .raw_response_body = R"({ "verificationToken": "eyJhbGciOiJFUz" })", + .expected_response = { + .net_error = net::OK, .status_code = net::HTTP_OK, .body = [] { + VerifyInit::Response::SuccessBody body; + body.verification_token = "eyJhbGciOiJFUz"; + return body; + }()}}); + + return kSuccess.get(); +} + +// clang-format off +// application/json errors: +// - HTTP 400: +// - { "code": null, "error": "Bad Request", "status": 400 } +// - { "code": 13001, "error": "too many pending verification requests for email", "status": 400 } +// - { "code": 13003, "error": "intent not allowed", "status": 400 } +// - { "code": 13004, "error": "account already exists", "status": 400 } +// - { "code": 13005, "error": "account does not exist", "status": 400 } +// - { "code": 13006, "error": "email domain is not supported", "status": 400 } +// - { "code": 13007, "error": "failed to send email due to invalid format", "status": 400 } +// - HTTP 401: +// - { "code": null, "error": "Unauthorized", "status": 401 } +// - HTTP 403: +// - { "code": 14007, "error": "invalid token audience", "status": 403 } +// - HTTP 5XX: +// - { "code": null, "error": "Internal Server Error", "status": <5xx> } +// clang-format on +const VerifyInitTestCase* ApplicationJsonErrorCodeIsNull() { + static const base::NoDestructor + kApplicationJsonErrorCodeIsNull( + {.test_name = "application_json_error_code_is_null", + .http_status_code = net::HTTP_BAD_REQUEST, + .raw_response_body = + R"({ "code": null, + "error": "Bad Request", + "status": 400 })", + .expected_response = {.net_error = net::OK, + .status_code = net::HTTP_BAD_REQUEST, + .body = base::unexpected([] { + VerifyInit::Response::ErrorBody body; + body.code = base::Value(); + return body; + }())}}); + return kApplicationJsonErrorCodeIsNull.get(); +} + +const VerifyInitTestCase* ApplicationJsonErrorCodeIsNotNull() { + static const base::NoDestructor + kApplicationJsonErrorCodeIsNotNull( + {.test_name = "application_json_error_code_is_not_null", + .http_status_code = net::HTTP_FORBIDDEN, + .raw_response_body = + R"({ "code": 14007, + "error": "invalid token audience", + "status": 403 })", + .expected_response = {.net_error = net::OK, + .status_code = net::HTTP_FORBIDDEN, + .body = base::unexpected([] { + VerifyInit::Response::ErrorBody body; + body.code = base::Value(14007); + return body; + }())}}); + return kApplicationJsonErrorCodeIsNotNull.get(); +} + +// non-application/json errors: +// - HTTP 5XX: +// - plain text errors returned by AWS/load balancer +const VerifyInitTestCase* NonApplicationJsonError() { + static const base::NoDestructor kNonApplicationJsonError( + {.test_name = "non_application_json_error", + .http_status_code = net::HTTP_INTERNAL_SERVER_ERROR, + .raw_response_body = "non-application/json error", + .expected_response = {.net_error = net::OK, + .status_code = net::HTTP_INTERNAL_SERVER_ERROR, + .body = std::nullopt}}); + return kNonApplicationJsonError.get(); +} + +using VerifyInitTest = EndpointTest; + +} // namespace + +TEST_P(VerifyInitTest, HandlesReplies) { + RunTestCase(); +} + +INSTANTIATE_TEST_SUITE_P(VerifyInitTestCases, + VerifyInitTest, + testing::Values(Success(), + ApplicationJsonErrorCodeIsNull(), + ApplicationJsonErrorCodeIsNotNull(), + NonApplicationJsonError()), + VerifyInitTest::kNameGenerator); + +} // namespace brave_account::endpoints