Skip to content

Commit

Permalink
Add new api file which verifies signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
justinbarry committed Apr 17, 2024
1 parent a999bfc commit 49205d0
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 37 deletions.
1 change: 1 addition & 0 deletions apps/demo-app/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
5 changes: 4 additions & 1 deletion apps/demo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
},
"dependencies": {
"@burnt-labs/abstraxion": "workspace:*",
"@burnt-labs/constants": "workspace:*",
"@burnt-labs/signers": "workspace:*",
"@burnt-labs/ui": "workspace:*",
"@cosmjs/amino": "^0.32.3",
"@cosmjs/cosmwasm-stargate": "^0.32.2",
"@keplr-wallet/cosmos": "^0.12.80",
"next": "^14.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
Expand All @@ -31,4 +34,4 @@
"tailwindcss": "^3.2.4",
"typescript": "^5.2.2"
}
}
}
189 changes: 189 additions & 0 deletions apps/demo-app/pages/api/check-signature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { verifyADR36Amino } from "@keplr-wallet/cosmos";

// This import will need to change based on the chain you are confirming against.
import { testnetChainInfo } from "@burnt-labs/constants";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { Pubkey } from "@cosmjs/amino";

interface GrantsResponse {
grants: Grant[];
pagination: Pagination;
}

interface Grant {
granter: string;
grantee: string;
authorization: Authorization;
expiration: string;
}

interface Authorization {
"@type": string;
grants: GrantAuthorization[];
}

interface GrantAuthorization {
contract: string;
limit: Limit;
filter: Filter;
}

interface Limit {
"@type": string;
remaining: string;
}

interface Filter {
"@type": string;
}

interface Pagination {
next_key: null | string;
total: string;
}

function isString(test: any): test is string {
return typeof test === "string";
}

async function checkGrants(granter: string, grantee: string): Promise<Boolean> {
const res = await fetch(
`${testnetChainInfo.rest}/cosmos/authz/v1beta1/grants/grantee/${granter}`,
{
cache: "no-store",
},
);
const data = (await res.json()) as GrantsResponse;
return data.grants.map((grant) => grant.grantee).includes(grantee);
}

function checkSignature(
address: string,
pubKey: Pubkey,
messageString: string,
signature: string,
): boolean {
const msg = Buffer.from(messageString, "hex").toString();

const signatureBuffer = Buffer.from(signature, "base64");

const uint8Signature = new Uint8Array(signatureBuffer); // Convert the buffer to an Uint8Array

const pubKeyValueBuffer = Buffer.from(pubKey, "base64"); // Decode the base64 encoded value

const pubKeyUint8Array = new Uint8Array(pubKeyValueBuffer); // Convert the buffer to an Uint8Array

return verifyADR36Amino(
testnetChainInfo.bech32Config.bech32PrefixAccAddr,
address,
msg,
pubKeyUint8Array,
uint8Signature,
);
}

export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
const { query, method } = req;
const client = await CosmWasmClient.connect(testnetChainInfo.rpc);

if (method === "GET") {
const { userSessionAddress, metaAccountAddress, message, signature } =
query;

const errors: string[] = [];
if (!userSessionAddress) {
errors.push("userSessionAddress is required");
}

if (!metaAccountAddress && typeof metaAccountAddress === "string") {
errors.push("itemId is required");
}

if (!message && typeof message === "string") {
errors.push("message is required");
}

if (!signature && typeof signature === "string") {
errors.push("signature is required");
}

if (errors.length > 0) {
res.status(400).json({ errors });
return;
}

// These aid TS type inference. You can pull in a more complex validation package to handle this like io-ts
if (!isString(userSessionAddress)) {
res
.status(400)
.json({ errors: ["userSessionAddress is required to be a string"] });
return;
}

if (!isString(metaAccountAddress)) {
res
.status(400)
.json({ errors: ["metaAccountAddress is required to be a string"] });
return;
}

if (!isString(message)) {
res.status(400).json({ errors: ["message is required to be a string"] });
return;
}

if (!isString(signature)) {
res
.status(400)
.json({ errors: ["signature is required to be a string"] });
return;
}

/*
* Confirming account "ownership" is a three-step process
* 1. Confirm the signature passed is valid for the temporary userSessionAddress
* 2. Pull any grant bestowed to the userSessionAddress on chain
* 3. Check that AT LEAST one grant exists of any type exists between the userSessionAddress and the metaAccountAddress
**/
// Need to get the pub key from the chain.
const account = await client.getAccount(userSessionAddress);
if (!account) {
res.status(404).json({
errors: ["account not found"],
});
return;
}
const { pubkey } = account;
if (!pubkey) {
res.status(404).json({
errors: ["public key not found"],
});
return;
}

const isValid = checkSignature(
userSessionAddress,
pubkey.value,
message,
signature,
);
if (!isValid) {
res.status(400).json({
errors: ["invalid signature"],
});
return;
}

// handle GET request
res.status(200).json({
valid: await checkGrants(metaAccountAddress, userSessionAddress),
});
} else {
res.setHeader("Allow", ["GET"]);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
20 changes: 16 additions & 4 deletions apps/demo-app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
{
"extends": "@burnt-labs/tsconfig/nextjs.json",
"compilerOptions": {
"plugins": [{ "name": "next" }],
"moduleResolution": "Bundler"
"plugins": [
{
"name": "next"
}
],
"moduleResolution": "Bundler",
"strictNullChecks": true
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading

0 comments on commit 49205d0

Please sign in to comment.