diff --git a/.env.example b/.env.example index e1152dcb0..c463ef2eb 100644 --- a/.env.example +++ b/.env.example @@ -13,9 +13,6 @@ ADMIN_WALLET_ADDRESS="" # PORT="3005" # HOST="0.0.0.0" -# Optional configuration to enable cors, defaults to allow all -# ACCESS_CONTROL_ALLOW_ORIGIN="*" - # Optional configuration to enable https usage for localhost # ENABLE_HTTPS="false" # HTTPS_PASSPHRASE="..." diff --git a/src/db/configuration/getConfiguration.ts b/src/db/configuration/getConfiguration.ts index d340b2f82..fc59565d1 100644 --- a/src/db/configuration/getConfiguration.ts +++ b/src/db/configuration/getConfiguration.ts @@ -1,6 +1,7 @@ import { Configuration } from "@prisma/client"; import { LocalWallet } from "@thirdweb-dev/wallets"; import { WalletType } from "../../schema/wallet"; +import { mandatoryAllowedCorsUrls } from "../../server/utils/cors-urls"; import { decrypt } from "../../utils/crypto"; import { env } from "../../utils/env"; import { logger } from "../../utils/logger"; @@ -184,6 +185,7 @@ export const getConfiguration = async (): Promise => { authDomain: "thirdweb.com", authWalletEncryptedJson: await createAuthWalletEncryptedJson(), minWalletBalance: "20000000000000000", + accessControlAllowOrigin: mandatoryAllowedCorsUrls.join(","), }, update: {}, }); diff --git a/src/prisma/migrations/20240110194551_cors_to_configurations/migration.sql b/src/prisma/migrations/20240110194551_cors_to_configurations/migration.sql new file mode 100644 index 000000000..957eeef1c --- /dev/null +++ b/src/prisma/migrations/20240110194551_cors_to_configurations/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "configuration" ADD COLUMN "accessControlAllowOrigin" TEXT NOT NULL DEFAULT 'https://thirdweb.com,https://embed.ipfscdn.io'; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 764f2e7be..1d21707cc 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -41,6 +41,7 @@ model Configuration { webhookAuthBearerToken String? @map("webhookAuthBearerToken") // Wallet balance minWalletBalance String @default("20000000000000000") @map("minWalletBalance") + accessControlAllowOrigin String @default("https://thirdweb.com,https://embed.ipfscdn.io") @map("accessControlAllowOrigin") @@map("configuration") } diff --git a/src/server/index.ts b/src/server/index.ts index 1cea718a4..ebd539889 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -49,6 +49,8 @@ const main = async () => { ...(env.ENABLE_HTTPS ? httpsObject : {}), }).withTypeProvider(); + server.decorateRequest("corsPreflightEnabled", false); + await withCors(server); await withRequestLogs(server); await withErrorHandler(server); diff --git a/src/server/middleware/auth.ts b/src/server/middleware/auth.ts index 8d004e2f4..e0bbf14f2 100644 --- a/src/server/middleware/auth.ts +++ b/src/server/middleware/auth.ts @@ -167,7 +167,7 @@ export const withAuth = async (server: FastifyInstance) => { server.decorateRequest("user", null); // Add auth validation middleware to check for authenticated requests - server.addHook("onRequest", async (req, res) => { + server.addHook("preHandler", async (req, res) => { if ( req.url === "/favicon.ico" || req.url === "/" || diff --git a/src/server/middleware/cors/cors.ts b/src/server/middleware/cors/cors.ts new file mode 100644 index 000000000..fea9bb190 --- /dev/null +++ b/src/server/middleware/cors/cors.ts @@ -0,0 +1,327 @@ +import { + FastifyInstance, + FastifyReply, + FastifyRequest, + HookHandlerDoneFunction, +} from "fastify"; +import { getConfig } from "../../../utils/cache/getConfig"; +import { + addAccessControlRequestHeadersToVaryHeader, + addOriginToVaryHeader, +} from "./vary"; + +interface ArrayOfValueOrArray extends Array> {} + +type OriginCallback = ( + err: Error | null, + origin: ValueOrArray, +) => void; +type OriginType = string | boolean | RegExp; +type ValueOrArray = T | ArrayOfValueOrArray; +type OriginFunction = ( + origin: string | undefined, + callback: OriginCallback, +) => void; + +interface FastifyCorsOptions { + /** + * Configures the Access-Control-Allow-Origin CORS header. + */ + origin?: ValueOrArray | OriginFunction; + /** + * Configures the Access-Control-Allow-Credentials CORS header. + * Set to true to pass the header, otherwise it is omitted. + */ + credentials?: boolean; + /** + * Configures the Access-Control-Expose-Headers CORS header. + * Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') + * or an array (ex: ['Content-Range', 'X-Content-Range']). + * If not specified, no custom headers are exposed. + */ + exposedHeaders?: string | string[]; + /** + * Configures the Access-Control-Allow-Headers CORS header. + * Expects a comma-delimited string (ex: 'Content-Type,Authorization') + * or an array (ex: ['Content-Type', 'Authorization']). If not + * specified, defaults to reflecting the headers specified in the + * request's Access-Control-Request-Headers header. + */ + allowedHeaders?: string | string[]; + /** + * Configures the Access-Control-Allow-Methods CORS header. + * Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: ['GET', 'PUT', 'POST']). + */ + methods?: string | string[]; + /** + * Configures the Access-Control-Max-Age CORS header. + * Set to an integer to pass the header, otherwise it is omitted. + */ + maxAge?: number; + /** + * Configures the Cache-Control header for CORS preflight responses. + * Set to an integer to pass the header as `Cache-Control: max-age=${cacheControl}`, + * or set to a string to pass the header as `Cache-Control: ${cacheControl}` (fully define + * the header value), otherwise the header is omitted. + */ + cacheControl?: number | string | null; + /** + * Pass the CORS preflight response to the route handler (default: false). + */ + preflightContinue?: boolean; + /** + * Provides a status code to use for successful OPTIONS requests, + * since some legacy browsers (IE11, various SmartTVs) choke on 204. + */ + optionsSuccessStatus?: number; + /** + * Pass the CORS preflight response to the route handler (default: false). + */ + preflight?: boolean; + /** + * Enforces strict requirement of the CORS preflight request headers (Access-Control-Request-Method and Origin). + * Preflight requests without the required headers will result in 400 errors when set to `true` (default: `true`). + */ + strictPreflight?: boolean; + /** + * Hide options route from the documentation built using fastify-swagger (default: true). + */ + hideOptionsRoute?: boolean; +} + +const defaultOptions = { + origin: "*", + methods: "GET,HEAD,PUT,PATCH,POST,DELETE", + preflightContinue: false, + optionsSuccessStatus: 204, + credentials: false, + exposedHeaders: undefined, + allowedHeaders: undefined, + maxAge: undefined, + preflight: true, + strictPreflight: true, +}; + +export const fastifyCors = async ( + fastify: FastifyInstance, + req: FastifyRequest, + reply: FastifyReply, + opts: FastifyCorsOptions, + next: HookHandlerDoneFunction, +) => { + const config = await getConfig(); + + const originArray = config.accessControlAllowOrigin.split(",") as string[]; + opts.origin = originArray; + + let hideOptionsRoute = true; + if (opts.hideOptionsRoute !== undefined) { + hideOptionsRoute = opts.hideOptionsRoute; + } + const corsOptions = normalizeCorsOptions(opts); + addCorsHeadersHandler(fastify, corsOptions, req, reply, next); + + next(); +}; + +function normalizeCorsOptions(opts: FastifyCorsOptions) { + const corsOptions = { ...defaultOptions, ...opts }; + if (Array.isArray(opts.origin) && opts.origin.indexOf("*") !== -1) { + corsOptions.origin = "*"; + } + if (Number.isInteger(corsOptions.cacheControl)) { + // integer numbers are formatted this way + corsOptions.cacheControl = `max-age=${corsOptions.cacheControl}`; + } else if (typeof corsOptions.cacheControl !== "string") { + // strings are applied directly and any other value is ignored + corsOptions.cacheControl = undefined; + } + return corsOptions; +} + +const addCorsHeadersHandler = ( + fastify: FastifyInstance, + options: FastifyCorsOptions, + req: FastifyRequest, + reply: FastifyReply, + next: HookHandlerDoneFunction, +) => { + // Always set Vary header + // https://github.com/rs/cors/issues/10 + addOriginToVaryHeader(reply); + + const resolveOriginOption = + typeof options.origin === "function" + ? resolveOriginWrapper(fastify, options.origin) + : (_: any, cb: any) => cb(null, options.origin); + + resolveOriginOption( + req, + (error: Error | null, resolvedOriginOption: boolean) => { + if (error !== null) { + return next(error); + } + + // Disable CORS and preflight if false + if (resolvedOriginOption === false) { + return next(); + } + + // Falsy values are invalid + if (!resolvedOriginOption) { + return next(new Error("Invalid CORS origin option")); + } + + addCorsHeaders(req, reply, resolvedOriginOption, options); + + if (req.raw.method === "OPTIONS" && options.preflight === true) { + // Strict mode enforces the required headers for preflight + if ( + options.strictPreflight === true && + (!req.headers.origin || !req.headers["access-control-request-method"]) + ) { + reply + .status(400) + .type("text/plain") + .send("Invalid Preflight Request"); + return; + } + + req.corsPreflightEnabled = true; + + addPreflightHeaders(req, reply, options); + + if (!options.preflightContinue) { + // Do not call the hook callback and terminate the request + // Safari (and potentially other browsers) need content-length 0, + // for 204 or they just hang waiting for a body + reply + .code(options.optionsSuccessStatus!) + .header("Content-Length", "0") + .send(); + return; + } + } + + return next(); + }, + ); +}; + +const addCorsHeaders = ( + req: FastifyRequest, + reply: FastifyReply, + originOption: any, + corsOptions: FastifyCorsOptions, +) => { + const origin = getAccessControlAllowOriginHeader( + req.headers.origin!, + originOption, + ); + + // In the case of origin not allowed the header is not + // written in the response. + // https://github.com/fastify/fastify-cors/issues/127 + if (origin) { + reply.header("Access-Control-Allow-Origin", origin); + } + + if (corsOptions.credentials) { + reply.header("Access-Control-Allow-Credentials", "true"); + } + + if (corsOptions.exposedHeaders !== null) { + reply.header( + "Access-Control-Expose-Headers", + Array.isArray(corsOptions.exposedHeaders) + ? corsOptions.exposedHeaders.join(", ") + : corsOptions.exposedHeaders, + ); + } +}; + +function addPreflightHeaders( + req: FastifyRequest, + reply: FastifyReply, + corsOptions: FastifyCorsOptions, +) { + reply.header( + "Access-Control-Allow-Methods", + Array.isArray(corsOptions.methods) + ? corsOptions.methods.join(", ") + : corsOptions.methods, + ); + + if (!corsOptions.allowedHeaders) { + addAccessControlRequestHeadersToVaryHeader(reply); + const reqAllowedHeaders = req.headers["access-control-request-headers"]; + if (reqAllowedHeaders !== undefined) { + reply.header("Access-Control-Allow-Headers", reqAllowedHeaders); + } + } else { + reply.header( + "Access-Control-Allow-Headers", + Array.isArray(corsOptions.allowedHeaders) + ? corsOptions.allowedHeaders.join(", ") + : corsOptions.allowedHeaders, + ); + } + + if (corsOptions.maxAge !== null) { + reply.header("Access-Control-Max-Age", String(corsOptions.maxAge)); + } + + if (corsOptions.cacheControl) { + reply.header("Cache-Control", corsOptions.cacheControl); + } +} + +const resolveOriginWrapper = (fastify: FastifyInstance, origin: any) => { + return (req: FastifyRequest, cb: any) => { + const result = origin.call(fastify, req.headers.origin, cb); + + // Allow for promises + if (result && typeof result.then === "function") { + result.then((res: any) => cb(null, res), cb); + } + }; +}; + +const getAccessControlAllowOriginHeader = ( + reqOrigin: string | undefined, + originOption: string, +) => { + if (originOption === "*") { + // allow any origin + return "*"; + } + + if (typeof originOption === "string") { + // fixed origin + return originOption; + } + + // reflect origin + return isRequestOriginAllowed(reqOrigin, originOption) ? reqOrigin : false; +}; + +const isRequestOriginAllowed = ( + reqOrigin: string | undefined, + allowedOrigin: string | RegExp, +) => { + if (Array.isArray(allowedOrigin)) { + for (let i = 0; i < allowedOrigin.length; ++i) { + if (isRequestOriginAllowed(reqOrigin, allowedOrigin[i])) { + return true; + } + } + return false; + } else if (typeof allowedOrigin === "string") { + return reqOrigin === allowedOrigin; + } else if (allowedOrigin instanceof RegExp && reqOrigin) { + allowedOrigin.lastIndex = 0; + return allowedOrigin.test(reqOrigin); + } else { + return !!allowedOrigin; + } +}; diff --git a/src/server/middleware/cors.ts b/src/server/middleware/cors/index.ts similarity index 74% rename from src/server/middleware/cors.ts rename to src/server/middleware/cors/index.ts index 0a7975096..0fb73bee6 100644 --- a/src/server/middleware/cors.ts +++ b/src/server/middleware/cors/index.ts @@ -1,12 +1,17 @@ -import fastifyCors from "@fastify/cors"; import { FastifyInstance } from "fastify"; -import { env } from "../../utils/env"; +import { fastifyCors } from "./cors"; export const withCors = async (server: FastifyInstance) => { - const originArray = env.ACCESS_CONTROL_ALLOW_ORIGIN.split(",") as string[]; - await server.register(fastifyCors, { - origin: originArray.map(sanitizeOrigin), - credentials: true, + server.addHook("preHandler", (request, reply, next) => { + fastifyCors( + server, + request, + reply, + { + credentials: true, + }, + next, + ); }); }; diff --git a/src/server/middleware/cors/vary.ts b/src/server/middleware/cors/vary.ts new file mode 100644 index 000000000..db3bb6e72 --- /dev/null +++ b/src/server/middleware/cors/vary.ts @@ -0,0 +1,116 @@ +"use strict"; + +import { FastifyReply } from "fastify"; +import LRUCache from "mnemonist/lru-cache"; + +/** + * Field Value Components + * Most HTTP header field values are defined using common syntax + * components (token, quoted-string, and comment) separated by + * whitespace or specific delimiting characters. Delimiters are chosen + * from the set of US-ASCII visual characters not allowed in a token + * (DQUOTE and "(),/:;<=>?@[\]{}"). + * + * field-name = token + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + * / DIGIT / ALPHA + * ; any VCHAR, except delimiters + * + * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6 + */ + +const validFieldnameRE = /^[!#$%&'*+\-.^\w`|~]+$/u; +function validateFieldname(fieldname: string) { + if (validFieldnameRE.test(fieldname) === false) { + throw new TypeError("Fieldname contains invalid characters."); + } +} + +export const parse = (header: string) => { + header = header.trim().toLowerCase(); + const result = []; + + if (header.length === 0) { + // pass through + } else if (header.indexOf(",") === -1) { + result.push(header); + } else { + const il = header.length; + let i = 0; + let pos = 0; + let char; + + // tokenize the header + for (i = 0; i < il; ++i) { + char = header[i]; + // when we have whitespace set the pos to the next position + if (char === " ") { + pos = i + 1; + // `,` is the separator of vary-values + } else if (char === ",") { + // if pos and current position are not the same we have a valid token + if (pos !== i) { + result.push(header.slice(pos, i)); + } + // reset the positions + pos = i + 1; + } + } + + if (pos !== i) { + result.push(header.slice(pos, i)); + } + } + + return result; +}; + +function createAddFieldnameToVary(fieldname: string) { + const headerCache = new LRUCache(1000); + + validateFieldname(fieldname); + + return function (reply: FastifyReply) { + let header = reply.getHeader("Vary") as any; + + if (!header) { + reply.header("Vary", fieldname); + return; + } + + if (header === "*") { + return; + } + + if (fieldname === "*") { + reply.header("Vary", "*"); + return; + } + + if (Array.isArray(header)) { + header = header.join(", "); + } + + if (!headerCache.has(header)) { + const vals = parse(header); + + if (vals.indexOf("*") !== -1) { + headerCache.set(header, "*"); + } else if (vals.indexOf(fieldname.toLowerCase()) === -1) { + headerCache.set(header, header + ", " + fieldname); + } else { + headerCache.set(header, null); + } + } + const cached = headerCache.get(header); + if (cached !== null) { + reply.header("Vary", cached); + } + }; +} + +export const addOriginToVaryHeader = createAddFieldnameToVary("Origin"); +export const addAccessControlRequestHeadersToVaryHeader = + createAddFieldnameToVary("Access-Control-Request-Headers"); diff --git a/src/server/routes/configuration/cors/add.ts b/src/server/routes/configuration/cors/add.ts new file mode 100644 index 000000000..b7fc2547f --- /dev/null +++ b/src/server/routes/configuration/cors/add.ts @@ -0,0 +1,71 @@ +import { Static, Type } from "@sinclair/typebox"; +import { FastifyInstance } from "fastify"; +import { StatusCodes } from "http-status-codes"; +import { updateConfiguration } from "../../../../db/configuration/updateConfiguration"; +import { getConfig } from "../../../../utils/cache/getConfig"; +import { mandatoryAllowedCorsUrls } from "../../../utils/cors-urls"; +import { ReplySchema } from "./get"; + +const BodySchema = Type.Object({ + urlsToAdd: Type.Array( + Type.String({ + description: + "Comma separated list of origins to allow CORS for. Thirdweb URLs are automatically added.", + minLength: 1, + }), + ), +}); + +BodySchema.examples = [ + { + urlsToAdd: ["https://example.com", "https://example2.com"], + }, +]; + +export async function addUrlToCorsConfiguration(fastify: FastifyInstance) { + fastify.route<{ + Body: Static; + Reply: Static; + }>({ + method: "POST", + url: "/configuration/cors", + schema: { + summary: "Add Url to CORS configuration", + description: "Add Url to CORS configuration", + tags: ["Configuration"], + operationId: "addUrlToCorsConfiguration", + body: BodySchema, + response: { + [StatusCodes.OK]: ReplySchema, + }, + }, + handler: async (req, res) => { + const oldConfig = await getConfig(); + + const urlsToAdd = req.body.urlsToAdd.map((url) => url.trim()); + + const requiredUrls = mandatoryAllowedCorsUrls; + + requiredUrls.forEach((url) => { + if (!urlsToAdd.includes(url)) { + urlsToAdd.push(url); + } + }); + + await updateConfiguration({ + accessControlAllowOrigin: [ + ...new Set([ + ...urlsToAdd, + ...oldConfig.accessControlAllowOrigin.split(","), + ]), + ].join(","), + }); + + // Fetch and return the updated configuration + const config = await getConfig(false); + res.status(200).send({ + result: config.accessControlAllowOrigin.split(","), + }); + }, + }); +} diff --git a/src/server/routes/configuration/cors/get.ts b/src/server/routes/configuration/cors/get.ts new file mode 100644 index 000000000..96595b949 --- /dev/null +++ b/src/server/routes/configuration/cors/get.ts @@ -0,0 +1,33 @@ +import { Static, Type } from "@sinclair/typebox"; +import { FastifyInstance } from "fastify"; +import { StatusCodes } from "http-status-codes"; +import { getConfig } from "../../../../utils/cache/getConfig"; + +export const ReplySchema = Type.Object({ + result: Type.Union([Type.Array(Type.String()), Type.String(), Type.Null()]), +}); + +export async function getCorsConfiguration(fastify: FastifyInstance) { + fastify.route<{ + Reply: Static; + }>({ + method: "GET", + url: "/configuration/cors", + schema: { + summary: "Get CORS configuration", + description: "Get the engine configuration for CORS", + tags: ["Configuration"], + operationId: "getCorsConfiguration", + response: { + [StatusCodes.OK]: ReplySchema, + }, + }, + handler: async (req, res) => { + const config = await getConfig(false); + + res.status(200).send({ + result: config.accessControlAllowOrigin.split(","), + }); + }, + }); +} diff --git a/src/server/routes/configuration/cors/remove.ts b/src/server/routes/configuration/cors/remove.ts new file mode 100644 index 000000000..fef0a4f1c --- /dev/null +++ b/src/server/routes/configuration/cors/remove.ts @@ -0,0 +1,74 @@ +import { Static, Type } from "@sinclair/typebox"; +import { FastifyInstance } from "fastify"; +import { StatusCodes } from "http-status-codes"; +import { updateConfiguration } from "../../../../db/configuration/updateConfiguration"; +import { getConfig } from "../../../../utils/cache/getConfig"; +import { mandatoryAllowedCorsUrls } from "../../../utils/cors-urls"; +import { ReplySchema } from "./get"; + +const BodySchema = Type.Object({ + urlsToRemove: Type.Array( + Type.String({ + description: "Comma separated list urls", + }), + ), +}); + +BodySchema.examples = [ + { + urlsToRemove: ["https://example.com", "https://example2.com"], + }, +]; + +export async function removeUrlToCorsConfiguration(fastify: FastifyInstance) { + fastify.route<{ + Body: Static; + Reply: Static; + }>({ + method: "DELETE", + url: "/configuration/cors", + schema: { + summary: "Remove Url from CORS configuration", + description: "Remove Url from CORS configuration", + tags: ["Configuration"], + operationId: "removeUrlToCorsConfiguration", + body: BodySchema, + response: { + [StatusCodes.OK]: ReplySchema, + }, + }, + handler: async (req, res) => { + const currentConfig = await getConfig(); + + const urlsToRemove = req.body.urlsToRemove.map((url) => url.trim()); + + // Check for mandatory URLs + const containsMandatoryUrl = urlsToRemove.some((url) => + mandatoryAllowedCorsUrls.includes(url), + ); + if (containsMandatoryUrl) { + throw new Error( + `Cannot remove mandatory URLs: ${mandatoryAllowedCorsUrls.join(",")}`, + ); + } + + // Filter out URLs to be removed + const newAllowOriginsList = currentConfig.accessControlAllowOrigin + .split(",") + .map((url) => url.trim()) + .filter((url) => !urlsToRemove.includes(url)); + + await updateConfiguration({ + accessControlAllowOrigin: [...new Set([...newAllowOriginsList])].join( + ",", + ), + }); + + // Fetch and return the updated configuration + const newConfig = await getConfig(false); + res + .status(200) + .send({ result: newConfig.accessControlAllowOrigin.split(",") }); + }, + }); +} diff --git a/src/server/routes/index.ts b/src/server/routes/index.ts index 48453a5cc..ea9e8a7d7 100644 --- a/src/server/routes/index.ts +++ b/src/server/routes/index.ts @@ -56,6 +56,9 @@ import { getBackendWalletBalanceConfiguration } from "./configuration/backend-wa import { updateBackendWalletBalanceConfiguration } from "./configuration/backend-wallet-balance/update"; import { getChainsConfiguration } from "./configuration/chains/get"; import { updateChainsConfiguration } from "./configuration/chains/update"; +import { addUrlToCorsConfiguration } from "./configuration/cors/add"; +import { getCorsConfiguration } from "./configuration/cors/get"; +import { removeUrlToCorsConfiguration } from "./configuration/cors/remove"; import { getTransactionConfiguration } from "./configuration/transactions/get"; import { updateTransactionConfiguration } from "./configuration/transactions/update"; import { getWalletsConfiguration } from "./configuration/wallets/get"; @@ -132,6 +135,9 @@ export const withRoutes = async (fastify: FastifyInstance) => { await fastify.register(updateAuthConfiguration); await fastify.register(getBackendWalletBalanceConfiguration); await fastify.register(updateBackendWalletBalanceConfiguration); + await fastify.register(getCorsConfiguration); + await fastify.register(addUrlToCorsConfiguration); + await fastify.register(removeUrlToCorsConfiguration); // Webhooks await fastify.register(getAllWebhooksData); diff --git a/src/server/types/fastify.d.ts b/src/server/types/fastify.d.ts new file mode 100644 index 000000000..2e915bf62 --- /dev/null +++ b/src/server/types/fastify.d.ts @@ -0,0 +1,7 @@ +import "fastify"; + +declare module "fastify" { + interface FastifyRequest { + corsPreflightEnabled: boolean; + } +} diff --git a/src/server/utils/cors-urls.ts b/src/server/utils/cors-urls.ts new file mode 100644 index 000000000..cb94c87e5 --- /dev/null +++ b/src/server/utils/cors-urls.ts @@ -0,0 +1,5 @@ +// URLs to check and add if missing +export const mandatoryAllowedCorsUrls = [ + "https://thirdweb.com", + "https://embed.ipfscdn.io", +]; diff --git a/src/utils/env.ts b/src/utils/env.ts index d907d088c..eb4386cea 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -68,9 +68,6 @@ export const env = createEnv({ ), PORT: z.coerce.number().default(3005), HOST: z.string().default("0.0.0.0"), - ACCESS_CONTROL_ALLOW_ORIGIN: z - .string() - .default("https://thirdweb.com,https://thirdweb-preview.com"), ENABLE_HTTPS: boolSchema("false"), HTTPS_PASSPHRASE: z.string().default("thirdweb-engine"), }, @@ -87,7 +84,6 @@ export const env = createEnv({ POSTGRES_CONNECTION_URL: process.env.POSTGRES_CONNECTION_URL, PORT: process.env.PORT, HOST: process.env.HOST, - ACCESS_CONTROL_ALLOW_ORIGIN: process.env.ACCESS_CONTROL_ALLOW_ORIGIN, ENABLE_HTTPS: process.env.ENABLE_HTTPS, HTTPS_PASSPHRASE: process.env.HTTPS_PASSPHRASE, }, diff --git a/src/worker/tasks/processTx.ts b/src/worker/tasks/processTx.ts index 4bf36afec..124480aff 100644 --- a/src/worker/tasks/processTx.ts +++ b/src/worker/tasks/processTx.ts @@ -473,6 +473,12 @@ export const processTx = async () => { error: err, }); + // Add to Webhook Queue to send updates + sendWebhookForQueueIds.push({ + queueId: tx.queueId!, + status: TransactionStatusEnum.Errored, + }); + await updateTx({ pgtx, queueId: tx.queueId!,