From 9146a0c340d5d8e6a77fb8db24a7c05914410a87 Mon Sep 17 00:00:00 2001 From: Lucid Date: Sat, 6 Dec 2025 20:29:16 -0500 Subject: [PATCH] Fixed env variables --- .github/workflows/main.yml | 10 ++- Dockerfile | 9 +++ package.json | 1 + src/app/api/graphql/route.ts | 50 +++++++++++- src/app/layout.tsx | 8 +- yarn.lock | 143 ++++++++++++++++++++++++++++++++++- 6 files changed, 215 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bfe46ef..daab704 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,7 +53,10 @@ jobs: context: . push: ${{ !github.event.pull_request.head.repo.fork }} tags: ${{ steps.meta.outputs.tags }} - build-args: API_URL=DATABASE_URL=${{ secrets.DATABASE_URL }} + build-args: | + DATABASE_URL=${{ secrets.DATABASE_URL }} + NEXT_PUBLIC_API_TOKEN=${{ secrets.NEXT_PUBLIC_API_TOKEN }} + NEXT_PUBLIC_GRAPHQL_URL=${{ secrets.NEXT_PUBLIC_GRAPHQL_URL }} - name: Build and Push Latest Docker Image id: build-and-push-latest uses: docker/build-push-action@v4 @@ -62,4 +65,7 @@ jobs: context: . push: true tags: ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:latest - build-args: DATABASE_URL=${{ secrets.DATABASE_URL }} + build-args: | + DATABASE_URL=${{ secrets.DATABASE_URL }} + NEXT_PUBLIC_API_TOKEN=${{ secrets.NEXT_PUBLIC_API_TOKEN }} + NEXT_PUBLIC_GRAPHQL_URL=${{ secrets.NEXT_PUBLIC_GRAPHQL_URL }} diff --git a/Dockerfile b/Dockerfile index 3f9a35c..4022a62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,11 @@ RUN corepack enable RUN corepack prepare yarn@stable --activate ARG DATABASE_URL ENV DATABASE_URL=${DATABASE_URL} +ENV DATABASE_URL=${DATABASE_URL} +ARG NEXT_PUBLIC_GRAPHQL_URL +ENV NEXT_PUBLIC_GRAPHQL_URL=${NEXT_PUBLIC_GRAPHQL_URL} +ARG NEXT_PUBLIC_API_TOKEN +ENV NEXT_PUBLIC_API_TOKEN=${NEXT_PUBLIC_API_TOKEN} RUN corepack enable RUN corepack prepare yarn@stable --activate WORKDIR /app @@ -26,6 +31,10 @@ RUN corepack enable RUN corepack prepare yarn@stable --activate ARG DATABASE_URL ENV DATABASE_URL=${DATABASE_URL} +ARG NEXT_PUBLIC_GRAPHQL_URL +ENV NEXT_PUBLIC_GRAPHQL_URL=${NEXT_PUBLIC_GRAPHQL_URL} +ARG NEXT_PUBLIC_API_TOKEN +ENV NEXT_PUBLIC_API_TOKEN=${NEXT_PUBLIC_API_TOKEN} RUN corepack enable RUN corepack prepare yarn@stable --activate WORKDIR /app diff --git a/package.json b/package.json index c0a3743..851ac8f 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@chakra-ui/charts": "^3.30.0", "@chakra-ui/react": "^3.30.0", "@emotion/react": "^11.14.0", + "@escape.tech/graphql-armor": "^3.1.7", "@prisma/client": "^6.19.0", "@prisma/extension-accelerate": "^2.0.2", "@urql/next": "^2.0.0", diff --git a/src/app/api/graphql/route.ts b/src/app/api/graphql/route.ts index 0de12e3..384c403 100644 --- a/src/app/api/graphql/route.ts +++ b/src/app/api/graphql/route.ts @@ -1,11 +1,54 @@ +/* eslint-disable react-hooks/rules-of-hooks */ +import { createYoga, Plugin, createSchema } from "graphql-yoga"; +import { EnvelopArmorPlugin } from "@escape.tech/graphql-armor"; import resolvers from "@/graphql/resolvers"; import typeDefs from "@/graphql/types"; -import { createSchema, createYoga } from "graphql-yoga"; interface NextContext { params: Promise>; } +const environment = process.env.NODE_ENV || "development"; + +const isValidApiKey = (apiKey: string): boolean => { + const envApiKey = + process.env.API_TOKEN || process.env.NEXT_PUBLIC_API_TOKEN || ""; + + return apiKey === envApiKey; +}; + +function useApiKey(): Plugin { + return { + async onRequest({ request, endResponse, fetchAPI }) { + const apiKey = await request.headers.get("x-api-key"); + + if (environment === "production") { + if (!apiKey || apiKey == null) { + endResponse( + new fetchAPI.Response( + JSON.stringify({ + message: "No API Key provided" + }), + { status: 401, headers: { "Content-Type": "application/json" } } + ) + ); + } + + if (apiKey !== null && !(await isValidApiKey(apiKey))) { + endResponse( + new fetchAPI.Response( + JSON.stringify({ + message: "Invalid API Key" + }), + { status: 403, headers: { "Content-Type": "application/json" } } + ) + ); + } + } + } + }; +} + const { handleRequest } = createYoga({ schema: createSchema({ typeDefs: typeDefs, @@ -16,7 +59,10 @@ const { handleRequest } = createYoga({ graphqlEndpoint: "/api/graphql", // Yoga needs to know how to create a valid Next response - fetchAPI: { Response } + fetchAPI: { Response }, + + plugins: [useApiKey(), EnvelopArmorPlugin()], + graphiql: environment !== "production" ? true : false }); export { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 983ba80..a8d4b96 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -22,7 +22,13 @@ export default function RootLayout({ const client = createClient({ url: "/api/graphql", exchanges: [cacheExchange, ssr, fetchExchange], - suspense: true + suspense: true, + fetchOptions: { + headers: { + "x-api-key": + process.env?.API_TOKEN || process.env?.NEXT_PUBLIC_API_TOKEN || "" + } + } }); return [client, ssr]; diff --git a/yarn.lock b/yarn.lock index 8d773ec..8898845 100644 --- a/yarn.lock +++ b/yarn.lock @@ -458,6 +458,18 @@ __metadata: languageName: node linkType: hard +"@envelop/core@npm:^5.2.3": + version: 5.4.0 + resolution: "@envelop/core@npm:5.4.0" + dependencies: + "@envelop/instrumentation": "npm:^1.0.0" + "@envelop/types": "npm:^5.2.1" + "@whatwg-node/promise-helpers": "npm:^1.2.4" + tslib: "npm:^2.5.0" + checksum: 10c0/e8f87c68ce80984f0127afe8c6786468b18db91e8cb2335468dbef8c8b32d0ff10121dc53dde56d6f6acbcad74798807258799864c398d15bd547c49839445d1 + languageName: node + linkType: hard + "@envelop/core@npm:^5.3.0": version: 5.3.2 resolution: "@envelop/core@npm:5.3.2" @@ -672,6 +684,134 @@ __metadata: languageName: node linkType: hard +"@escape.tech/graphql-armor-block-field-suggestions@npm:3.0.1": + version: 3.0.1 + resolution: "@escape.tech/graphql-armor-block-field-suggestions@npm:3.0.1" + dependencies: + "@envelop/core": "npm:^5.2.3" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + checksum: 10c0/b90c32530d6d7ad5bf713f3f6f864d214fbdf9b3930eead5511009f2926a3804ed3c9c6bbe08e12db794e9fe4745aeb620b4e96fd3416c958c1d2580e2d19e1a + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-cost-limit@npm:2.4.3": + version: 2.4.3 + resolution: "@escape.tech/graphql-armor-cost-limit@npm:2.4.3" + dependencies: + "@envelop/core": "npm:^5.2.3" + "@escape.tech/graphql-armor-types": "npm:0.7.0" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/89dc54b68a29d572b98be66f9e4629a51c78d0c72f8b62aa5bbe071356bed7951611f226e71bb9bf1f83e322585f7b51982e454a49b0cd4f81c7fe4c0b822893 + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-max-aliases@npm:2.6.2": + version: 2.6.2 + resolution: "@escape.tech/graphql-armor-max-aliases@npm:2.6.2" + dependencies: + "@envelop/core": "npm:^5.2.3" + "@escape.tech/graphql-armor-types": "npm:0.7.0" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/7a49bfa9faa68a4a8fad7beab40f7ccc6dc01d28dc6238a335e77aa0fc1ff1aab7a2207d11e5341b7c48e473a485ed2cdc98bd7d134bc8ae157945d77675bdb5 + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-max-depth@npm:2.4.2": + version: 2.4.2 + resolution: "@escape.tech/graphql-armor-max-depth@npm:2.4.2" + dependencies: + "@envelop/core": "npm:^5.2.3" + "@escape.tech/graphql-armor-types": "npm:0.7.0" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/e38612e5bb0ed5db1d36100ca0701d32cc22a28f0418b56106a756683d86c1eef56d6987965748f1dbf386c19f7d693ae6a9b1416104cc63795e3dcb6ee0e27a + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-max-directives@npm:2.3.1": + version: 2.3.1 + resolution: "@escape.tech/graphql-armor-max-directives@npm:2.3.1" + dependencies: + "@envelop/core": "npm:^5.2.3" + "@escape.tech/graphql-armor-types": "npm:0.7.0" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/2f14c1bc4356087db30fd4c83b762383999f70e7fd289f6954d26ae62790ffe65c5aa279cbcb08d4aca666e671306eb97c43a514ef574cc6d81dea33963fc048 + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-max-tokens@npm:2.5.1": + version: 2.5.1 + resolution: "@escape.tech/graphql-armor-max-tokens@npm:2.5.1" + dependencies: + "@envelop/core": "npm:^5.2.3" + "@escape.tech/graphql-armor-types": "npm:0.7.0" + graphql: "npm:^16.10.0" + dependenciesMeta: + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/db9e5cbdbd66a1aa031e2398fdf507bb9cb51321e9bb9c75dc668c6a250c6e7b5bc8d499487973184af0dd6fc2c150f88e008b1badfe01e6690397763765de7f + languageName: node + linkType: hard + +"@escape.tech/graphql-armor-types@npm:0.7.0": + version: 0.7.0 + resolution: "@escape.tech/graphql-armor-types@npm:0.7.0" + dependencies: + graphql: "npm:^16.0.0" + checksum: 10c0/555d8ce8c364b31dfd8239cfb3cac3b44307950df457ce7f3585aa30d112edb9332ea6aa417f9e9fb245ed913004f8350ba9abbc60847fde75d4c006fd5c68b7 + languageName: node + linkType: hard + +"@escape.tech/graphql-armor@npm:^3.1.7": + version: 3.1.7 + resolution: "@escape.tech/graphql-armor@npm:3.1.7" + dependencies: + "@escape.tech/graphql-armor-block-field-suggestions": "npm:3.0.1" + "@escape.tech/graphql-armor-cost-limit": "npm:2.4.3" + "@escape.tech/graphql-armor-max-aliases": "npm:2.6.2" + "@escape.tech/graphql-armor-max-depth": "npm:2.4.2" + "@escape.tech/graphql-armor-max-directives": "npm:2.3.1" + "@escape.tech/graphql-armor-max-tokens": "npm:2.5.1" + graphql: "npm:^16.10.0" + peerDependencies: + "@apollo/server": ^4.0.0 + "@envelop/core": ^5.0.0 + "@escape.tech/graphql-armor-types": 0.7.0 + peerDependenciesMeta: + "@apollo/server": + optional: true + "@envelop/core": + optional: true + "@escape.tech/graphql-armor-types": + optional: true + checksum: 10c0/0795987bbfc1d79e22c1701fd53ef8a6b3da3b0bb03b1881f738040173a0c0e676d7a907076ca2a5827b1f1bf8e541b34f6c18d2600dac38cf9c7c6ea97aa9ac + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.7.0, @eslint-community/eslint-utils@npm:^4.8.0": version: 4.9.0 resolution: "@eslint-community/eslint-utils@npm:4.9.0" @@ -4997,7 +5137,7 @@ __metadata: languageName: node linkType: hard -"graphql@npm:^16.12.0": +"graphql@npm:^16.0.0, graphql@npm:^16.10.0, graphql@npm:^16.12.0": version: 16.12.0 resolution: "graphql@npm:16.12.0" checksum: 10c0/b6fffa4e8a4e4a9933ebe85e7470b346dbf49050c1a482fac5e03e4a1a7bed2ecd3a4c97e29f04457af929464bc5e4f2aac991090c2f320111eef26e902a5c75 @@ -5947,6 +6087,7 @@ __metadata: "@chakra-ui/charts": "npm:^3.30.0" "@chakra-ui/react": "npm:^3.30.0" "@emotion/react": "npm:^11.14.0" + "@escape.tech/graphql-armor": "npm:^3.1.7" "@eslint/eslintrc": "npm:^3.3.3" "@eslint/js": "npm:^9.39.1" "@iconify/react": "npm:^6.0.2"