Add /validate endpoint to admin REST api (#81)

This commit is contained in:
Lee *!* Clagett 2023-09-27 16:50:12 -04:00 committed by Lee *!* Clagett
parent 3b35ce2845
commit 10dc4801d7
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":[...]}`
* [**reject_requests**](#reject_requests): `{"type": "import"|"create", "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:
* **token**: A string to be returned when the webhook is triggered
* **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
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
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

View file

@ -777,6 +777,7 @@ namespace lws
{"/modify_account_status", call_admin<rpc::modify_account_>, 50 * 1024},
{"/reject_requests", call_admin<rpc::reject_requests_>, 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_delete", call_admin<rpc::webhook_delete_>, 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));
}
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)
{
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)));
}
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
{
switch (req.type)

View file

@ -70,6 +70,14 @@ namespace rpc
};
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
{
std::string url;
@ -147,6 +155,13 @@ namespace rpc
};
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_
{
using request = webhook_add_req;
@ -177,5 +192,4 @@ namespace rpc
{ return (*this)(dest, std::move(disk)); }
};
constexpr const webhook_list_ webhook_list{};
}} // lws // rpc