Files
sync-server-v2/source/routes/account.ts
T

105 lines
3.2 KiB
TypeScript

import { z } from "zod";
import type { AuthSecp256k1 } from '../auth/auth.ts';
import type { Database } from "../services/storage/database.ts";
import type { RouteDefinition, RouteStream } from './types.ts';
import type { Accounts } from '../auth/accounts.ts';
import { HTTP_STATUS_CODE_NOT_IMPLEMENTED, HTTP_STATUS_CODE_SUCCESS } from "../constants.ts";
import { UnauthorizedError } from '../errors/index.ts';
const accountSchema = z.object({
publicKey: z.string(),
signature: z.string(),
timestamp: z.coerce.number(),
});
const setBalanceSchema = z.object({
amount: z.number(),
});
export class AccountRoute {
constructor(private readonly database: Database, private readonly auth: AuthSecp256k1, private readonly accounts: Accounts) {}
async getRoutes(): Promise<Array<RouteDefinition>> {
return [
{
url: '/account',
handler: this.getAccount.bind(this),
},
{
url: '/account/balance',
handler: this.getAccount.bind(this),
},
{
url: '/account/setbalance',
handler: this.setBalance.bind(this),
},
// This one may not make sense. It could be a large overhead for something that most wont use?
// Maybe make this an optional endpoint or one that we don't support, but the client can try to hit or something?
{
url: '/account/ledger',
handler: this.getLedger.bind(this),
}
];
}
async getAccount(stream: RouteStream): Promise<void> {
// Get the public key, signature and timestamp from the headers
const { publicKey, signature, timestamp } = accountSchema.parse(stream.headers);
// Create the canonical payload for the signature verification
const payload = `${stream.path}:${timestamp}`;
// Verify the signature
const verified = await this.auth.verifySignature(publicKey, signature, payload);
if (!verified) {
throw new UnauthorizedError('Invalid signature');
}
// Get the account's balance from the database
const balance = await this.accounts.getBalance(publicKey);
// Send the balance to the client
stream.send({
statusCode: HTTP_STATUS_CODE_SUCCESS,
body: balance,
});
}
async setBalance(stream: RouteStream): Promise<void> {
const { publicKey, signature, timestamp } = accountSchema.parse(stream.headers);
const { amount } = setBalanceSchema.parse(stream.body);
// Create the canonical payload for the signature verification
const payload = `${stream.path}:${timestamp}`;
// Verify the signature
const verified = await this.auth.verifySignature(publicKey, signature, payload);
if (!verified) {
throw new UnauthorizedError('Invalid signature');
}
// Get the account's balance from the database
const balance = await this.accounts.getBalance(publicKey);
// Set the balance to the database
await this.accounts.setBalance(publicKey, amount);
// Send the balance to the client
stream.send({
statusCode: HTTP_STATUS_CODE_SUCCESS,
body: balance,
});
}
async getLedger(stream: RouteStream): Promise<void> {
stream.send({
statusCode: HTTP_STATUS_CODE_NOT_IMPLEMENTED,
body: 'Not implemented',
});
}
}