105 lines
3.2 KiB
TypeScript
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',
|
|
});
|
|
}
|
|
} |