Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ ENABLE_HTTPS="true"
REDIS_URL="redis://127.0.0.1:6379/0"
THIRDWEB_API_SECRET_KEY="my-thirdweb-secret-key"

TEST_AWS_KMS_KEY_ID=""
TEST_AWS_KMS_ACCESS_KEY_ID=""
TEST_AWS_KMS_SECRET_ACCESS_KEY=""
TEST_AWS_KMS_REGION=""
TEST_AWS_KMS_KEY_ID="UNIMPLEMENTED"
TEST_AWS_KMS_ACCESS_KEY_ID="UNIMPLEMENTED"
TEST_AWS_KMS_SECRET_ACCESS_KEY="UNIMPLEMENTED"
TEST_AWS_KMS_REGION="UNIMPLEMENTED"

TEST_GCP_KMS_RESOURCE_PATH=""
TEST_GCP_KMS_EMAIL=""
TEST_GCP_KMS_PK=""
TEST_GCP_KMS_RESOURCE_PATH="UNIMPLEMENTED"
TEST_GCP_KMS_EMAIL="UNIMPLEMENTED"
TEST_GCP_KMS_PK="UNIMPLEMENTED"
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"prool": "^0.0.16",
"superjson": "^2.2.1",
"thirdweb": "5.61.3",
"undici": "^6.20.1",
"uuid": "^9.0.1",
"winston": "^3.14.1",
"zod": "^3.23.8"
Expand Down
16 changes: 8 additions & 8 deletions src/db/configuration/getConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,7 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
// TODO: Remove backwards compatibility with next breaking change
if (awsAccessKeyId && awsSecretAccessKey && awsRegion) {
// First try to load the aws secret using the encryption password
let decryptedSecretAccessKey = decrypt(
awsSecretAccessKey,
env.ENCRYPTION_PASSWORD,
);
let decryptedSecretAccessKey = decrypt(awsSecretAccessKey);

// If that fails, try to load the aws secret using the thirdweb api secret key
if (!awsSecretAccessKey) {
Expand Down Expand Up @@ -115,10 +112,7 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
// TODO: Remove backwards compatibility with next breaking change
if (gcpApplicationCredentialEmail && gcpApplicationCredentialPrivateKey) {
// First try to load the gcp secret using the encryption password
let decryptedGcpKey = decrypt(
gcpApplicationCredentialPrivateKey,
env.ENCRYPTION_PASSWORD,
);
let decryptedGcpKey = decrypt(gcpApplicationCredentialPrivateKey);

// If that fails, try to load the gcp secret using the thirdweb api secret key
if (!gcpApplicationCredentialPrivateKey) {
Expand Down Expand Up @@ -172,6 +166,12 @@ const toParsedConfig = async (config: Configuration): Promise<ParsedConfig> => {
gcp: gcpWalletConfiguration,
legacyWalletType_removeInNextBreakingChange,
},
mtlsCertificate: config.mtlsCertificateEncrypted
? decrypt(config.mtlsCertificateEncrypted)
: null,
mtlsPrivateKey: config.mtlsPrivateKeyEncrypted
? decrypt(config.mtlsPrivateKeyEncrypted)
: null,
};
};

Expand Down
2 changes: 1 addition & 1 deletion src/db/configuration/updateConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { encrypt } from "../../utils/crypto";
import { prisma } from "../client";

export const updateConfiguration = async (
data: Prisma.ConfigurationUpdateArgs["data"],
data: Prisma.ConfigurationUpdateInput,
) => {
return prisma.configuration.update({
where: {
Expand Down
26 changes: 7 additions & 19 deletions src/db/webhooks/createWebhook.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { Webhooks } from "@prisma/client";
import { createHash, randomBytes } from "crypto";
import { WebhooksEventTypes } from "../../schema/webhooks";
import type { Prisma, Webhooks } from "@prisma/client";
import { createHash, randomBytes } from "node:crypto";
import { prisma } from "../client";

interface CreateWebhooksParams {
url: string;
name?: string;
eventType: WebhooksEventTypes;
}

export const insertWebhook = async ({
url,
name,
eventType,
}: CreateWebhooksParams): Promise<Webhooks> => {
// generate random bytes
export const insertWebhook = async (
args: Omit<Prisma.WebhooksCreateInput, "secret">,
): Promise<Webhooks> => {
// Generate a webhook secret.
const bytes = randomBytes(4096);
// hash the bytes to create the secret (this will not be stored by itself)
const secret = createHash("sha512").update(bytes).digest("base64url");

return prisma.webhooks.create({
data: {
url,
name,
eventType,
...args,
secret,
},
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "configuration" ADD COLUMN "mtlsCertificateEncrypted" TEXT,
ADD COLUMN "mtlsPrivateKeyEncrypted" TEXT;
20 changes: 12 additions & 8 deletions src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ model Configuration {
accessControlAllowOrigin String @default("https://thirdweb.com,https://embed.ipfscdn.io") @map("accessControlAllowOrigin")
ipAllowlist String[] @default([]) @map("ipAllowlist")
clearCacheCronSchedule String @default("*/30 * * * * *") @map("clearCacheCronSchedule")
// mTLS support
mtlsCertificateEncrypted String?
mtlsPrivateKeyEncrypted String?

@@map("configuration")
}
Expand Down Expand Up @@ -172,14 +175,15 @@ model Transactions {
}

model Webhooks {
id Int @id @default(autoincrement()) @map("id")
name String? @map("name")
url String @map("url")
secret String @map("secret")
eventType String @map("evenType")
createdAt DateTime @default(now()) @map("createdAt")
updatedAt DateTime @updatedAt @map("updatedAt")
revokedAt DateTime? @map("revokedAt")
id Int @id @default(autoincrement()) @map("id")
name String? @map("name")
url String @map("url")
secret String @map("secret")
eventType String @map("evenType")
createdAt DateTime @default(now()) @map("createdAt")
updatedAt DateTime @updatedAt @map("updatedAt")
revokedAt DateTime? @map("revokedAt")

ContractSubscriptions ContractSubscriptions[]

@@map("webhooks")
Expand Down
4 changes: 4 additions & 0 deletions src/schema/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface ParsedConfig
| "gcpApplicationCredentialEmail"
| "gcpApplicationCredentialPrivateKey"
| "contractSubscriptionsRetryDelaySeconds"
| "mtlsCertificateEncrypted"
| "mtlsPrivateKeyEncrypted"
> {
walletConfiguration: {
aws: AwsWalletConfiguration | null;
Expand All @@ -41,4 +43,6 @@ export interface ParsedConfig
};
contractSubscriptionsRequeryDelaySeconds: string;
chainOverridesParsed: Chain[];
mtlsCertificate: string | null;
mtlsPrivateKey: string | null;
}
2 changes: 1 addition & 1 deletion src/server/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
type ThirdwebAuthUser,
} from "@thirdweb-dev/auth/fastify";
import { AsyncWallet } from "@thirdweb-dev/wallets/evm/wallets/async";
import { createHash } from "crypto";
import type { FastifyInstance } from "fastify";
import type { FastifyRequest } from "fastify/types/request";
import jsonwebtoken, { type JwtPayload } from "jsonwebtoken";
import { createHash } from "node:crypto";
import { validate as uuidValidate } from "uuid";
import { getPermissions } from "../../db/permissions/getPermissions";
import { createToken } from "../../db/tokens/createToken";
Expand Down
14 changes: 9 additions & 5 deletions src/server/routes/configuration/auth/get.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Static, Type } from "@sinclair/typebox";
import { FastifyInstance } from "fastify";
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { getConfig } from "../../../../utils/cache/getConfig";
import { standardResponseSchema } from "../../../schemas/sharedApiSchemas";

export const responseBodySchema = Type.Object({
result: Type.Object({
domain: Type.String(),
authDomain: Type.String(),
mtlsCertificate: Type.Union([Type.String(), Type.Null()]),
// Do not return mtlsPrivateKey.
}),
});

Expand All @@ -27,10 +29,12 @@ export async function getAuthConfiguration(fastify: FastifyInstance) {
},
},
handler: async (req, res) => {
const config = await getConfig();
const { authDomain, mtlsCertificate } = await getConfig();

res.status(StatusCodes.OK).send({
result: {
domain: config.authDomain,
authDomain,
mtlsCertificate,
},
});
},
Expand Down
62 changes: 55 additions & 7 deletions src/server/routes/configuration/auth/update.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Static, Type } from "@sinclair/typebox";
import { FastifyInstance } from "fastify";
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { updateConfiguration } from "../../../../db/configuration/updateConfiguration";
import { getConfig } from "../../../../utils/cache/getConfig";
import { encrypt } from "../../../../utils/crypto";
import { createCustomError } from "../../../middleware/error";
import { standardResponseSchema } from "../../../schemas/sharedApiSchemas";
import { responseBodySchema } from "./get";

export const requestBodySchema = Type.Object({
domain: Type.String(),
});
export const requestBodySchema = Type.Partial(
Type.Object({
authDomain: Type.String(),
mtlsCertificate: Type.String({
description: "Engine certificate used for outbound mTLS requests.",
}),
mtlsPrivateKey: Type.String({
description: "Engine private key used for outbound mTLS requests.",
}),
}),
);

export async function updateAuthConfiguration(fastify: FastifyInstance) {
fastify.route<{
Expand All @@ -29,15 +39,53 @@ export async function updateAuthConfiguration(fastify: FastifyInstance) {
},
},
handler: async (req, res) => {
const { authDomain, mtlsCertificate, mtlsPrivateKey } = req.body;

if (mtlsCertificate) {
if (
!(
mtlsCertificate.startsWith("-----BEGIN CERTIFICATE-----\n") &&
mtlsCertificate.endsWith("\n-----END CERTIFICATE-----")
)
) {
throw createCustomError(
"Invalid mtlsCertificate.",
StatusCodes.BAD_REQUEST,
"INVALID_MTLS_CERTIFICATE",
);
}
}
if (mtlsPrivateKey) {
if (
!(
mtlsPrivateKey.startsWith("-----BEGIN PRIVATE KEY-----\n") &&
mtlsPrivateKey.endsWith("\n-----END PRIVATE KEY-----")
)
) {
throw createCustomError(
"Invalid mtlsPrivateKey.",
StatusCodes.BAD_REQUEST,
"INVALID_MTLS_PRIVATE_KEY",
);
}
}

await updateConfiguration({
authDomain: req.body.domain,
authDomain,
mtlsCertificateEncrypted: mtlsCertificate
? encrypt(mtlsCertificate)
: undefined,
mtlsPrivateKeyEncrypted: mtlsPrivateKey
? encrypt(mtlsPrivateKey)
: undefined,
});

const config = await getConfig(false);

res.status(StatusCodes.OK).send({
result: {
domain: config.authDomain,
authDomain: config.authDomain,
mtlsCertificate: config.mtlsCertificate,
},
});
},
Expand Down
2 changes: 2 additions & 0 deletions src/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import { createWebhookRoute } from "./webhooks/create";
import { getWebhooksEventTypes } from "./webhooks/events";
import { getAllWebhooksData } from "./webhooks/getAll";
import { revokeWebhook } from "./webhooks/revoke";
import { testWebhookRoute } from "./webhooks/test";

export const withRoutes = async (fastify: FastifyInstance) => {
// Backend Wallets
Expand Down Expand Up @@ -158,6 +159,7 @@ export const withRoutes = async (fastify: FastifyInstance) => {
await fastify.register(createWebhookRoute);
await fastify.register(revokeWebhook);
await fastify.register(getWebhooksEventTypes);
await fastify.register(testWebhookRoute);

// Permissions
await fastify.register(getAllPermissions);
Expand Down
6 changes: 3 additions & 3 deletions src/server/routes/webhooks/create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Static, Type } from "@sinclair/typebox";
import { FastifyInstance } from "fastify";
import { Type, type Static } from "@sinclair/typebox";
import type { FastifyInstance } from "fastify";
import { StatusCodes } from "http-status-codes";
import { insertWebhook } from "../../../db/webhooks/createWebhook";
import { WebhooksEventTypes } from "../../../schema/webhooks";
Expand Down Expand Up @@ -45,7 +45,7 @@ export async function createWebhookRoute(fastify: FastifyInstance) {
method: "POST",
url: "/webhooks/create",
schema: {
summary: "Create a webhook",
summary: "Create webhook",
description:
"Create a webhook to call when a specific Engine event occurs.",
tags: ["Webhooks"],
Expand Down
Loading
Loading