Skip to content

Commit

Permalink
feat(api): relayer config auth (#1322)
Browse files Browse the repository at this point in the history
* wip

* updates

Signed-off-by: james-a-morris <[email protected]>

* feat(api): add relayer config enpoint

Signed-off-by: Pablo Maldonado <[email protected]>

* feat(api): get relayer address and check whitelist

Signed-off-by: Pablo Maldonado <[email protected]>

* refactor: comments

Signed-off-by: Pablo Maldonado <[email protected]>

* Update api/relayer-config.ts

Co-authored-by: James Morris, MS <[email protected]>

* refactor: organise files

Signed-off-by: Pablo Maldonado <[email protected]>

* refactor: rename func

Signed-off-by: Pablo Maldonado <[email protected]>

* feat: fixes and tests

Signed-off-by: Pablo Maldonado <[email protected]>

* feat: remove query

Signed-off-by: Pablo Maldonado <[email protected]>

* feat: types

Signed-off-by: Pablo Maldonado <[email protected]>

* feat: add config types

Signed-off-by: Pablo Maldonado <[email protected]>

* fix: types

Signed-off-by: Pablo Maldonado <[email protected]>

* feat: simplify

Signed-off-by: Pablo Maldonado <[email protected]>

* Update api/_types/exclusivity.types.ts

Co-authored-by: James Morris, MS <[email protected]>

* Update api/relayer-config.ts

Co-authored-by: James Morris, MS <[email protected]>

* feat: simplify handler

Signed-off-by: Pablo Maldonado <[email protected]>

---------

Signed-off-by: james-a-morris <[email protected]>
Signed-off-by: Pablo Maldonado <[email protected]>
Co-authored-by: Paul <[email protected]>
Co-authored-by: james-a-morris <[email protected]>
Co-authored-by: James Morris, MS <[email protected]>
  • Loading branch information
4 people authored Dec 11, 2024
1 parent 86c454d commit 79b1d5d
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 14 deletions.
22 changes: 22 additions & 0 deletions api/_exclusivity/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ethers } from "ethers";

export const MAX_MESSAGE_AGE_SECONDS = 300;

// TODO: get this from gh
export const getWhiteListedRelayers = () => {
return [
"0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D", // dev wallet
];
};

export const getRelayerFromSignature = (signature: string, message: string) => {
return ethers.utils.verifyMessage(message, signature);
};

export const isTimestampValid = (
timestamp: number,
maxAgeSeconds: number
): boolean => {
const currentTime = Math.floor(Date.now() / 1000);
return currentTime - timestamp <= maxAgeSeconds;
};
11 changes: 11 additions & 0 deletions api/_types/exclusivity.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { array, boolean, Infer, object, optional } from "superstruct";
import { positiveFloatStr, positiveIntStr, validAddress } from "../_utils";
import { TypedVercelRequest } from "./generic.types";

export const RelayerFillLimitSchema = object({
originChainId: positiveIntStr(),
Expand All @@ -18,6 +19,16 @@ export const RelayerFillLimitArraySchema = array(RelayerFillLimitSchema);

export type RelayerFillLimit = Infer<typeof RelayerFillLimitSchema>;

export type RelayerConfigUpdate = {
timestamp: number;
relayerFillLimits: RelayerFillLimit[];
};

export type TypedRelayerConfigUpdateRequest = TypedVercelRequest<
never,
RelayerConfigUpdate
>;

// // Example config.
// export const RelayerConfigUpdate: RelayerFillLimit[] = [
// {
Expand Down
5 changes: 3 additions & 2 deletions api/_types/generic.types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { VercelRequest } from "@vercel/node";

export type TypedVercelRequest<T> = VercelRequest & {
query: Partial<T>;
export type TypedVercelRequest<Query, Body = any> = VercelRequest & {
query: Partial<Query>;
body: Partial<Body>;
};
38 changes: 26 additions & 12 deletions api/relayer-config.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { VercelResponse } from "@vercel/node";
import { TypedVercelRequest } from "./_types";
import {
getRelayerFromSignature,
getWhiteListedRelayers,
isTimestampValid,
MAX_MESSAGE_AGE_SECONDS,
} from "./_exclusivity/utils";
import { RelayerConfigUpdate, TypedRelayerConfigUpdateRequest } from "./_types";

const handler = async (
request: TypedVercelRequest<any>,
request: TypedRelayerConfigUpdateRequest,
response: VercelResponse
) => {
if (request.method === "GET") {
// Handle GET request
response.status(200).json({ message: "GET request received" });
} else if (request.method === "POST") {
// Handle POST request
response.status(200).json({ message: "POST request received" });
} else {
// Method not allowed
response.setHeader("Allow", ["GET", "POST"]);
response.status(405).end(`Method ${request.method} Not Allowed`);
if (request.method !== "POST") {
return response.status(405).end(`Method ${request.method} Not Allowed`);
}
const body = request.body as RelayerConfigUpdate;
const { authorization } = request.headers;
const { timestamp } = body;
if (!isTimestampValid(timestamp, MAX_MESSAGE_AGE_SECONDS)) {
return response.status(400).json({ message: "Message too old" });
}

if (
!authorization ||
!getWhiteListedRelayers().includes(
getRelayerFromSignature(authorization, JSON.stringify(body))
)
) {
return response.status(401).json({ message: "Unauthorized" });
}
return response.status(200).json({ message: "POST request received" });
};

export default handler;
91 changes: 91 additions & 0 deletions test/api/relayer-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { VercelResponse } from "@vercel/node";
import { ethers } from "ethers";
import * as utils from "../../api/_exclusivity/utils";
import { TypedRelayerConfigUpdateRequest } from "../../api/_types";
import handler from "../../api/relayer-config";
const { MAX_MESSAGE_AGE_SECONDS } = utils;

const getMockedResponse = () => {
const response: any = {};
response.status = jest.fn().mockReturnValue(response);
response.json = jest.fn();
response.setHeader = jest.fn();
return response as VercelResponse;
};

describe("Relayer Config API", () => {
let response: VercelResponse;
const whitelistedRelayer = ethers.Wallet.createRandom();
const unauthorizedRelayer = ethers.Wallet.createRandom();

beforeEach(() => {
response = getMockedResponse();
jest
.spyOn(utils, "getWhiteListedRelayers")
.mockReturnValue([whitelistedRelayer.address]);
});

test("POST request with valid timestamp", async () => {
const message = {
timestamp: Date.now() / 1000,
};
const messageString = JSON.stringify(message);
const signature = await whitelistedRelayer.signMessage(messageString);

const request = {
method: "POST",
headers: {
authorization: signature,
},
body: message,
} as TypedRelayerConfigUpdateRequest;

await handler(request, response);

expect(response.status).toHaveBeenCalledWith(200);
});

test("POST request with invalid timestamp", async () => {
const message = {
timestamp: Date.now() / 1000 - MAX_MESSAGE_AGE_SECONDS - 1,
};
const signature = await whitelistedRelayer.signMessage(
JSON.stringify(message)
);

const request = {
method: "POST",
headers: {
authorization: signature,
},
body: message,
} as TypedRelayerConfigUpdateRequest;

await handler(request, response);

expect(response.status).toHaveBeenCalledWith(400);
expect(response.json).toHaveBeenCalledWith({ message: "Message too old" });
});

test("POST request with invalid signature", async () => {
const message = {
timestamp: Date.now() / 1000,
};
const signature = await unauthorizedRelayer.signMessage(
JSON.stringify(message)
);

const request = {
method: "POST",
headers: {
authorization: signature,
},
body: message,
} as TypedRelayerConfigUpdateRequest;

await handler(request, response);

expect(response.status).toHaveBeenCalledWith(401);
expect(response.json).toHaveBeenCalledWith({ message: "Unauthorized" });
});
});

0 comments on commit 79b1d5d

Please sign in to comment.