From 6cd3878700166446b290f5965bf1459b4dd19516 Mon Sep 17 00:00:00 2001 From: Matti Nannt Date: Sun, 16 Oct 2022 11:10:20 +0200 Subject: [PATCH] make all env variables required at build time #110 (#111) * make all env variables required at build time * update env files * add .env.docker file with basic docker configuration * update Readme with new instructions --- .env.docker | 12 ++-- .env.example | 12 ++-- .github/workflows/docker-image.yml | 69 ----------------------- README.md | 6 +- apps/web/Dockerfile | 4 +- apps/web/lib/email.ts | 23 +++----- apps/web/lib/jwt.ts | 7 +-- apps/web/lib/posthog.ts | 10 +--- apps/web/lib/telemetry.ts | 9 +-- apps/web/next.config.js | 23 -------- apps/web/pages/api/auth/[...nextauth].ts | 5 +- apps/web/pages/api/public/users/index.tsx | 7 +-- apps/web/pages/auth/signin.tsx | 6 +- apps/web/pages/auth/signup.tsx | 26 ++++----- apps/web/pages/f/[id].tsx | 22 +++----- turbo.json | 25 +++++++- 16 files changed, 81 insertions(+), 185 deletions(-) delete mode 100644 .github/workflows/docker-image.yml diff --git a/.env.docker b/.env.docker index 4309285e8d..f4257259a0 100644 --- a/.env.docker +++ b/.env.docker @@ -39,19 +39,19 @@ DATABASE_URL='postgresql://postgres:postgres@postgres:5432/snoopforms?schema=pub ##################### # Email Verification. If you enable Email Verification you have to setup SMTP-Settings, too. -EMAIL_VERIFICATION_DISABLED=1 +NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED=1 # Password Reset. If you enable Password Reset functionality you have to setup SMTP-Settings, too. -PASSWORD_RESET_DISABLED=1 +NEXT_PUBLIC_PASSWORD_RESET_DISABLED=1 ####################### # Additional Options # ####################### -# TERMS_URL=https://www.example.com/terms -# PRIVACY_URL=https://www.example.com/privacy -# PUBLIC_IMPRINT_URL=https://www.example.com/imprint -# PUBLIC_PRIVACY_URL=https://www.example.com/enduserPrivacy +# NEXT_PUBLIC_TERMS_URL=https://www.example.com/terms +# NEXT_PUBLIC_PRIVACY_URL=https://www.example.com/privacy +# NEXT_PUBLIC_IMPRINT_URL=https://www.example.com/imprint +# NEXT_PUBLIC_PRIVACY_URL=https://www.example.com/enduserPrivacy ###################### # Posthog Tracking # diff --git a/.env.example b/.env.example index 53adcdbbbc..15fcb3ada7 100644 --- a/.env.example +++ b/.env.example @@ -41,19 +41,19 @@ SMTP_PASSWORD=smtpPassword ##################### # Email Verification. If you enable Email Verification you have to setup SMTP-Settings, too. -# EMAIL_VERIFICATION_DISABLED=1 +# NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED=1 # Password Reset. If you enable Password Reset functionality you have to setup SMTP-Settings, too. -# PASSWORD_RESET_DISABLED=1 +# NEXT_PUBLIC_PASSWORD_RESET_DISABLED=1 ####################### # Additional Options # ####################### -# TERMS_URL=https://www.example.com/terms -# PRIVACY_URL=https://www.example.com/privacy -# PUBLIC_IMPRINT_URL=https://www.example.com/imprint -# PUBLIC_PRIVACY_URL=https://www.example.com/enduserPrivacy +# NEXT_PUBLIC_TERMS_URL=https://www.example.com/terms +# NEXT_PUBLIC_PRIVACY_URL=https://www.example.com/privacy +# NEXT_PUBLIC_IMPRINT_URL=https://www.example.com/imprint +# NEXT_PUBLIC_PRIVACY_URL=https://www.example.com/enduserPrivacy ###################### # Posthog Tracking # diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml deleted file mode 100644 index 18d9354fa4..0000000000 --- a/.github/workflows/docker-image.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Docker Build & Publish - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -on: - push: - branches: - - "main" - tags: - - "v*.*.*" - pull_request: - branches: - - "main" - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - -jobs: - build: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Login against a Docker registry except on PR - # https://github.com/docker/login-action - - name: Log into registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Extract metadata (tags, labels) for Docker - # https://github.com/docker/metadata-action - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 - with: - images: ${{ env.REGISTRY }}/${{ github.repository }} - tags: | - type=schedule - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=sha - - # Build and push Docker image with Buildx (don't push on PR) - # https://github.com/docker/build-push-action - - name: Build and push Docker image - uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc - with: - file: apps/web/Dockerfile - context: . - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/README.md b/README.md index 917ddced2a..b1e71fe76b 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ git clone https://github.com/formbricks/snoopforms.git && cd snoopforms ``` -Create a `.env` file based on `.env.docker` and change all fields according to your setup. This file comes with a basic setup and snoopForms works without making any changes to the file. To enable email sending functionality you need to configure the SMTP settings in the `.env` file. If you configured your email credentials, you can also comment the following lines to enable email verification (`# EMAIL_VERIFICATION_DISABLED=1`) and password reset (`# PASSWORD_RESET_DISABLED=1`). +Create a `.env` file based on `.env.docker` and change all fields according to your setup. This file comes with a basic setup and snoopForms works without making any changes to the file. To enable email sending functionality you need to configure the SMTP settings in the `.env` file. If you configured your email credentials, you can also comment the following lines to enable email verification (`# NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED=1`) and password reset (`# NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED=1`). Copy the `.env.docker` file to `.env` and edit it with an editor of your choice if needed. @@ -136,7 +136,9 @@ cp .env.docker .env ``` -Start the docker compose process to build and spin up the snoopForms container as well as the PostgreSQL database. +Note: The environment variables are used at build time. When you change environment variables later, you need to rebuild the image with `docker compose build` for the changes to take effect. + +Finally start the docker compose process to build and spin up the snoopForms container as well as the PostgreSQL database. ```bash diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index ab77ea3578..d9d556c9d6 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -7,6 +7,9 @@ WORKDIR /app # First install the dependencies (as they change less often) COPY . . +# Copy .env file because Docker don't follow symlinks +COPY .env /app/apps/web/ + RUN pnpm install # Build the project @@ -33,7 +36,6 @@ COPY --from=installer /app/apps/web/package.json . COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public -COPY --from=installer --chown=nextjs:nodejs /.env ./apps/web/.env COPY --from=installer --chown=nextjs:nodejs /app/packages/database/prisma ./packages/database/prisma CMD pnpm dlx prisma migrate deploy && node apps/web/server.js \ No newline at end of file diff --git a/apps/web/lib/email.ts b/apps/web/lib/email.ts index 325f13335f..1aac348ad3 100644 --- a/apps/web/lib/email.ts +++ b/apps/web/lib/email.ts @@ -1,9 +1,6 @@ -import getConfig from "next/config"; import { createToken } from "./jwt"; const nodemailer = require("nodemailer"); -const { serverRuntimeConfig } = getConfig(); - interface sendEmailData { to: string; subject: string; @@ -13,18 +10,18 @@ interface sendEmailData { export const sendEmail = async (emailData: sendEmailData) => { let transporter = nodemailer.createTransport({ - host: serverRuntimeConfig.smtpHost, - port: serverRuntimeConfig.smtpPort, - secure: serverRuntimeConfig.smtpSecureEnabled, // true for 465, false for other ports + host: process.env.SMTP_HOST, + port: process.env.SMTP_PORT, + secure: process.env.SMTP_SECURE_ENABLED === "1", // true for 465, false for other ports auth: { - user: serverRuntimeConfig.smtpUser, - pass: serverRuntimeConfig.smtpPassword, + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD, }, // logger: true, // debug: true, }); const emailDefaults = { - from: `snoopForms <${serverRuntimeConfig.mailFrom || "noreply@snoopforms.com"}>`, + from: `snoopForms <${process.env.MAIL_FROM || "noreply@snoopforms.com"}>`, }; await transporter.sendMail({ ...emailDefaults, ...emailData }); }; @@ -33,9 +30,9 @@ export const sendVerificationEmail = async (user) => { const token = createToken(user.id, user.email, { expiresIn: "1d", }); - const verifyLink = `${serverRuntimeConfig.nextauthUrl}/auth/verify?token=${encodeURIComponent(token)}`; + const verifyLink = `${process.env.NEXTAUTH_URL}/auth/verify?token=${encodeURIComponent(token)}`; const verificationRequestLink = `${ - serverRuntimeConfig.nextauthUrl + process.env.NEXTAUTH_URL }/auth/verification-requested?email=${encodeURIComponent(user.email)}`; await sendEmail({ to: user.email, @@ -54,9 +51,7 @@ export const sendForgotPasswordEmail = async (user) => { const token = createToken(user.id, user.email, { expiresIn: "1d", }); - const verifyLink = `${serverRuntimeConfig.nextauthUrl}/auth/reset-password?token=${encodeURIComponent( - token - )}`; + const verifyLink = `${process.env.NEXTAUTH_URL}/auth/reset-password?token=${encodeURIComponent(token)}`; await sendEmail({ to: user.email, subject: "Reset your snoopForms password", diff --git a/apps/web/lib/jwt.ts b/apps/web/lib/jwt.ts index fca31db7ee..ee2f392664 100644 --- a/apps/web/lib/jwt.ts +++ b/apps/web/lib/jwt.ts @@ -1,11 +1,8 @@ import jwt from "jsonwebtoken"; -import getConfig from "next/config"; import { prisma } from "database"; -const { serverRuntimeConfig } = getConfig(); - export function createToken(userId, userEmail, options = {}) { - return jwt.sign({ id: userId }, serverRuntimeConfig.nextauthSecret + userEmail, options); + return jwt.sign({ id: userId }, process.env.NEXTAUTH_SECRET + userEmail, options); } export async function verifyToken(token, userEmail = "") { @@ -23,5 +20,5 @@ export async function verifyToken(token, userEmail = "") { userEmail = foundUser.email; } - return jwt.verify(token, serverRuntimeConfig.nextauthSecret + userEmail); + return jwt.verify(token, process.env.NEXTAUTH_SECRET + userEmail); } diff --git a/apps/web/lib/posthog.ts b/apps/web/lib/posthog.ts index 68c1111217..6a3ffe36c3 100644 --- a/apps/web/lib/posthog.ts +++ b/apps/web/lib/posthog.ts @@ -1,22 +1,18 @@ -import getConfig from "next/config"; import { hashString } from "./utils"; -const { serverRuntimeConfig } = getConfig(); const enabled = - process.env.NODE_ENV === "production" && - serverRuntimeConfig.posthogApiKey && - serverRuntimeConfig.posthogApiHost; + process.env.NODE_ENV === "production" && process.env.POSTHOG_API_HOST && process.env.POSTHOG_API_KEY; export const capturePosthogEvent = async (userId, eventName, properties = {}) => { if (!enabled) { return; } try { - await fetch(`${serverRuntimeConfig.posthogApiHost}/capture/`, { + await fetch(`${process.env.POSTHOG_API_HOST}/capture/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - api_key: serverRuntimeConfig.posthogApiKey, + api_key: process.env.POSTHOG_API_KEY, event: eventName, properties: { distinct_id: hashString(userId.toString()), diff --git a/apps/web/lib/telemetry.ts b/apps/web/lib/telemetry.ts index b3f9ea9bdb..e688d34fbd 100644 --- a/apps/web/lib/telemetry.ts +++ b/apps/web/lib/telemetry.ts @@ -1,8 +1,5 @@ -import getConfig from "next/config"; import { hashString } from "./utils"; -const { serverRuntimeConfig } = getConfig(); - /* We use this telemetry service to better understand how snoopForms is being used and how we can improve it. All data including the IP address is collected anonymously and we cannot trace anything back to you or your customers. If you still want to @@ -10,9 +7,9 @@ const { serverRuntimeConfig } = getConfig(); export const sendTelemetry = async (eventName: string) => { if ( - !serverRuntimeConfig.telemetryDisabled && + process.env.TELEMETRY_DISABLED !== "1" && process.env.NODE_ENV === "production" && - serverRuntimeConfig.nextauthUrl !== "http://localhost:3000" + process.env.NEXTAUTH_URL !== "http://localhost:3000" ) { try { await fetch("https://posthog.snoopforms.com/capture/", { @@ -22,7 +19,7 @@ export const sendTelemetry = async (eventName: string) => { api_key: "phc_BTq4eagaCzPyUSURXVYwlScTQRvcmBDXjYh7OG6kiqw", event: eventName, properties: { - distinct_id: hashString(serverRuntimeConfig.nextauthUrl), + distinct_id: hashString(process.env.NEXTAUTH_URL), }, timestamp: new Date().toISOString(), }), diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 427a0850a6..4f149bb505 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -7,29 +7,6 @@ const nextConfig = { experimental: { outputFileTracingRoot: path.join(__dirname, "../../"), }, - serverRuntimeConfig: { - // Will only be available on the server side - nextauthSecret: process.env.NEXTAUTH_SECRET, - nextauthUrl: process.env.NEXTAUTH_URL, - mailFrom: process.env.MAIL_FROM, - smtpHost: process.env.SMTP_HOST, - smtpPort: process.env.SMTP_PORT, - smtpUser: process.env.SMTP_USER, - smtpPassword: process.env.SMTP_PASSWORD, - smtpSecureEnabled: process.env.SMTP_SECURE_ENABLED === "1", - posthogApiHost: process.env.POSTHOG_API_HOST, - posthogApiKey: process.env.POSTHOG_API_KEY, - telemetryDisabled: process.env.TELEMETRY_DISABLED === "1", - }, - publicRuntimeConfig: { - // Will be available on both server and client - termsUrl: process.env.TERMS_URL, - privacyUrl: process.env.PRIVACY_URL, - publicImprintUrl: process.env.PUBLIC_IMPRINT_URL, - publicPrivacyUrl: process.env.PUBLIC_PRIVACY_URL, - emailVerificationDisabled: process.env.EMAIL_VERIFICATION_DISABLED === "1", - passwordResetDisabled: process.env.PASSWORD_RESET_DISABLED === "1", - }, async redirects() { return [ { diff --git a/apps/web/pages/api/auth/[...nextauth].ts b/apps/web/pages/api/auth/[...nextauth].ts index 6fb08af9e1..f4d5758c0d 100644 --- a/apps/web/pages/api/auth/[...nextauth].ts +++ b/apps/web/pages/api/auth/[...nextauth].ts @@ -1,13 +1,10 @@ import { NextApiRequest, NextApiResponse } from "next"; -import getConfig from "next/config"; import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { prisma } from "database"; import { verifyPassword } from "../../../lib/auth"; import { verifyToken } from "../../../lib/jwt"; -const { publicRuntimeConfig } = getConfig(); - export default async function auth(req: NextApiRequest, res: NextApiResponse) { return await NextAuth(req, res, { providers: [ @@ -124,7 +121,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) { ], callbacks: { async signIn({ user }: any) { - if (user.emailVerified || publicRuntimeConfig.emailVerificationDisabled) { + if (user.emailVerified || process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED === "1") { return true; } else { // Return false to display a default error message or you can return a URL to redirect to diff --git a/apps/web/pages/api/public/users/index.tsx b/apps/web/pages/api/public/users/index.tsx index 77b920c595..9b555474b2 100644 --- a/apps/web/pages/api/public/users/index.tsx +++ b/apps/web/pages/api/public/users/index.tsx @@ -1,11 +1,8 @@ import type { NextApiRequest, NextApiResponse } from "next"; import { prisma } from "database"; import { sendVerificationEmail } from "../../../../lib/email"; -import getConfig from "next/config"; import { capturePosthogEvent } from "../../../../lib/posthog"; -const { publicRuntimeConfig } = getConfig(); - export default async function handle(req: NextApiRequest, res: NextApiResponse) { // POST /api/public/users // Creates a new user @@ -15,8 +12,6 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) let user = req.body; user = { ...user, ...{ email: user.email.toLowerCase() } }; - const { emailVerificationDisabled } = publicRuntimeConfig; - // create user in database try { const userData = await prisma.user.create({ @@ -24,7 +19,7 @@ export default async function handle(req: NextApiRequest, res: NextApiResponse) ...user, }, }); - if (!emailVerificationDisabled) await sendVerificationEmail(userData); + if (process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED !== "1") await sendVerificationEmail(userData); capturePosthogEvent(user.email, "user created"); res.json(userData); } catch (e) { diff --git a/apps/web/pages/auth/signin.tsx b/apps/web/pages/auth/signin.tsx index 8a8e5757b9..2d77ce00ad 100644 --- a/apps/web/pages/auth/signin.tsx +++ b/apps/web/pages/auth/signin.tsx @@ -1,14 +1,10 @@ import { XCircleIcon } from "@heroicons/react/24/solid"; import { signIn } from "next-auth/react"; -import getConfig from "next/config"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import BaseLayoutUnauthorized from "../../components/layout/BaseLayoutUnauthorized"; -const { publicRuntimeConfig } = getConfig(); -const { passwordResetDisabled } = publicRuntimeConfig; - export default function SignInPage() { const router = useRouter(); const { error } = router.query; @@ -91,7 +87,7 @@ export default function SignInPage() { Sign in
- {!passwordResetDisabled && ( + {process.env.NEXT_PUBLIC_PASSWORD_RESET_DISABLED !== "1" && ( Forgot your password? diff --git a/apps/web/pages/auth/signup.tsx b/apps/web/pages/auth/signup.tsx index a46d61466c..459af04476 100644 --- a/apps/web/pages/auth/signup.tsx +++ b/apps/web/pages/auth/signup.tsx @@ -2,19 +2,14 @@ import { XCircleIcon } from "@heroicons/react/24/solid"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import getConfig from "next/config"; import { useState } from "react"; import BaseLayoutUnauthorized from "../../components/layout/BaseLayoutUnauthorized"; import { createUser } from "../../lib/users"; -const { publicRuntimeConfig } = getConfig(); - export default function SignUpPage() { const router = useRouter(); const [error, setError] = useState(""); - const { emailVerificationDisabled, privacyUrl, termsUrl } = publicRuntimeConfig; - const handleSubmit = async (e) => { e.preventDefault(); try { @@ -25,9 +20,10 @@ export default function SignUpPage() { e.target.elements.password.value ); - const url = emailVerificationDisabled - ? `/auth/signup-without-verification-success` - : `/auth/verification-requested?email=${encodeURIComponent(e.target.elements.email.value)}`; + const url = + process.env.NEXT_PUBLIC_EMAIL_VERIFICATION_DISABLED === "1" + ? `/auth/signup-without-verification-success` + : `/auth/verification-requested?email=${encodeURIComponent(e.target.elements.email.value)}`; router.push(url); } catch (e) { @@ -136,24 +132,26 @@ export default function SignUpPage() { Log in.
- {(termsUrl || privacyUrl) && ( + {(process.env.NEXT_PUBLIC_TERMS_URL || process.env.NEXT_PUBLIC_PRIVACY_URL) && (
By clicking "Sign Up", you agree to our
- {termsUrl && ( + {process.env.NEXT_PUBLIC_TERMS_URL && ( terms of service )} - {termsUrl && privacyUrl && and } - {privacyUrl && ( + {process.env.NEXT_PUBLIC_TERMS_URL && process.env.NEXT_PUBLIC_PRIVACY_URL && ( + and + )} + {process.env.NEXT_PUBLIC_PRIVACY_URL && ( privacy policy diff --git a/apps/web/pages/f/[id].tsx b/apps/web/pages/f/[id].tsx index f31cb0fb5a..772366caf3 100644 --- a/apps/web/pages/f/[id].tsx +++ b/apps/web/pages/f/[id].tsx @@ -5,10 +5,6 @@ import MessagePage from "../../components/MessagePage"; import { useNoCodeFormPublic } from "../../lib/noCodeForm"; import { useRouter } from "next/router"; import Image from "next/image"; -import getConfig from "next/config"; - -const { publicRuntimeConfig } = getConfig(); -const { publicPrivacyUrl, publicImprintUrl } = publicRuntimeConfig; function NoCodeFormPublic() { const router = useRouter(); @@ -43,18 +39,20 @@ function NoCodeFormPublic() { ) : ( )} - {(publicPrivacyUrl || publicImprintUrl) && ( + {(process.env.NEXT_PUBLIC_PRIVACY_URL || process.env.NEXT_PUBLIC_IMPRINT_URL) && (