From fb825e66a5394ce0852cedd2ad5d52b868ab1ce5 Mon Sep 17 00:00:00 2001 From: Stan Wohlwend Date: Mon, 1 Jul 2024 19:40:42 -0700 Subject: [PATCH] Prefer getEnvVariable in stack-backend --- apps/backend/.eslintrc.cjs | 10 +++++++++- apps/backend/src/app/api/v1/contributors/route.ts | 6 ++---- apps/backend/src/lib/tokens.tsx | 3 ++- apps/backend/src/oauth/providers/facebook.tsx | 3 ++- apps/backend/src/oauth/providers/github.tsx | 3 ++- apps/backend/src/oauth/providers/google.tsx | 3 ++- apps/backend/src/oauth/providers/microsoft.tsx | 3 ++- apps/backend/src/oauth/providers/spotify.tsx | 3 ++- apps/backend/src/prisma-client.tsx | 3 ++- packages/stack-shared/src/utils/env.tsx | 10 +++++++--- turbo.json | 3 ++- 11 files changed, 34 insertions(+), 16 deletions(-) diff --git a/apps/backend/.eslintrc.cjs b/apps/backend/.eslintrc.cjs index 8458c5cd..153dfc7e 100644 --- a/apps/backend/.eslintrc.cjs +++ b/apps/backend/.eslintrc.cjs @@ -1,5 +1,13 @@ module.exports = { extends: ["../../eslint-configs/defaults.js", "../../eslint-configs/next.js"], ignorePatterns: ["/*", "!/src", "!/prisma"], - rules: {}, + rules: { + "no-restricted-syntax": [ + "error", + { + selector: "MemberExpression[type=MemberExpression][object.type=MemberExpression][object.object.type=Identifier][object.object.name=process][object.property.type=Identifier][object.property.name=env]", + message: "Don't use process.env directly in Stack's backend. Use getEnvVariable(...) or getNodeEnvironment() instead.", + }, + ], + }, }; diff --git a/apps/backend/src/app/api/v1/contributors/route.ts b/apps/backend/src/app/api/v1/contributors/route.ts index faa24ae8..f28a3b6c 100644 --- a/apps/backend/src/app/api/v1/contributors/route.ts +++ b/apps/backend/src/app/api/v1/contributors/route.ts @@ -2,6 +2,7 @@ import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import * as yup from "yup"; import sharp from "sharp"; import { captureError } from "@stackframe/stack-shared/dist/utils/errors"; +import { getNodeEnvironment } from "@stackframe/stack-shared/dist/utils/env"; let pngImagePromise: Promise | undefined; @@ -18,7 +19,7 @@ export const GET = createSmartRouteHandler({ }).required(), }), handler: async () => { - if (process.env.NODE_ENV === "development" || !pngImagePromise) { + if (getNodeEnvironment() === "development" || !pngImagePromise) { pngImagePromise = (async () => { const ghPage = await fetch("https://github.com/stack-auth/stack"); const ghPageText = await ghPage.text(); @@ -77,9 +78,6 @@ export const GET = createSmartRouteHandler({ })(); pngImagePromise.catch((error) => { captureError("contributors-image", error); - if (process.env.NODE_ENV === "development") { - pngImagePromise = undefined; - } }); } diff --git a/apps/backend/src/lib/tokens.tsx b/apps/backend/src/lib/tokens.tsx index 41c60524..aeb892fa 100644 --- a/apps/backend/src/lib/tokens.tsx +++ b/apps/backend/src/lib/tokens.tsx @@ -4,6 +4,7 @@ import { decryptJWT, encryptJWT } from '@stackframe/stack-shared/dist/utils/jwt' import { KnownErrors } from '@stackframe/stack-shared'; import { prismaClient } from '@/prisma-client'; import { generateSecureRandomString } from '@stackframe/stack-shared/dist/utils/crypto'; +import { getEnvVariable } from '@stackframe/stack-shared/dist/utils/env'; export const authorizationHeaderSchema = yup.string().matches(/^StackSession [^ ]+$/); @@ -56,7 +57,7 @@ export async function encodeAccessToken({ projectId: string, userId: string, }) { - return await encryptJWT({ projectId, userId }, process.env.STACK_ACCESS_TOKEN_EXPIRATION_TIME || '1h'); + return await encryptJWT({ projectId, userId }, getEnvVariable("STACK_ACCESS_TOKEN_EXPIRATION_TIME", "1h")); } export async function createAuthTokens({ diff --git a/apps/backend/src/oauth/providers/facebook.tsx b/apps/backend/src/oauth/providers/facebook.tsx index 27d475d5..fd715722 100644 --- a/apps/backend/src/oauth/providers/facebook.tsx +++ b/apps/backend/src/oauth/providers/facebook.tsx @@ -1,6 +1,7 @@ import { TokenSet } from "openid-client"; import { OAuthBaseProvider } from "./base"; import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; export class FacebookProvider extends OAuthBaseProvider { constructor(options: { @@ -11,7 +12,7 @@ export class FacebookProvider extends OAuthBaseProvider { issuer: "https://www.facebook.com", authorizationEndpoint: "https://facebook.com/v20.0/dialog/oauth/", tokenEndpoint: "https://graph.facebook.com/v20.0/oauth/access_token", - redirectUri: process.env.NEXT_PUBLIC_STACK_URL + "/api/v1/auth/callback/facebook", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_BACKEND_URL") + "/api/v1/auth/callback/facebook", baseScope: "public_profile email", ...options }); diff --git a/apps/backend/src/oauth/providers/github.tsx b/apps/backend/src/oauth/providers/github.tsx index 2b387990..4a2bb32d 100644 --- a/apps/backend/src/oauth/providers/github.tsx +++ b/apps/backend/src/oauth/providers/github.tsx @@ -1,6 +1,7 @@ import { TokenSet } from "openid-client"; import { OAuthBaseProvider } from "./base"; import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; export class GithubProvider extends OAuthBaseProvider { constructor(options: { @@ -12,7 +13,7 @@ export class GithubProvider extends OAuthBaseProvider { authorizationEndpoint: "https://github.com/login/oauth/authorize", tokenEndpoint: "https://github.com/login/oauth/access_token", userinfoEndpoint: "https://api.github.com/user", - redirectUri: process.env.NEXT_PUBLIC_STACK_URL + "/api/v1/auth/callback/github", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_BACKEND_URL") + "/api/v1/auth/callback/github", baseScope: "user:email", ...options, }); diff --git a/apps/backend/src/oauth/providers/google.tsx b/apps/backend/src/oauth/providers/google.tsx index 3034d034..0a124ef6 100644 --- a/apps/backend/src/oauth/providers/google.tsx +++ b/apps/backend/src/oauth/providers/google.tsx @@ -1,6 +1,7 @@ import { TokenSet } from "openid-client"; import { OAuthBaseProvider } from "./base"; import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; export class GoogleProvider extends OAuthBaseProvider { constructor(options: { @@ -12,7 +13,7 @@ export class GoogleProvider extends OAuthBaseProvider { authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth", tokenEndpoint: "https://oauth2.googleapis.com/token", userinfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo", - redirectUri: process.env.NEXT_PUBLIC_STACK_URL + "/api/v1/auth/callback/google", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_BACKEND_URL") + "/api/v1/auth/callback/google", baseScope: "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile", ...options, }); diff --git a/apps/backend/src/oauth/providers/microsoft.tsx b/apps/backend/src/oauth/providers/microsoft.tsx index a6aaf005..bc20f4ae 100644 --- a/apps/backend/src/oauth/providers/microsoft.tsx +++ b/apps/backend/src/oauth/providers/microsoft.tsx @@ -1,6 +1,7 @@ import { TokenSet } from "openid-client"; import { OAuthBaseProvider } from "./base"; import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; export class MicrosoftProvider extends OAuthBaseProvider { constructor(options: { @@ -11,7 +12,7 @@ export class MicrosoftProvider extends OAuthBaseProvider { issuer: "https://login.microsoftonline.com", authorizationEndpoint: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize", tokenEndpoint: "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", - redirectUri: process.env.NEXT_PUBLIC_STACK_URL + "/api/v1/auth/callback/microsoft", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_BACKEND_URL") + "/api/v1/auth/callback/microsoft", baseScope: "User.Read", ...options, }); diff --git a/apps/backend/src/oauth/providers/spotify.tsx b/apps/backend/src/oauth/providers/spotify.tsx index bae5241d..e204f8ea 100644 --- a/apps/backend/src/oauth/providers/spotify.tsx +++ b/apps/backend/src/oauth/providers/spotify.tsx @@ -1,6 +1,7 @@ import { TokenSet } from "openid-client"; import { OAuthBaseProvider } from "./base"; import { OAuthUserInfo, validateUserInfo } from "../utils"; +import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; export class SpotifyProvider extends OAuthBaseProvider { constructor(options: { @@ -11,7 +12,7 @@ export class SpotifyProvider extends OAuthBaseProvider { issuer: "https://accounts.spotify.com", authorizationEndpoint: "https://accounts.spotify.com/authorize", tokenEndpoint: "https://accounts.spotify.com/api/token", - redirectUri: process.env.NEXT_PUBLIC_STACK_URL + "/api/v1/auth/callback/spotify", + redirectUri: getEnvVariable("NEXT_PUBLIC_STACK_BACKEND_URL") + "/api/v1/auth/callback/spotify", baseScope: "user-read-email user-read-private", ...options, }); diff --git a/apps/backend/src/prisma-client.tsx b/apps/backend/src/prisma-client.tsx index e399f372..b8729e4a 100644 --- a/apps/backend/src/prisma-client.tsx +++ b/apps/backend/src/prisma-client.tsx @@ -1,5 +1,6 @@ import { PrismaClient } from '@prisma/client'; +import { getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env'; // In dev mode, fast refresh causes us to recreate many Prisma clients, eventually overloading the database. // Therefore, only create one Prisma client in dev mode. @@ -8,7 +9,7 @@ const globalForPrisma = global as unknown as { prisma: PrismaClient }; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition export const prismaClient = globalForPrisma.prisma || new PrismaClient(); -if (process.env.NODE_ENV !== 'production') { +if (getNodeEnvironment() !== 'production') { globalForPrisma.prisma = prismaClient; } diff --git a/packages/stack-shared/src/utils/env.tsx b/packages/stack-shared/src/utils/env.tsx index 2b57190d..1ad2bb00 100644 --- a/packages/stack-shared/src/utils/env.tsx +++ b/packages/stack-shared/src/utils/env.tsx @@ -6,9 +6,9 @@ export function isBrowserLike() { } /** - * Returns the environment variable with the given name, throwing an error if it's undefined or the empty string. + * Returns the environment variable with the given name, returning the default (if given) or throwing an error (otherwise) if it's undefined or the empty string. */ -export function getEnvVariable(name: string): string { +export function getEnvVariable(name: string, defaultValue?: string | undefined): string { if (isBrowserLike()) { throw new Error(deindent` Can't use getEnvVariable on the client because Next.js transpiles expressions of the kind process.env.XYZ at build-time on the client. @@ -17,5 +17,9 @@ export function getEnvVariable(name: string): string { `); } - return (process.env[name] ?? throwErr(`Missing environment variable: ${name}`)) || throwErr(`Empty environment variable: ${name}`); + return ((process.env[name] || defaultValue) ?? throwErr(`Missing environment variable: ${name}`)) || (defaultValue ?? throwErr(`Empty environment variable: ${name}`)); +} + +export function getNodeEnvironment() { + return getEnvVariable("NODE_ENV", ""); } diff --git a/turbo.json b/turbo.json index 60b87803..a43c011f 100644 --- a/turbo.json +++ b/turbo.json @@ -7,7 +7,8 @@ "SENTRY_*", "VERCEL_GIT_COMMIT_SHA", "NEXT_PUBLIC_POSTHOG_*", - "POSTHOG_*" + "POSTHOG_*", + "NODE_ENV" ], "tasks": { "build": {