mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-17 18:36:37 +00:00
Merge remote-tracking branch 'origin/dev' into update-packages
This commit is contained in:
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
@@ -44,19 +44,9 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
dev-patch-updates:
|
||||
dependency-type: "development"
|
||||
patch-updates:
|
||||
update-types:
|
||||
- "patch"
|
||||
dev-minor-updates:
|
||||
dependency-type: "development"
|
||||
minor-updates:
|
||||
update-types:
|
||||
- "minor"
|
||||
prod-patch-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "patch"
|
||||
prod-minor-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "minor"
|
||||
57
Dockerfile
57
Dockerfile
@@ -1,21 +1,11 @@
|
||||
FROM node:24-alpine AS builder
|
||||
|
||||
# OCI Image Labels - Build Args for dynamic values
|
||||
ARG VERSION="dev"
|
||||
ARG REVISION=""
|
||||
ARG CREATED=""
|
||||
ARG LICENSE="AGPL-3.0"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG BUILD=oss
|
||||
ARG DATABASE=sqlite
|
||||
|
||||
# Derive title and description based on BUILD type
|
||||
ARG IMAGE_TITLE="Pangolin"
|
||||
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
||||
|
||||
RUN apk add --no-cache curl tzdata python3 make g++
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package*.json ./
|
||||
@@ -23,41 +13,31 @@ RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN echo "export * from \"./$DATABASE\";" > server/db/index.ts
|
||||
RUN echo "export const driver: \"pg\" | \"sqlite\" = \"$DATABASE\";" >> server/db/index.ts
|
||||
|
||||
RUN echo "export const build = \"$BUILD\" as \"saas\" | \"enterprise\" | \"oss\";" > server/build.ts
|
||||
|
||||
# Copy the appropriate TypeScript configuration based on build type
|
||||
RUN if [ "$BUILD" = "oss" ]; then cp tsconfig.oss.json tsconfig.json; \
|
||||
elif [ "$BUILD" = "saas" ]; then cp tsconfig.saas.json tsconfig.json; \
|
||||
elif [ "$BUILD" = "enterprise" ]; then cp tsconfig.enterprise.json tsconfig.json; \
|
||||
fi
|
||||
|
||||
# if the build is oss then remove the server/private directory
|
||||
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi
|
||||
|
||||
RUN if [ "$DATABASE" = "pg" ]; then npx drizzle-kit generate --dialect postgresql --schema ./server/db/pg/schema --out init; else npx drizzle-kit generate --dialect $DATABASE --schema ./server/db/$DATABASE/schema --out init; fi
|
||||
|
||||
RUN mkdir -p dist
|
||||
RUN npm run next:build
|
||||
RUN node esbuild.mjs -e server/index.ts -o dist/server.mjs -b $BUILD
|
||||
RUN if [ "$DATABASE" = "pg" ]; then \
|
||||
node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs; \
|
||||
else \
|
||||
node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs; \
|
||||
fi
|
||||
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
|
||||
npm run set:$DATABASE && \
|
||||
npm run set:$BUILD && \
|
||||
npm run db:$DATABASE:generate && \
|
||||
npm run build:$DATABASE && \
|
||||
npm run build:cli
|
||||
|
||||
# test to make sure the build output is there and error if not
|
||||
RUN test -f dist/server.mjs
|
||||
|
||||
RUN npm run build:cli
|
||||
|
||||
# Prune dev dependencies and clean up to prepare for copy to runner
|
||||
RUN npm prune --omit=dev && npm cache clean --force
|
||||
|
||||
FROM node:24-alpine AS runner
|
||||
|
||||
# OCI Image Labels - Build Args for dynamic values
|
||||
ARG VERSION="dev"
|
||||
ARG REVISION=""
|
||||
ARG CREATED=""
|
||||
ARG LICENSE="AGPL-3.0"
|
||||
|
||||
# Derive title and description based on BUILD type
|
||||
ARG IMAGE_TITLE="Pangolin"
|
||||
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Only curl and tzdata needed at runtime - no build tools!
|
||||
@@ -66,11 +46,10 @@ RUN apk add --no-cache curl tzdata
|
||||
# Copy pre-built node_modules from builder (already pruned to production only)
|
||||
# This includes the compiled native modules like better-sqlite3
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/init ./dist/init
|
||||
COPY --from=builder /app/server/migrations ./dist/init
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
||||
|
||||
190
package-lock.json
generated
190
package-lock.json
generated
@@ -51,10 +51,8 @@
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"cookie": "1.1.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cookies": "0.9.1",
|
||||
"cors": "2.8.6",
|
||||
"cors": "2.8.5",
|
||||
"crypto-js": "4.2.0",
|
||||
"d3": "7.9.0",
|
||||
"date-fns": "4.1.0",
|
||||
@@ -66,7 +64,6 @@
|
||||
"glob": "13.0.0",
|
||||
"helmet": "8.1.0",
|
||||
"http-errors": "2.0.1",
|
||||
"i": "0.3.7",
|
||||
"input-otp": "1.4.2",
|
||||
"ioredis": "5.9.2",
|
||||
"jmespath": "0.16.0",
|
||||
@@ -80,10 +77,7 @@
|
||||
"next-themes": "0.4.6",
|
||||
"nextjs-toploader": "3.9.17",
|
||||
"node-cache": "5.1.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "7.0.13",
|
||||
"npm": "11.8.0",
|
||||
"nprogress": "0.2.0",
|
||||
"nodemailer": "7.0.11",
|
||||
"oslo": "1.2.1",
|
||||
"pg": "8.18.0",
|
||||
"posthog-node": "5.24.7",
|
||||
@@ -94,7 +88,6 @@
|
||||
"react-easy-sort": "1.8.0",
|
||||
"react-hook-form": "7.71.1",
|
||||
"react-icons": "5.5.0",
|
||||
"rebuild": "0.1.2",
|
||||
"recharts": "2.15.4",
|
||||
"reodotdev": "1.0.0",
|
||||
"resend": "6.9.1",
|
||||
@@ -11026,19 +11019,6 @@
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-parser": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
|
||||
@@ -11067,19 +11047,6 @@
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookies": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
|
||||
"integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"keygrip": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
|
||||
@@ -11561,15 +11528,6 @@
|
||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-view-buffer": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
|
||||
@@ -13804,29 +13762,6 @@
|
||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -14030,18 +13965,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -14553,14 +14476,6 @@
|
||||
"node": ">=10.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz",
|
||||
"integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==",
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
@@ -15351,19 +15266,6 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keygrip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
|
||||
"integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tsscmp": "1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -16430,44 +16332,6 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
@@ -18662,15 +18526,6 @@
|
||||
"yaml": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/optimist": {
|
||||
"version": "0.3.7",
|
||||
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz",
|
||||
"integrity": "sha512-TCx0dXQzVtSCg2OgY/bO9hjM9cV4XYx09TVK+s3+FhkjT6LovsLe+pPMzpWf+6yXK/hUizs2gUoTw3jHM0VaTQ==",
|
||||
"license": "MIT/X11",
|
||||
"dependencies": {
|
||||
"wordwrap": "~0.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -20657,20 +20512,6 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/rebuild": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rebuild/-/rebuild-0.1.2.tgz",
|
||||
"integrity": "sha512-EtDZ5IapND57htCrOOcfH7MzXCQKivzSZUIZIuc8H0xDHfmi9HDBZIyjT7Neh5GcUoxQ6hfsXluC+UrYLgGbZg==",
|
||||
"dependencies": {
|
||||
"optimist": "0.3.x"
|
||||
},
|
||||
"bin": {
|
||||
"rebuild": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.8"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz",
|
||||
@@ -22315,15 +22156,6 @@
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsscmp": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
|
||||
"integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.x"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
@@ -22804,15 +22636,6 @@
|
||||
"integrity": "sha512-jHl/NQgASfw5ZML3cnbjdfr/gXK5zO8a2xKSoCVe+5+EsIaO9tMTh7SsnfhESnCpZ+Xb6XBeU91wiuyERUPshQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/when-exit": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
|
||||
@@ -22985,15 +22808,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wordwrap": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
|
||||
"integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
|
||||
16
package.json
16
package.json
@@ -12,6 +12,8 @@
|
||||
"license": "SEE LICENSE IN LICENSE AND README.md",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
|
||||
"dev:check": "npx tsc --noEmit && npm run format:check",
|
||||
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push",
|
||||
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
|
||||
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
|
||||
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
|
||||
@@ -24,12 +26,13 @@
|
||||
"set:enterprise": "echo 'export const build = \"enterprise\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.enterprise.json tsconfig.json",
|
||||
"set:sqlite": "echo 'export * from \"./sqlite\";\nexport const driver: \"pg\" | \"sqlite\" = \"sqlite\";' > server/db/index.ts",
|
||||
"set:pg": "echo 'export * from \"./pg\";\nexport const driver: \"pg\" | \"sqlite\" = \"pg\";' > server/db/index.ts",
|
||||
"next:build": "next build",
|
||||
"build:next": "next build",
|
||||
"build:sqlite": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs",
|
||||
"build:pg": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs",
|
||||
"start": "ENVIRONMENT=prod node dist/migrations.mjs && ENVIRONMENT=prod NODE_ENV=development node --enable-source-maps dist/server.mjs",
|
||||
"email": "email dev --dir server/emails/templates --port 3005",
|
||||
"build:cli": "node esbuild.mjs -e cli/index.ts -o dist/cli.mjs",
|
||||
"format:check": "prettier --check .",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -75,10 +78,8 @@
|
||||
"class-variance-authority": "0.7.1",
|
||||
"clsx": "2.1.1",
|
||||
"cmdk": "1.1.1",
|
||||
"cookie": "1.1.1",
|
||||
"cookie-parser": "1.4.7",
|
||||
"cookies": "0.9.1",
|
||||
"cors": "2.8.6",
|
||||
"cors": "2.8.5",
|
||||
"crypto-js": "4.2.0",
|
||||
"d3": "7.9.0",
|
||||
"date-fns": "4.1.0",
|
||||
@@ -90,7 +91,6 @@
|
||||
"glob": "13.0.0",
|
||||
"helmet": "8.1.0",
|
||||
"http-errors": "2.0.1",
|
||||
"i": "0.3.7",
|
||||
"input-otp": "1.4.2",
|
||||
"ioredis": "5.9.2",
|
||||
"jmespath": "0.16.0",
|
||||
@@ -104,10 +104,7 @@
|
||||
"next-themes": "0.4.6",
|
||||
"nextjs-toploader": "3.9.17",
|
||||
"node-cache": "5.1.2",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "7.0.13",
|
||||
"npm": "11.8.0",
|
||||
"nprogress": "0.2.0",
|
||||
"nodemailer": "7.0.11",
|
||||
"oslo": "1.2.1",
|
||||
"pg": "8.18.0",
|
||||
"posthog-node": "5.24.7",
|
||||
@@ -118,7 +115,6 @@
|
||||
"react-easy-sort": "1.8.0",
|
||||
"react-hook-form": "7.71.1",
|
||||
"react-icons": "5.5.0",
|
||||
"rebuild": "0.1.2",
|
||||
"recharts": "2.15.4",
|
||||
"reodotdev": "1.0.0",
|
||||
"resend": "6.9.1",
|
||||
|
||||
@@ -37,27 +37,55 @@ const paramsSchema = z.strictObject({
|
||||
const bodySchema = z.strictObject({
|
||||
logoUrl: z
|
||||
.union([
|
||||
z.string().length(0),
|
||||
z.url().refine(
|
||||
async (url) => {
|
||||
z.literal(""),
|
||||
z
|
||||
.url("Must be a valid URL")
|
||||
.superRefine(async (url, ctx) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
return (
|
||||
response.status === 200 &&
|
||||
(
|
||||
response.headers.get("content-type") ?? ""
|
||||
).startsWith("image/")
|
||||
);
|
||||
const response = await fetch(url, {
|
||||
method: "HEAD"
|
||||
}).catch(() => {
|
||||
// If HEAD fails (CORS or method not allowed), try GET
|
||||
return fetch(url, { method: "GET" });
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `Failed to load image. Please check that the URL is accessible.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType =
|
||||
response.headers.get("content-type") ?? "";
|
||||
if (!contentType.startsWith("image/")) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `URL does not point to an image. Please provide a URL to an image file (e.g., .png, .jpg, .svg).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
return false;
|
||||
let errorMessage =
|
||||
"Unable to verify image URL. Please check that the URL is accessible and points to an image file.";
|
||||
|
||||
if (error instanceof TypeError && error.message.includes("fetch")) {
|
||||
errorMessage =
|
||||
"Network error: Unable to reach the URL. Please check your internet connection and verify the URL is correct.";
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = `Error verifying URL: ${error.message}`;
|
||||
}
|
||||
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: errorMessage
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
error: "Invalid logo URL, must be a valid image URL"
|
||||
}
|
||||
)
|
||||
})
|
||||
])
|
||||
.optional(),
|
||||
.transform((val) => (val === "" ? null : val))
|
||||
.nullish(),
|
||||
logoWidth: z.coerce.number<number>().min(1),
|
||||
logoHeight: z.coerce.number<number>().min(1),
|
||||
resourceTitle: z.string(),
|
||||
@@ -117,9 +145,8 @@ export async function upsertLoginPageBranding(
|
||||
typeof loginPageBranding
|
||||
>;
|
||||
|
||||
if ((updateData.logoUrl ?? "").trim().length === 0) {
|
||||
updateData.logoUrl = undefined;
|
||||
}
|
||||
// Empty strings are transformed to null by the schema, which will clear the logo URL in the database
|
||||
// We keep it as null (not undefined) because undefined fields are omitted from Drizzle updates
|
||||
|
||||
if (
|
||||
build !== "saas" &&
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db, olms } from "@server/db";
|
||||
import { db, olms, users } from "@server/db";
|
||||
import { clients, currentFingerprint } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
@@ -36,6 +36,7 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
||||
currentFingerprint,
|
||||
eq(olms.olmId, currentFingerprint.olmId)
|
||||
)
|
||||
.leftJoin(users, eq(clients.userId, users.userId))
|
||||
.limit(1);
|
||||
return res;
|
||||
} else if (niceId && orgId) {
|
||||
@@ -48,6 +49,7 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
|
||||
currentFingerprint,
|
||||
eq(olms.olmId, currentFingerprint.olmId)
|
||||
)
|
||||
.leftJoin(users, eq(clients.userId, users.userId))
|
||||
.limit(1);
|
||||
return res;
|
||||
}
|
||||
@@ -207,6 +209,9 @@ export type GetClientResponse = NonNullable<
|
||||
olmId: string | null;
|
||||
agent: string | null;
|
||||
olmVersion: string | null;
|
||||
userEmail: string | null;
|
||||
userName: string | null;
|
||||
userUsername: string | null;
|
||||
fingerprint: {
|
||||
username: string | null;
|
||||
hostname: string | null;
|
||||
@@ -322,6 +327,9 @@ export async function getClient(
|
||||
olmId: client.olms ? client.olms.olmId : null,
|
||||
agent: client.olms?.agent || null,
|
||||
olmVersion: client.olms?.version || null,
|
||||
userEmail: client.user?.email ?? null,
|
||||
userName: client.user?.name ?? null,
|
||||
userUsername: client.user?.username ?? null,
|
||||
fingerprint: fingerprintData,
|
||||
posture: postureData
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||
import { validateSessionToken } from "@server/auth/sessions/app";
|
||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { getUserDeviceName } from "@server/db/names";
|
||||
import { buildSiteConfigurationForOlmClient } from "./buildConfiguration";
|
||||
import { OlmErrorCodes, sendOlmError } from "./error";
|
||||
import { handleFingerprintInsertion } from "./fingerprintingUtils";
|
||||
@@ -97,6 +98,21 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const deviceModel = fingerprint?.deviceModel ?? null;
|
||||
const computedName = getUserDeviceName(deviceModel, client.name);
|
||||
if (computedName && computedName !== client.name) {
|
||||
await db
|
||||
.update(clients)
|
||||
.set({ name: computedName })
|
||||
.where(eq(clients.clientId, client.clientId));
|
||||
}
|
||||
if (computedName && computedName !== olm.name) {
|
||||
await db
|
||||
.update(olms)
|
||||
.set({ name: computedName })
|
||||
.where(eq(olms.olmId, olm.olmId));
|
||||
}
|
||||
|
||||
const [org] = await db
|
||||
.select()
|
||||
.from(orgs)
|
||||
|
||||
@@ -43,25 +43,52 @@ export type AuthPageCustomizationProps = {
|
||||
|
||||
const AuthPageFormSchema = z.object({
|
||||
logoUrl: z.union([
|
||||
z.string().length(0),
|
||||
z.url().refine(
|
||||
async (url) => {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
return (
|
||||
response.status === 200 &&
|
||||
(response.headers.get("content-type") ?? "").startsWith(
|
||||
"image/"
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
return false;
|
||||
z.literal(""),
|
||||
z.url("Must be a valid URL").superRefine(async (url, ctx) => {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "HEAD"
|
||||
}).catch(() => {
|
||||
// If HEAD fails (CORS or method not allowed), try GET
|
||||
return fetch(url, { method: "GET" });
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `Failed to load image. Please check that the URL is accessible.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
{
|
||||
error: "Invalid logo URL, must be a valid image URL"
|
||||
|
||||
const contentType = response.headers.get("content-type") ?? "";
|
||||
if (!contentType.startsWith("image/")) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: `URL does not point to an image. Please provide a URL to an image file (e.g., .png, .jpg, .svg).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
let errorMessage =
|
||||
"Unable to verify image URL. Please check that the URL is accessible and points to an image file.";
|
||||
|
||||
if (
|
||||
error instanceof TypeError &&
|
||||
error.message.includes("fetch")
|
||||
) {
|
||||
errorMessage =
|
||||
"Network error: Unable to reach the URL. Please check your internet connection and verify the URL is correct.";
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = `Error verifying URL: ${error.message}`;
|
||||
}
|
||||
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: errorMessage
|
||||
});
|
||||
}
|
||||
)
|
||||
})
|
||||
]),
|
||||
logoWidth: z.coerce.number<number>().min(1),
|
||||
logoHeight: z.coerce.number<number>().min(1),
|
||||
@@ -405,9 +432,7 @@ export default function AuthPageBrandingForm({
|
||||
<Button
|
||||
variant="destructive"
|
||||
type="submit"
|
||||
loading={
|
||||
isUpdatingBranding || isDeletingBranding
|
||||
}
|
||||
loading={isDeletingBranding}
|
||||
disabled={
|
||||
isUpdatingBranding ||
|
||||
isDeletingBranding ||
|
||||
@@ -422,7 +447,7 @@ export default function AuthPageBrandingForm({
|
||||
<Button
|
||||
type="submit"
|
||||
form="auth-page-branding-form"
|
||||
loading={isUpdatingBranding || isDeletingBranding}
|
||||
loading={isUpdatingBranding}
|
||||
disabled={
|
||||
isUpdatingBranding ||
|
||||
isDeletingBranding ||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
InfoSections,
|
||||
InfoSectionTitle
|
||||
} from "@app/components/InfoSection";
|
||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
type ClientInfoCardProps = {};
|
||||
@@ -16,6 +17,12 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
||||
const { client, updateClient } = useClientContext();
|
||||
const t = useTranslations();
|
||||
|
||||
const userDisplayName = getUserDisplayName({
|
||||
email: client.userEmail,
|
||||
name: client.userName,
|
||||
username: client.userUsername
|
||||
});
|
||||
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
@@ -25,8 +32,12 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
||||
<InfoSectionContent>{client.name}</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
|
||||
<InfoSectionContent>{client.niceId}</InfoSectionContent>
|
||||
<InfoSectionTitle>
|
||||
{userDisplayName ? t("user") : t("identifier")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{userDisplayName || client.niceId}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("status")}</InfoSectionTitle>
|
||||
|
||||
@@ -1,41 +1,13 @@
|
||||
"use client";
|
||||
import * as React from "react";
|
||||
import * as NProgress from "nprogress";
|
||||
|
||||
import NextTopLoader from "nextjs-toploader";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
export function TopLoader() {
|
||||
return (
|
||||
<>
|
||||
<NextTopLoader showSpinner={false} color="var(--color-primary)" />
|
||||
<FinishingLoader />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FinishingLoader() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
React.useEffect(() => {
|
||||
NProgress.done();
|
||||
}, [pathname, router, searchParams]);
|
||||
React.useEffect(() => {
|
||||
const linkClickListener = (ev: MouseEvent) => {
|
||||
const element = ev.target as HTMLElement;
|
||||
const closestlink = element.closest("a");
|
||||
const isOpenToNewTabClick =
|
||||
ev.ctrlKey ||
|
||||
ev.shiftKey ||
|
||||
ev.metaKey || // apple
|
||||
(ev.button && ev.button == 1); // middle click, >IE9 + everyone else
|
||||
|
||||
if (closestlink && isOpenToNewTabClick) {
|
||||
NProgress.done();
|
||||
}
|
||||
};
|
||||
window.addEventListener("click", linkClickListener);
|
||||
return () => window.removeEventListener("click", linkClickListener);
|
||||
}, []);
|
||||
return null;
|
||||
return (
|
||||
<NextTopLoader
|
||||
color="var(--color-primary)"
|
||||
showSpinner={false}
|
||||
height={2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
TableRow
|
||||
} from "@app/components/ui/table";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@app/components/ui/tabs";
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { Loader2, RefreshCw } from "lucide-react";
|
||||
import moment from "moment";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
@@ -59,8 +58,6 @@ export default function ViewDevicesDialog({
|
||||
|
||||
const [devices, setDevices] = useState<Device[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isArchiveModalOpen, setIsArchiveModalOpen] = useState(false);
|
||||
const [selectedDevice, setSelectedDevice] = useState<Device | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<"available" | "archived">("available");
|
||||
|
||||
const fetchDevices = async () => {
|
||||
@@ -108,8 +105,6 @@ export default function ViewDevicesDialog({
|
||||
d.olmId === olmId ? { ...d, archived: true } : d
|
||||
)
|
||||
);
|
||||
setIsArchiveModalOpen(false);
|
||||
setSelectedDevice(null);
|
||||
} catch (error: any) {
|
||||
console.error("Error archiving device:", error);
|
||||
toast({
|
||||
@@ -153,8 +148,6 @@ export default function ViewDevicesDialog({
|
||||
|
||||
function reset() {
|
||||
setDevices([]);
|
||||
setSelectedDevice(null);
|
||||
setIsArchiveModalOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -263,12 +256,7 @@ export default function ViewDevicesDialog({
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setSelectedDevice(
|
||||
device
|
||||
);
|
||||
setIsArchiveModalOpen(
|
||||
true
|
||||
);
|
||||
archiveDevice(device.olmId);
|
||||
}}
|
||||
>
|
||||
{t(
|
||||
@@ -361,34 +349,6 @@ export default function ViewDevicesDialog({
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
|
||||
{selectedDevice && (
|
||||
<ConfirmDeleteDialog
|
||||
open={isArchiveModalOpen}
|
||||
setOpen={(val) => {
|
||||
setIsArchiveModalOpen(val);
|
||||
if (!val) {
|
||||
setSelectedDevice(null);
|
||||
}
|
||||
}}
|
||||
dialog={
|
||||
<div className="space-y-2">
|
||||
<p>
|
||||
{t("deviceQuestionArchive") ||
|
||||
"Are you sure you want to archive this device?"}
|
||||
</p>
|
||||
<p>
|
||||
{t("deviceMessageArchive") ||
|
||||
"The device will be archived and removed from your active devices list."}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
buttonText={t("deviceArchiveConfirm") || "Archive Device"}
|
||||
onConfirm={async () => archiveDevice(selectedDevice.olmId)}
|
||||
string={selectedDevice.name || selectedDevice.olmId}
|
||||
title={t("archiveDevice") || "Archive Device"}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export function NewtSiteInstallCommands({
|
||||
- NEWT_SECRET=${secret}${acceptClientsEnv}`
|
||||
],
|
||||
"Docker Run": [
|
||||
`docker run -dit fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||
`docker run -dit --network host fosrl/newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}`
|
||||
]
|
||||
},
|
||||
kubernetes: {
|
||||
|
||||
Reference in New Issue
Block a user