From 9ddbec5643c171c7143d9ed438b895d248148bb0 Mon Sep 17 00:00:00 2001 From: shangzy Date: Tue, 16 Jun 2026 11:09:11 +0800 Subject: [PATCH] refactor: remove OIDC authentication --- .env.production | 5 -- README.md | 29 ------- app/api/auth/[...nextauth]/route.ts | 47 ----------- bun.lock | 35 +------- docker-compose.coolify.yml | 6 -- lib/auth-config.test.ts | 88 -------------------- lib/auth-config.ts | 102 ------------------------ package.json | 1 - proxy.test.ts | 26 ------ proxy.ts | 119 ---------------------------- 10 files changed, 2 insertions(+), 456 deletions(-) delete mode 100644 app/api/auth/[...nextauth]/route.ts delete mode 100644 lib/auth-config.test.ts delete mode 100644 lib/auth-config.ts delete mode 100644 proxy.test.ts delete mode 100644 proxy.ts diff --git a/.env.production b/.env.production index 6c14059..5ad3659 100644 --- a/.env.production +++ b/.env.production @@ -1,6 +1 @@ PG_CONNECTION_STRING=postgres://postgres:XigzIbHE0khmlGgnHSlz7Lsd7OkPTigKcfwM8z0iIcjMggCrcFaUwgiNtloklj8z@114.116.243.78:5432/new-api -OIDC_ISSUER=https://door.copilot.shenyang-bridge.com -OIDC_CLIENT_ID=da95b2f28cf2fccb5c75 -OIDC_CLIENT_SECRET=f2bac46946033c842cf25362f233743e6adc66b5 -AUTH_SECRET=lnT5Ewn25S2Rps0t5aw4VbiPoHFAt72FpCea4KsLUy0= -OIDC_PROVIDER_NAME=Sinodoor diff --git a/README.md b/README.md index d1aba48..849de20 100644 --- a/README.md +++ b/README.md @@ -40,35 +40,6 @@ PG_CONNECTION_STRING=postgres://user:password@host:5432/database The app uses this variable in `lib/db.ts` to create a `pg` connection pool. -OIDC authentication is optional. If no OIDC variables are set, the dashboard and API remain open. -To require login through a standard OIDC provider such as Sinodoor, add: - -```bash -OIDC_ISSUER=https://casdoor.example.com -OIDC_CLIENT_ID=analytics -OIDC_CLIENT_SECRET=replace-me -AUTH_SECRET=replace-with-random-secret -NEXTAUTH_URL=https://your-analytics-domain -# Optional login button label: -OIDC_PROVIDER_NAME=Sinodoor -``` - -Generate `AUTH_SECRET` with a stable random value, for example: - -```bash -openssl rand -base64 32 -``` - -When OIDC is enabled, configure the provider redirect URI as: - -```text -https://your-analytics-domain/api/auth/callback/oidc -``` - -`NEXTAUTH_URL` must be the same public `https://` origin that users open through the reverse proxy. This keeps login redirects and callback URLs from using the container listener such as `http://0.0.0.0:8019`. - -Partial OIDC configuration is treated as an error instead of falling back to open access. - ## Deployment The included Dockerfile builds a standalone Next.js output and starts `server.js` on port `8019`. diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index f01e676..0000000 --- a/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,47 +0,0 @@ -import NextAuth, { type NextAuthOptions } from "next-auth"; -import type { OAuthConfig } from "next-auth/providers/oauth"; -import { - getAuthMode, - getOidcProviderName, - getRequiredAuthSecret, - mapOidcProfile, - type OidcProfile, -} from "@/lib/auth-config"; - -const authMode = getAuthMode(); - -function oidcProvider(): OAuthConfig { - return { - id: "oidc", - name: getOidcProviderName(), - type: "oauth", - wellKnown: `${process.env.OIDC_ISSUER}/.well-known/openid-configuration`, - authorization: { params: { scope: "openid profile email" } }, - checks: ["pkce", "state"], - clientId: process.env.OIDC_CLIENT_ID, - clientSecret: process.env.OIDC_CLIENT_SECRET, - idToken: true, - profile(profile) { - return mapOidcProfile(profile); - }, - }; -} - -const authOptions: NextAuthOptions = { - providers: authMode.enabled ? [oidcProvider()] : [], - secret: authMode.enabled ? getRequiredAuthSecret() : "auth-disabled", - session: { - strategy: "jwt", - }, -}; - -const handler = authMode.enabled - ? NextAuth(authOptions) - : function authDisabled() { - return Response.json( - { error: authMode.error ?? "OIDC authentication is not configured." }, - { status: authMode.error ? 500 : 404 } - ); - }; - -export { handler as GET, handler as POST }; diff --git a/bun.lock b/bun.lock index 9cc2122..0759adf 100644 --- a/bun.lock +++ b/bun.lock @@ -10,7 +10,6 @@ "lucide-react": "^1.7.0", "motion": "^12.38.0", "next": "16.2.2", - "next-auth": "4.24.14", "pg": "^8.20.0", "react": "19.2.4", "react-dom": "19.2.4", @@ -63,8 +62,6 @@ "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], - "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], @@ -193,8 +190,6 @@ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="], - "@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="], - "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="], "@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="], @@ -401,8 +396,6 @@ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -661,8 +654,6 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -717,7 +708,7 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lucide-react": ["lucide-react@1.7.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-yI7BeItCLZJTXikmK4KNUGCKoGzSvbKlfCvw44bU4fXAL6v3gYS4uHD1jzsLkfwODYwI6Drw5Tu9Z5ulDe0TSg=="], @@ -749,18 +740,12 @@ "next": ["next@16.2.2", "", { "dependencies": { "@next/env": "16.2.2", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.2", "@next/swc-darwin-x64": "16.2.2", "@next/swc-linux-arm64-gnu": "16.2.2", "@next/swc-linux-arm64-musl": "16.2.2", "@next/swc-linux-x64-gnu": "16.2.2", "@next/swc-linux-x64-musl": "16.2.2", "@next/swc-win32-arm64-msvc": "16.2.2", "@next/swc-win32-x64-msvc": "16.2.2", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A=="], - "next-auth": ["next-auth@4.24.14", "", { "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", "cookie": "^0.7.0", "jose": "^4.15.5", "oauth": "^0.9.15", "openid-client": "^5.4.0", "preact": "^10.6.3", "preact-render-to-string": "^5.1.19", "uuid": "^8.3.2" }, "peerDependencies": { "@auth/core": "0.34.3", "next": "^12.2.5 || ^13 || ^14 || ^15 || ^16", "nodemailer": "^7.0.7", "react": "^17.0.2 || ^18 || ^19", "react-dom": "^17.0.2 || ^18 || ^19" }, "optionalPeers": ["@auth/core", "nodemailer"] }, "sha512-YRz6xFDXKUwiXSMMChbrBEWyFktZ1qZXEgeSHQQ3nsy08B4c/xLk6REeutRsIFwkjY/1+ShHnu07DN3JeJguig=="], - "node-exports-info": ["node-exports-info@1.6.0", "", { "dependencies": { "array.prototype.flatmap": "^1.3.3", "es-errors": "^1.3.0", "object.entries": "^1.1.9", "semver": "^6.3.1" } }, "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw=="], "node-releases": ["node-releases@2.0.36", "", {}, "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA=="], - "oauth": ["oauth@0.9.15", "", {}, "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], @@ -775,10 +760,6 @@ "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], - "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], - - "openid-client": ["openid-client@5.7.1", "", { "dependencies": { "jose": "^4.15.9", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew=="], - "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], @@ -827,14 +808,8 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "preact": ["preact@10.29.2", "", {}, "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ=="], - - "preact-render-to-string": ["preact-render-to-string@5.2.6", "", { "dependencies": { "pretty-format": "^3.8.0" }, "peerDependencies": { "preact": ">=10" } }, "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw=="], - "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "pretty-format": ["pretty-format@3.8.0", "", {}, "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="], - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], @@ -975,8 +950,6 @@ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], - "victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -993,7 +966,7 @@ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -1003,8 +976,6 @@ "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], @@ -1047,8 +1018,6 @@ "sharp/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], diff --git a/docker-compose.coolify.yml b/docker-compose.coolify.yml index 977039f..b89c3b1 100644 --- a/docker-compose.coolify.yml +++ b/docker-compose.coolify.yml @@ -8,12 +8,6 @@ services: environment: - SERVICE_URL_ANALYTICS_8019 - PG_CONNECTION_STRING - - OIDC_ISSUER - - OIDC_CLIENT_ID - - OIDC_CLIENT_SECRET - - AUTH_SECRET - - NEXTAUTH_URL - - OIDC_PROVIDER_NAME networks: - coolify diff --git a/lib/auth-config.test.ts b/lib/auth-config.test.ts deleted file mode 100644 index f9ea7e7..0000000 --- a/lib/auth-config.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { - getAuthMode, - mapOidcProfile, - isAuthRoute, - isProtectedPath, - type AuthEnv, -} from "./auth-config"; - -describe("optional OIDC auth config", () => { - test("disables auth when no OIDC settings are present", () => { - const env: AuthEnv = {}; - - expect(getAuthMode(env)).toEqual({ enabled: false, error: null }); - }); - - test("does not enable auth when only non-OIDC auth settings are present", () => { - const env: AuthEnv = { - AUTH_SECRET: "session-secret", - OIDC_PROVIDER_NAME: "Casdoor", - }; - - expect(getAuthMode(env)).toEqual({ enabled: false, error: null }); - }); - - test("enables auth when all required OIDC settings are present", () => { - const env: AuthEnv = { - OIDC_ISSUER: "https://door.example.com", - OIDC_CLIENT_ID: "analytics", - OIDC_CLIENT_SECRET: "secret", - AUTH_SECRET: "session-secret", - }; - - expect(getAuthMode(env)).toEqual({ enabled: true, error: null }); - }); - - test("reports missing settings when OIDC config is partial", () => { - const env: AuthEnv = { - OIDC_ISSUER: "https://door.example.com", - OIDC_CLIENT_ID: "analytics", - }; - - expect(getAuthMode(env)).toEqual({ - enabled: false, - error: "Missing required auth environment variables: OIDC_CLIENT_SECRET, AUTH_SECRET", - }); - }); -}); - -describe("auth route matching", () => { - test("protects analytics pages and API data routes", () => { - expect(isProtectedPath("/")).toBe(true); - expect(isProtectedPath("/logs")).toBe(true); - expect(isProtectedPath("/detail/user/alice")).toBe(true); - expect(isProtectedPath("/api/overview")).toBe(true); - expect(isProtectedPath("/api/detail/user/alice")).toBe(true); - }); - - test("does not protect auth or static asset routes", () => { - expect(isProtectedPath("/api/auth/signin")).toBe(false); - expect(isProtectedPath("/_next/static/chunk.js")).toBe(false); - expect(isProtectedPath("/favicon.ico")).toBe(false); - expect(isProtectedPath("/icon.svg")).toBe(false); - }); - - test("detects auth routes", () => { - expect(isAuthRoute("/api/auth/signin")).toBe(true); - expect(isAuthRoute("/api/overview")).toBe(false); - }); -}); - -describe("OIDC profile mapping", () => { - test("uses standard OIDC profile claims for the NextAuth user", () => { - expect( - mapOidcProfile({ - sub: "user-123", - preferred_username: "alice", - email: "alice@example.com", - picture: "https://example.com/alice.png", - }) - ).toEqual({ - id: "user-123", - name: "alice", - email: "alice@example.com", - image: "https://example.com/alice.png", - }); - }); -}); diff --git a/lib/auth-config.ts b/lib/auth-config.ts deleted file mode 100644 index 14306ab..0000000 --- a/lib/auth-config.ts +++ /dev/null @@ -1,102 +0,0 @@ -export interface AuthEnv { - OIDC_ISSUER?: string; - OIDC_CLIENT_ID?: string; - OIDC_CLIENT_SECRET?: string; - OIDC_PROVIDER_NAME?: string; - AUTH_SECRET?: string; - NEXTAUTH_URL?: string; -} - -export interface AuthMode { - enabled: boolean; - error: string | null; -} - -export interface OidcProfile { - sub: unknown; - name?: unknown; - preferred_username?: unknown; - email?: unknown; - picture?: unknown; -} - -const REQUIRED_AUTH_KEYS = [ - "OIDC_ISSUER", - "OIDC_CLIENT_ID", - "OIDC_CLIENT_SECRET", - "AUTH_SECRET", -] as const; - -const OIDC_KEYS = [ - "OIDC_ISSUER", - "OIDC_CLIENT_ID", - "OIDC_CLIENT_SECRET", -] as const; - -const STATIC_FILE_PATTERN = /\.(?:ico|svg|png|jpg|jpeg|gif|webp|css|js|map|txt|xml|json)$/i; - -export function getAuthMode(env: AuthEnv = process.env): AuthMode { - const hasAnyOidcConfig = OIDC_KEYS.some((key) => Boolean(trimEnv(env[key]))); - if (!hasAnyOidcConfig) return { enabled: false, error: null }; - - const missing = REQUIRED_AUTH_KEYS.filter((key) => !trimEnv(env[key])); - if (missing.length > 0) { - return { - enabled: false, - error: `Missing required auth environment variables: ${missing.join(", ")}`, - }; - } - - return { enabled: true, error: null }; -} - -export function isAuthEnabled(env: AuthEnv = process.env): boolean { - return getAuthMode(env).enabled; -} - -export function isAuthRoute(pathname: string): boolean { - return pathname === "/api/auth" || pathname.startsWith("/api/auth/"); -} - -export function isProtectedPath(pathname: string): boolean { - if (isAuthRoute(pathname)) return false; - if (pathname.startsWith("/_next/")) return false; - if (pathname === "/favicon.ico" || pathname === "/robots.txt" || pathname === "/sitemap.xml") return false; - if (STATIC_FILE_PATTERN.test(pathname)) return false; - - return pathname === "/" || pathname.startsWith("/api/") || pathname.startsWith("/"); -} - -export function getOidcProviderName(env: AuthEnv = process.env): string { - return trimEnv(env.OIDC_PROVIDER_NAME) || "OIDC"; -} - -export function getRequiredAuthSecret(env: AuthEnv = process.env): string { - const secret = trimEnv(env.AUTH_SECRET); - if (!secret) throw new Error("Missing required auth environment variable: AUTH_SECRET"); - return secret; -} - -export function mapOidcProfile(profile: OidcProfile) { - const subject = String(profile.sub); - const name = firstString(profile.name, profile.preferred_username, profile.email, subject); - - return { - id: subject, - name, - email: typeof profile.email === "string" ? profile.email : null, - image: typeof profile.picture === "string" ? profile.picture : null, - }; -} - -function trimEnv(value: string | undefined): string { - return value?.trim() ?? ""; -} - -function firstString(...values: unknown[]): string { - for (const value of values) { - if (typeof value === "string" && value.trim()) return value; - } - - return ""; -} diff --git a/package.json b/package.json index 0df9a85..2b690fa 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "lucide-react": "^1.7.0", "motion": "^12.38.0", "next": "16.2.2", - "next-auth": "4.24.14", "pg": "^8.20.0", "react": "19.2.4", "react-dom": "19.2.4", diff --git a/proxy.test.ts b/proxy.test.ts deleted file mode 100644 index c35ae15..0000000 --- a/proxy.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { NextRequest } from "next/server"; -import { buildPublicRequestUrl } from "./proxy"; - -describe("public request URL", () => { - test("uses NEXTAUTH_URL for OIDC callback URLs behind Docker reverse proxies", () => { - const request = new NextRequest("http://0.0.0.0:8019/logs?range=7d"); - - expect( - buildPublicRequestUrl(request, { - NEXTAUTH_URL: "https://analytics.example.com", - }).href - ).toBe("https://analytics.example.com/logs?range=7d"); - }); - - test("falls back to forwarded proxy headers when no public URL is configured", () => { - const request = new NextRequest("http://0.0.0.0:8019/", { - headers: { - "x-forwarded-host": "analytics.example.com", - "x-forwarded-proto": "https", - }, - }); - - expect(buildPublicRequestUrl(request).href).toBe("https://analytics.example.com/"); - }); -}); diff --git a/proxy.ts b/proxy.ts deleted file mode 100644 index 820acc3..0000000 --- a/proxy.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { getToken } from "next-auth/jwt"; -import { NextResponse, type NextRequest } from "next/server"; -import { - getAuthMode, - getRequiredAuthSecret, - isAuthRoute, - isProtectedPath, - type AuthEnv, -} from "@/lib/auth-config"; - -export async function proxy(request: NextRequest) { - const { pathname } = request.nextUrl; - if (!isProtectedPath(pathname)) return NextResponse.next(); - - const authMode = getAuthMode(); - if (!authMode.enabled && !authMode.error) return NextResponse.next(); - - if (authMode.error) { - return authConfigErrorResponse(request, authMode.error); - } - - const token = await getToken({ - req: request, - secret: getRequiredAuthSecret(), - }); - - if (token) return NextResponse.next(); - - if (pathname.startsWith("/api/")) { - return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); - } - - const callbackUrl = buildPublicRequestUrl(request); - const signInUrl = new URL("/api/auth/signin/oidc", callbackUrl); - signInUrl.searchParams.set("callbackUrl", callbackUrl.href); - return NextResponse.redirect(signInUrl); -} - -export const config = { - matcher: ["/((?!_next/static|_next/image|favicon.ico|icon.svg).*)"], -}; - -function authConfigErrorResponse(request: NextRequest, error: string) { - if (request.nextUrl.pathname.startsWith("/api/") && !isAuthRoute(request.nextUrl.pathname)) { - return NextResponse.json({ error }, { status: 500 }); - } - - return new NextResponse(authErrorHtml(error), { - status: 500, - headers: { - "content-type": "text/html; charset=utf-8", - }, - }); -} - -export function buildPublicRequestUrl(request: NextRequest, env: AuthEnv = process.env): URL { - const origin = getPublicOrigin(request, env); - return new URL(`${request.nextUrl.pathname}${request.nextUrl.search}`, origin); -} - -function getPublicOrigin(request: NextRequest, env: AuthEnv): string { - const configuredOrigin = getConfiguredOrigin(env.NEXTAUTH_URL); - if (configuredOrigin) return configuredOrigin; - - const forwardedHost = firstHeaderValue(request.headers.get("x-forwarded-host")); - const host = forwardedHost || firstHeaderValue(request.headers.get("host")) || request.nextUrl.host; - const forwardedProto = firstHeaderValue(request.headers.get("x-forwarded-proto")); - const proto = forwardedProto || request.nextUrl.protocol.replace(":", "") || "http"; - - return `${proto}://${host}`; -} - -function getConfiguredOrigin(value: string | undefined): string | null { - if (!value?.trim()) return null; - - try { - return new URL(value).origin; - } catch { - return null; - } -} - -function firstHeaderValue(value: string | null): string { - return value?.split(",")[0]?.trim() ?? ""; -} - -function authErrorHtml(error: string): string { - return ` - - - - - Authentication Configuration Error - - - -
-

OIDC 配置不完整

-

${escapeHtml(error)}

-

请补全 OIDC 配置,或移除全部 OIDC 相关变量以保持开放访问。

-
- -`; -} - -function escapeHtml(value: string): string { - return value - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); -}