Add /validate endpoint to admin REST api

This commit is contained in:
Lee *!* Clagett 2023-08-28 22:07:16 -04:00
parent 2872ba9ecb
commit 46b676b0f0
4 changed files with 122 additions and 1 deletions

View file

@ -68,6 +68,7 @@ are:
* [**modify_account_status**](#modify_account_status): `{"status": "active"|"hidden"|"inactive", "addresses":[...]}` * [**modify_account_status**](#modify_account_status): `{"status": "active"|"hidden"|"inactive", "addresses":[...]}`
* [**reject_requests**](#reject_requests): `{"type": "import"|"create", "addresses":[...]}` * [**reject_requests**](#reject_requests): `{"type": "import"|"create", "addresses":[...]}`
* [**rescan**](#rescan): `{"height":..., "addresses":[...]}` * [**rescan**](#rescan): `{"height":..., "addresses":[...]}`
* [**validate**](#validate): `{"spend_public_hex":..., "view_public_hex":..., "view_key_hex":...}`
* [**webhook_add**](#webhook_add): `{"type":"tx-confirmation", "address":"...", "url":"...", ...}` with optional fields: * [**webhook_add**](#webhook_add): `{"type":"tx-confirmation", "address":"...", "url":"...", ...}` with optional fields:
* **token**: A string to be returned when the webhook is triggered * **token**: A string to be returned when the webhook is triggered
* **payment_id**: 16 hex characters representing a unique identifier for a transaction * **payment_id**: 16 hex characters representing a unique identifier for a transaction
@ -143,6 +144,46 @@ information from that endpoint on how to use this one.
This tells the scanner to rescan specific account(s) from the specified This tells the scanner to rescan specific account(s) from the specified
height. height.
### validate
This takes the spend_public, view_public, and view key all as hex, and then
does basic validation for the caller: (1) that each value is 64 hex-ascii
characters, (2) that the public keys are valid ed25519 points, and that (3)
the view_key matches the view_public.
The return value is the `address` on success, and `error` object on
validation failure:
#### Example Request
```json
{
"params": {
"view_public_hex": "3e77c1ee5a396cd6c3f68ce343882b2a09317649d6501078911fb17a1adac0b6",
"spend_public_hex": "7028d918af2cc4f1cfa499cca2ef014e57124982b99004e9630caf0b25c13954",
"view_key_hex": "80..."
},
"auth": "f50922f5fcd186eaa4bd7070b8072b66fea4fd736f06bd82df702e2314187d09"
}
```
#### Example Failure Return
```json
{
"error": {
"field": "view_public_hex",
"details": "Invalid public key format"
}
}
```
HTTP error codes are still returned if the JSON itself is invalid.
#### Example Success Return
```json
{
"address": "9wRAu3giCtKhSsVnkZJ7LLE6zqzrmMKpPg39S8aoC7T6F6GobeDpz8TcvVfTQT3ucW82oTYKG8v3ZMAeh8SZVXWwMdvwZew"
}
```
### webhook_add ### webhook_add
This is used to track events happening in the database: (1) a new payment to This is used to track events happening in the database: (1) a new payment to
an optional payment_id, or (2) a new account creation. This endpoint always an optional payment_id, or (2) a new account creation. This endpoint always

View file

@ -777,6 +777,7 @@ namespace lws
{"/modify_account_status", call_admin<rpc::modify_account_>, 50 * 1024}, {"/modify_account_status", call_admin<rpc::modify_account_>, 50 * 1024},
{"/reject_requests", call_admin<rpc::reject_requests_>, 50 * 1024}, {"/reject_requests", call_admin<rpc::reject_requests_>, 50 * 1024},
{"/rescan", call_admin<rpc::rescan_>, 50 * 1024}, {"/rescan", call_admin<rpc::rescan_>, 50 * 1024},
{"/validate", call_admin<rpc::validate_>, 50 * 1024},
{"/webhook_add", call_admin<rpc::webhook_add_>, 50 * 1024}, {"/webhook_add", call_admin<rpc::webhook_add_>, 50 * 1024},
{"/webhook_delete", call_admin<rpc::webhook_delete_>, 50 * 1024}, {"/webhook_delete", call_admin<rpc::webhook_delete_>, 50 * 1024},
{"/webhook_delete_uuid", call_admin<rpc::webhook_del_uuid_>,50 * 1024}, {"/webhook_delete_uuid", call_admin<rpc::webhook_del_uuid_>,50 * 1024},

View file

@ -167,6 +167,10 @@ namespace lws { namespace rpc
{ {
read_addresses(source, self, WIRE_FIELD(height)); read_addresses(source, self, WIRE_FIELD(height));
} }
void read_bytes(wire::reader& source, validate_req& self)
{
wire::object(source, WIRE_FIELD(spend_public_hex), WIRE_FIELD(view_public_hex), WIRE_FIELD(view_key_hex));
}
void read_bytes(wire::reader& source, webhook_add_req& self) void read_bytes(wire::reader& source, webhook_add_req& self)
{ {
boost::optional<std::string> address; boost::optional<std::string> address;
@ -237,6 +241,67 @@ namespace lws { namespace rpc
return write_addresses(dest, disk.rescan(req.height, epee::to_span(req.addresses))); return write_addresses(dest, disk.rescan(req.height, epee::to_span(req.addresses)));
} }
namespace
{
struct validate_error
{
std::string field;
std::string details;
};
void write_bytes(wire::writer& dest, const validate_error& self)
{
wire::object(dest, WIRE_FIELD(field), WIRE_FIELD(details));
}
expect<void> output_error(wire::writer& dest, std::string field, std::string details)
{
wire::object(dest, wire::field("error", validate_error{std::move(field), std::move(details)}));
return success();
}
template<typename T>
bool convert_key(wire::writer& dest, T& out, const boost::string_ref in, const boost::string_ref field)
{
if (in.size() != sizeof(out) * 2)
{
output_error(dest, std::string{field}, "Expected " + std::to_string(sizeof(out) * 2) + " characters");
return false;
}
if (!epee::from_hex::to_buffer(epee::as_mut_byte_span(out), in))
{
output_error(dest, std::string{field}, "Invalid hex");
return false;
}
return true;
}
}
expect<void> validate_::operator()(wire::writer& dest, const db::storage&, const request& req) const
{
db::account_address address{};
crypto::secret_key view_key{};
if (!convert_key(dest, address.spend_public, req.spend_public_hex, "spend_public_hex"))
return success(); // error is delivered in JSON as opposed to HTTP codes
if (!convert_key(dest, address.view_public, req.view_public_hex, "view_public_hex"))
return success();
if (!convert_key(dest, unwrap(unwrap(view_key)), req.view_key_hex, "view_key_hex"))
return success();
if (!crypto::check_key(address.spend_public))
return output_error(dest, "spend_public_hex", "Invalid public key format");
if (!crypto::check_key(address.view_public))
return output_error(dest, "view_public_hex", "Invalid public key format");
crypto::public_key test{};
if (!crypto::secret_key_to_public_key(view_key, test) || test != address.view_public)
return output_error(dest, "view_key_hex", "view_key and view_public do not match");
wire::object(dest, wire::field("address", db::address_string(address)));
return success();
}
expect<void> webhook_add_::operator()(wire::writer& dest, db::storage disk, request&& req) const expect<void> webhook_add_::operator()(wire::writer& dest, db::storage disk, request&& req) const
{ {
switch (req.type) switch (req.type)

View file

@ -70,6 +70,14 @@ namespace rpc
}; };
void read_bytes(wire::reader&, rescan_req&); void read_bytes(wire::reader&, rescan_req&);
struct validate_req
{
std::string spend_public_hex;
std::string view_public_hex;
std::string view_key_hex;
};
void read_bytes(wire::reader&, validate_req&);
struct webhook_add_req struct webhook_add_req
{ {
std::string url; std::string url;
@ -147,6 +155,13 @@ namespace rpc
}; };
constexpr const rescan_ rescan{}; constexpr const rescan_ rescan{};
struct validate_
{
using request = validate_req;
expect<void> operator()(wire::writer& dest, const db::storage&, const request& req) const;
};
constexpr const validate_ validate{};
struct webhook_add_ struct webhook_add_
{ {
using request = webhook_add_req; using request = webhook_add_req;
@ -177,5 +192,4 @@ namespace rpc
{ return (*this)(dest, std::move(disk)); } { return (*this)(dest, std::move(disk)); }
}; };
constexpr const webhook_list_ webhook_list{}; constexpr const webhook_list_ webhook_list{};
}} // lws // rpc }} // lws // rpc