Skip to content

Commit 3d1dd12

Browse files
authored
Add support for auth webhooks (#296)
* Add support for auth webhooks * update auth middleware * Update auth webhook url
1 parent 157140c commit 3d1dd12

File tree

4 files changed

+107
-53
lines changed

4 files changed

+107
-53
lines changed

src/schema/webhooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export enum WebhooksEventTypes {
77
CANCELLED_TX = "cancelled_transaction",
88
ALL_TX = "all_transactions",
99
BACKEND_WALLET_BALANCE = "backend_wallet_balance",
10+
AUTH = "auth",
1011
}
1112

1213
export interface SanitizedWebHooksSchema {

src/server/middleware/auth.ts

Lines changed: 96 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import { getPermissions } from "../../db/permissions/getPermissions";
1414
import { createToken } from "../../db/tokens/createToken";
1515
import { getToken } from "../../db/tokens/getToken";
1616
import { revokeToken } from "../../db/tokens/revokeToken";
17+
import { WebhooksEventTypes } from "../../schema/webhooks";
18+
import { getWebhookConfig } from "../../utils/cache/getWebhook";
1719
import { env } from "../../utils/env";
1820
import { logger } from "../../utils/logger";
1921
import { Permission } from "../schemas/auth";
22+
import { sendWebhookRequest } from "../utils/webhook";
2023

2124
export type TAuthData = never;
2225
export type TAuthSession = { permissions: string };
@@ -199,66 +202,115 @@ export const withAuth = async (server: FastifyInstance) => {
199202
// If the secret key is being used, treat the user as the auth wallet
200203
const config = await getConfiguration();
201204
const wallet = new LocalWallet();
202-
await wallet.import({
203-
encryptedJson: config.authWalletEncryptedJson,
204-
password: env.THIRDWEB_API_SECRET_KEY,
205-
});
205+
206+
try {
207+
await wallet.import({
208+
encryptedJson: config.authWalletEncryptedJson,
209+
password: env.ENCRYPTION_PASSWORD,
210+
});
211+
} catch {
212+
// If that fails, we try to load the wallet with the secret key
213+
await wallet.import({
214+
encryptedJson: config.authWalletEncryptedJson,
215+
password: env.THIRDWEB_API_SECRET_KEY,
216+
});
217+
218+
// And then update the auth wallet to use encryption password instead
219+
const encryptedJson = await wallet.export({
220+
strategy: "encryptedJson",
221+
password: env.ENCRYPTION_PASSWORD,
222+
});
223+
224+
logger.worker.info(
225+
`[Encryption] Updating authWalletEncryptedJson to use ENCRYPTION_PASSWORD`,
226+
);
227+
await updateConfiguration({
228+
authWalletEncryptedJson: encryptedJson,
229+
});
230+
}
206231

207232
req.user = {
208233
address: await wallet.getAddress(),
209234
session: {
210235
permissions: Permission.Admin,
211236
},
212237
};
238+
213239
return;
214240
}
215241

216242
// Otherwise, check for an authenticated user
217-
const jwt = getJWT(req);
218-
if (jwt) {
219-
// 1. Check if the token is a valid engine JWT
220-
const token = await getToken({ jwt });
221-
222-
// First, we ensure that the token hasn't been revoked
223-
if (token?.revokedAt === null) {
224-
// Then we perform our standard auth checks for the user
225-
const user = await getUser(req);
226-
227-
// Ensure that the token user is an admin or owner
228-
if (
229-
(user && user?.session?.permissions === Permission.Owner) ||
230-
user?.session?.permissions === Permission.Admin
231-
) {
232-
req.user = user;
233-
return;
243+
try {
244+
const jwt = getJWT(req);
245+
if (jwt) {
246+
// 1. Check if the token is a valid engine JWT
247+
const token = await getToken({ jwt });
248+
249+
// First, we ensure that the token hasn't been revoked
250+
if (token?.revokedAt === null) {
251+
// Then we perform our standard auth checks for the user
252+
const user = await getUser(req);
253+
254+
// Ensure that the token user is an admin or owner
255+
if (
256+
(user && user?.session?.permissions === Permission.Owner) ||
257+
user?.session?.permissions === Permission.Admin
258+
) {
259+
req.user = user;
260+
return;
261+
}
234262
}
235-
}
236263

237-
// 2. Otherwise, check if the token is a valid api-server JWT
238-
const user =
239-
(await authWithApiServer(jwt, "thirdweb.com")) ||
240-
(await authWithApiServer(jwt, "thirdweb-preview.com"));
241-
242-
// If we have an api-server user, return it with the proper permissions
243-
if (user) {
244-
const res = await getPermissions({ walletAddress: user.address });
245-
246-
if (
247-
res?.permissions === Permission.Owner ||
248-
res?.permissions === Permission.Admin
249-
) {
250-
req.user = {
251-
address: user.address,
252-
session: {
253-
permissions: res.permissions,
254-
},
255-
};
256-
return;
264+
// 2. Otherwise, check if the token is a valid api-server JWT
265+
const user =
266+
(await authWithApiServer(jwt, "thirdweb.com")) ||
267+
(await authWithApiServer(jwt, "thirdweb-preview.com"));
268+
269+
// If we have an api-server user, return it with the proper permissions
270+
if (user) {
271+
const res = await getPermissions({ walletAddress: user.address });
272+
273+
if (
274+
res?.permissions === Permission.Owner ||
275+
res?.permissions === Permission.Admin
276+
) {
277+
req.user = {
278+
address: user.address,
279+
session: {
280+
permissions: res.permissions,
281+
},
282+
};
283+
return;
284+
}
257285
}
258286
}
287+
} catch {
288+
// no-op
289+
}
290+
291+
const authWebhooks = await getWebhookConfig(WebhooksEventTypes.AUTH);
292+
if (authWebhooks) {
293+
const authResponses = await Promise.all(
294+
authWebhooks.map((webhook) =>
295+
sendWebhookRequest(webhook, {
296+
url: req.url,
297+
method: req.method,
298+
headers: req.headers,
299+
params: req.params,
300+
query: req.query,
301+
cookies: req.cookies,
302+
body: req.body,
303+
}),
304+
),
305+
);
306+
307+
// If every auth webhook returns true, we allow the request
308+
if (authResponses.every((ok) => !!ok)) {
309+
return;
310+
}
259311
}
260-
} catch {
261-
// no-op
312+
} catch (err: any) {
313+
logger.server.error(`[Auth] ${err?.message || err}`);
262314
}
263315

264316
// If we have no secret key or authenticated user, return 401

src/server/routes/webhooks/create.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ BodySchema.examples = [
7979
name: "Backend Wallet Balance Event",
8080
eventType: WebhooksEventTypes.BACKEND_WALLET_BALANCE,
8181
},
82+
{
83+
url: "http://localhost:3000/auth",
84+
name: "Auth Check",
85+
eventType: WebhooksEventTypes.AUTH,
86+
},
8287
];
8388

8489
const ReplySchema = Type.Object({

src/server/utils/webhook.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Static } from "@sinclair/typebox";
21
import crypto from "crypto";
32
import { getConfiguration } from "../../db/configuration/getConfiguration";
43
import { getTxById } from "../../db/transactions/getTxById";
@@ -9,10 +8,7 @@ import {
98
} from "../../schema/webhooks";
109
import { getWebhookConfig } from "../../utils/cache/getWebhook";
1110
import { logger } from "../../utils/logger";
12-
import {
13-
TransactionStatusEnum,
14-
transactionResponseSchema,
15-
} from "../schemas/transaction";
11+
import { TransactionStatusEnum } from "../schemas/transaction";
1612

1713
let balanceNotificationLastSentAt = -1;
1814

@@ -21,7 +17,7 @@ interface TxWebookParams {
2117
}
2218

2319
export const generateSignature = (
24-
body: Static<typeof transactionResponseSchema> | WalletBalanceWebhookSchema,
20+
body: Record<string, any>,
2521
timestamp: string,
2622
secret: string,
2723
): string => {
@@ -31,7 +27,7 @@ export const generateSignature = (
3127

3228
export const createWebhookRequestHeaders = async (
3329
webhookConfig: SanitizedWebHooksSchema,
34-
body: Static<typeof transactionResponseSchema> | WalletBalanceWebhookSchema,
30+
body: Record<string, any>,
3531
): Promise<HeadersInit> => {
3632
const headers: {
3733
Accept: string;
@@ -56,9 +52,9 @@ export const createWebhookRequestHeaders = async (
5652
return headers;
5753
};
5854

59-
const sendWebhookRequest = async (
55+
export const sendWebhookRequest = async (
6056
webhookConfig: SanitizedWebHooksSchema,
61-
body: Static<typeof transactionResponseSchema> | WalletBalanceWebhookSchema,
57+
body: Record<string, any>,
6258
): Promise<boolean> => {
6359
const headers = await createWebhookRequestHeaders(webhookConfig, body);
6460
const response = await fetch(webhookConfig?.url, {

0 commit comments

Comments
 (0)