mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-02 18:50:39 +02:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a0a093ed0b | |||
| 9cec711427 |
@@ -14,13 +14,12 @@ body:
|
|||||||
label: Environment
|
label: Environment
|
||||||
description: Please fill out the relevant details below for your environment.
|
description: Please fill out the relevant details below for your environment.
|
||||||
value: |
|
value: |
|
||||||
- OS Type & Version:
|
- OS Type & Version: (e.g., Ubuntu 22.04)
|
||||||
- Pangolin Version:
|
- Pangolin Version:
|
||||||
- Edition (Community or Enterprise):
|
|
||||||
- Gerbil Version:
|
- Gerbil Version:
|
||||||
- Traefik Version:
|
- Traefik Version:
|
||||||
- Newt Version:
|
- Newt Version:
|
||||||
- Client Version:
|
- Olm Version: (if applicable)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { APP_PATH } from "./server/lib/consts";
|
import { APP_PATH } from "@server/lib/consts";
|
||||||
import { defineConfig } from "drizzle-kit";
|
import { defineConfig } from "drizzle-kit";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@ go 1.25.0
|
|||||||
require (
|
require (
|
||||||
github.com/charmbracelet/huh v1.0.0
|
github.com/charmbracelet/huh v1.0.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
golang.org/x/term v0.43.0
|
golang.org/x/term v0.42.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +33,6 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.44.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
|||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
@@ -255,23 +255,6 @@
|
|||||||
"resourceGoTo": "Go to Resource",
|
"resourceGoTo": "Go to Resource",
|
||||||
"resourceDelete": "Delete Resource",
|
"resourceDelete": "Delete Resource",
|
||||||
"resourceDeleteConfirm": "Confirm Delete Resource",
|
"resourceDeleteConfirm": "Confirm Delete Resource",
|
||||||
"labelDelete": "Delete Label",
|
|
||||||
"labelAdd": "Add Label",
|
|
||||||
"labelCreateSuccessMessage": "Label Created Successfully",
|
|
||||||
"labelEditSuccessMessage": "Label Modified Successfully",
|
|
||||||
"labelNameField": "Label Name",
|
|
||||||
"labelColorField": "Label Color",
|
|
||||||
"labelPlaceholder": "Ex: homelab",
|
|
||||||
"labelCreate": "Create Label",
|
|
||||||
"createLabelDialogTitle": "Create Label",
|
|
||||||
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
|
|
||||||
"labelEdit": "Edit Label",
|
|
||||||
"editLabelDialogTitle": "Update Label",
|
|
||||||
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
|
|
||||||
"labelDeleteConfirm": "Confirm Delete Label",
|
|
||||||
"labelErrorDelete": "Failed to delete label",
|
|
||||||
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
|
|
||||||
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
|
|
||||||
"visibility": "Visibility",
|
"visibility": "Visibility",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
@@ -1157,18 +1140,6 @@
|
|||||||
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
|
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
|
||||||
"idpErrorNotFound": "IdP not found",
|
"idpErrorNotFound": "IdP not found",
|
||||||
"inviteInvalid": "Invalid Invite",
|
"inviteInvalid": "Invalid Invite",
|
||||||
"labels": "Labels",
|
|
||||||
"orgLabelsDescription": "Manage labels in this organization.",
|
|
||||||
"addLabels": "Add labels",
|
|
||||||
"siteLabelsTab": "Labels",
|
|
||||||
"siteLabelsDescription": "Manage labels associated with this site.",
|
|
||||||
"labelsNotFound": "Labels not found",
|
|
||||||
"labelSearch": "Search labels",
|
|
||||||
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
|
|
||||||
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
|
|
||||||
"accessLabelFilterClear": "Clear label filters",
|
|
||||||
"selectColor": "Select color",
|
|
||||||
"createNewLabel": "Create new org label \"{label}\"",
|
|
||||||
"inviteInvalidDescription": "The invite link is invalid.",
|
"inviteInvalidDescription": "The invite link is invalid.",
|
||||||
"inviteErrorWrongUser": "Invite is not for this user",
|
"inviteErrorWrongUser": "Invite is not for this user",
|
||||||
"inviteErrorUserNotExists": "User does not exist. Please create an account first.",
|
"inviteErrorUserNotExists": "User does not exist. Please create an account first.",
|
||||||
@@ -1354,29 +1325,6 @@
|
|||||||
"otpAuthBack": "Back to Password",
|
"otpAuthBack": "Back to Password",
|
||||||
"navbar": "Navigation Menu",
|
"navbar": "Navigation Menu",
|
||||||
"navbarDescription": "Main navigation menu for the application",
|
"navbarDescription": "Main navigation menu for the application",
|
||||||
"commandPaletteTitle": "Command palette",
|
|
||||||
"commandPaletteDescription": "Search for pages, organizations, resources, and actions",
|
|
||||||
"commandPaletteSearchPlaceholder": "Search pages, resources, actions...",
|
|
||||||
"commandPaletteNoResults": "No results found.",
|
|
||||||
"commandPaletteSearching": "Searching...",
|
|
||||||
"commandPaletteNavigation": "Navigation",
|
|
||||||
"commandPaletteOrganizations": "Organizations",
|
|
||||||
"commandPaletteSites": "Sites",
|
|
||||||
"commandPaletteResources": "Resources",
|
|
||||||
"commandPaletteUsers": "Users",
|
|
||||||
"commandPaletteClients": "Machine clients",
|
|
||||||
"commandPaletteActions": "Actions",
|
|
||||||
"commandPaletteCreateSite": "Create site",
|
|
||||||
"commandPaletteCreateProxyResource": "Create public resource",
|
|
||||||
"commandPaletteCreateUser": "Create user",
|
|
||||||
"commandPaletteCreateApiKey": "Create API key",
|
|
||||||
"commandPaletteCreateMachineClient": "Create machine client",
|
|
||||||
"commandPaletteCreateAlertRule": "Create alert rule",
|
|
||||||
"commandPaletteCreateIdentityProvider": "Create identity provider",
|
|
||||||
"commandPaletteToggleTheme": "Toggle theme",
|
|
||||||
"commandPaletteChooseOrganization": "Choose organization",
|
|
||||||
"commandPaletteShortcutMac": "⌘K",
|
|
||||||
"commandPaletteShortcutWindows": "Ctrl K",
|
|
||||||
"navbarDocsLink": "Documentation",
|
"navbarDocsLink": "Documentation",
|
||||||
"otpErrorEnable": "Unable to enable 2FA",
|
"otpErrorEnable": "Unable to enable 2FA",
|
||||||
"otpErrorEnableDescription": "An error occurred while enabling 2FA",
|
"otpErrorEnableDescription": "An error occurred while enabling 2FA",
|
||||||
@@ -1672,7 +1620,6 @@
|
|||||||
"certificateStatus": "Certificate",
|
"certificateStatus": "Certificate",
|
||||||
"certificateStatusAutoRefreshHint": "Status refreshes automatically.",
|
"certificateStatusAutoRefreshHint": "Status refreshes automatically.",
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"loadingEllipsis": "Loading...",
|
|
||||||
"loadingAnalytics": "Loading Analytics",
|
"loadingAnalytics": "Loading Analytics",
|
||||||
"restart": "Restart",
|
"restart": "Restart",
|
||||||
"domains": "Domains",
|
"domains": "Domains",
|
||||||
|
|||||||
+6
-1
@@ -5,7 +5,12 @@ const withNextIntl = createNextIntlPlugin();
|
|||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
reactStrictMode: false,
|
reactStrictMode: false,
|
||||||
reactCompiler: true,
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
reactCompiler: true
|
||||||
|
},
|
||||||
output: "standalone"
|
output: "standalone"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Generated
+3407
-2197
File diff suppressed because it is too large
Load Diff
+50
-52
@@ -32,10 +32,10 @@
|
|||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "8.5.0",
|
"@asteasolutions/zod-to-openapi": "8.4.1",
|
||||||
"@aws-sdk/client-s3": "3.1047.0",
|
"@aws-sdk/client-s3": "3.1011.0",
|
||||||
"@faker-js/faker": "10.4.0",
|
"@faker-js/faker": "10.3.0",
|
||||||
"@headlessui/react": "2.2.10",
|
"@headlessui/react": "2.2.9",
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
"@node-rs/argon2": "2.0.2",
|
"@node-rs/argon2": "2.0.2",
|
||||||
@@ -59,17 +59,16 @@
|
|||||||
"@radix-ui/react-tabs": "1.1.13",
|
"@radix-ui/react-tabs": "1.1.13",
|
||||||
"@radix-ui/react-toast": "1.2.15",
|
"@radix-ui/react-toast": "1.2.15",
|
||||||
"@radix-ui/react-tooltip": "1.2.8",
|
"@radix-ui/react-tooltip": "1.2.8",
|
||||||
"@react-email/body": "0.3.0",
|
"@react-email/components": "1.0.8",
|
||||||
"@react-email/components": "1.0.12",
|
"@react-email/render": "2.0.4",
|
||||||
"@react-email/render": "2.0.8",
|
"@react-email/tailwind": "2.0.5",
|
||||||
"@react-email/tailwind": "2.0.7",
|
|
||||||
"@simplewebauthn/browser": "13.3.0",
|
"@simplewebauthn/browser": "13.3.0",
|
||||||
"@simplewebauthn/server": "13.3.0",
|
"@simplewebauthn/server": "13.3.0",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.100.10",
|
"@tanstack/react-query": "5.90.21",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"arctic": "3.7.0",
|
"arctic": "3.7.0",
|
||||||
"axios": "1.16.1",
|
"axios": "1.15.0",
|
||||||
"better-sqlite3": "11.9.1",
|
"better-sqlite3": "11.9.1",
|
||||||
"canvas-confetti": "1.9.4",
|
"canvas-confetti": "1.9.4",
|
||||||
"class-variance-authority": "0.7.1",
|
"class-variance-authority": "0.7.1",
|
||||||
@@ -81,76 +80,76 @@
|
|||||||
"d3": "7.9.0",
|
"d3": "7.9.0",
|
||||||
"drizzle-orm": "0.45.2",
|
"drizzle-orm": "0.45.2",
|
||||||
"express": "5.2.1",
|
"express": "5.2.1",
|
||||||
"express-rate-limit": "8.5.2",
|
"express-rate-limit": "8.3.0",
|
||||||
"glob": "13.0.6",
|
"glob": "13.0.6",
|
||||||
"helmet": "8.1.0",
|
"helmet": "8.1.0",
|
||||||
"http-errors": "2.0.1",
|
"http-errors": "2.0.1",
|
||||||
"input-otp": "1.4.2",
|
"input-otp": "1.4.2",
|
||||||
"ioredis": "5.10.1",
|
"ioredis": "5.10.0",
|
||||||
"jmespath": "0.16.0",
|
"jmespath": "0.16.0",
|
||||||
"js-yaml": "4.1.1",
|
"js-yaml": "4.1.1",
|
||||||
"jsonwebtoken": "9.0.3",
|
"jsonwebtoken": "9.0.3",
|
||||||
"lucide-react": "0.577.0",
|
"lucide-react": "0.577.0",
|
||||||
"maxmind": "5.0.6",
|
"maxmind": "5.0.5",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "16.2.6",
|
"next": "15.5.15",
|
||||||
"next-intl": "4.12.0",
|
"next-intl": "4.8.3",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "3.9.17",
|
"nextjs-toploader": "3.9.17",
|
||||||
"node-cache": "5.1.2",
|
"node-cache": "5.1.2",
|
||||||
"nodemailer": "8.0.7",
|
"nodemailer": "8.0.5",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "8.20.0",
|
"pg": "8.20.0",
|
||||||
"posthog-node": "5.34.1",
|
"posthog-node": "5.28.0",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.6",
|
"react": "19.2.4",
|
||||||
"react-day-picker": "9.14.0",
|
"react-day-picker": "9.14.0",
|
||||||
"react-dom": "19.2.6",
|
"react-dom": "19.2.4",
|
||||||
"react-easy-sort": "1.8.0",
|
"react-easy-sort": "1.8.0",
|
||||||
"react-hook-form": "7.75.0",
|
"react-hook-form": "7.71.2",
|
||||||
"react-icons": "5.6.0",
|
"react-icons": "5.6.0",
|
||||||
"recharts": "3.8.1",
|
"recharts": "2.15.4",
|
||||||
"reodotdev": "1.1.0",
|
"reodotdev": "1.1.0",
|
||||||
"resend": "6.12.3",
|
"resend": "6.9.2",
|
||||||
"semver": "7.8.0",
|
"semver": "7.7.4",
|
||||||
"sshpk": "1.18.0",
|
"sshpk": "1.18.0",
|
||||||
"stripe": "20.4.1",
|
"stripe": "20.4.1",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"tailwind-merge": "3.6.0",
|
"tailwind-merge": "3.5.0",
|
||||||
"topojson-client": "3.1.0",
|
"topojson-client": "3.1.0",
|
||||||
"tw-animate-css": "1.4.0",
|
"tw-animate-css": "1.4.0",
|
||||||
"use-debounce": "10.1.1",
|
"use-debounce": "10.1.0",
|
||||||
"uuid": "14.0.0",
|
"uuid": "13.0.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"visionscarto-world-atlas": "1.0.0",
|
"visionscarto-world-atlas": "1.0.0",
|
||||||
"winston": "3.19.0",
|
"winston": "3.19.0",
|
||||||
"winston-daily-rotate-file": "5.0.0",
|
"winston-daily-rotate-file": "5.0.0",
|
||||||
"ws": "8.20.1",
|
"ws": "8.19.0",
|
||||||
"yaml": "2.9.0",
|
"yaml": "2.8.3",
|
||||||
"yargs": "18.0.0",
|
"yargs": "18.0.0",
|
||||||
"zod": "4.4.3",
|
"zod": "4.3.6",
|
||||||
"zod-validation-error": "5.0.0"
|
"zod-validation-error": "5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "1.66.0",
|
"@dotenvx/dotenvx": "1.54.1",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@react-email/ui": "^6.1.4",
|
"@react-email/preview-server": "5.2.10",
|
||||||
"@tailwindcss/postcss": "4.3.0",
|
"@tailwindcss/postcss": "4.2.2",
|
||||||
"@tanstack/react-query-devtools": "5.100.10",
|
"@tanstack/react-query-devtools": "5.91.3",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
"@types/cookie-parser": "1.4.10",
|
"@types/cookie-parser": "1.4.10",
|
||||||
"@types/cors": "2.8.19",
|
"@types/cors": "2.8.19",
|
||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/d3": "7.4.3",
|
"@types/d3": "7.4.3",
|
||||||
"@types/express": "5.0.6",
|
"@types/express": "5.0.6",
|
||||||
"@types/express-session": "1.19.0",
|
"@types/express-session": "1.18.2",
|
||||||
"@types/jmespath": "0.15.2",
|
"@types/jmespath": "0.15.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsonwebtoken": "9.0.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "25.8.0",
|
"@types/node": "25.3.5",
|
||||||
"@types/nodemailer": "8.0.0",
|
"@types/nodemailer": "7.0.11",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/pg": "8.20.0",
|
"@types/pg": "8.18.0",
|
||||||
"@types/react": "19.2.14",
|
"@types/react": "19.2.14",
|
||||||
"@types/react-dom": "19.2.3",
|
"@types/react-dom": "19.2.3",
|
||||||
"@types/semver": "7.7.1",
|
"@types/semver": "7.7.1",
|
||||||
@@ -161,22 +160,21 @@
|
|||||||
"@types/yargs": "17.0.35",
|
"@types/yargs": "17.0.35",
|
||||||
"babel-plugin-react-compiler": "1.0.0",
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.27.4",
|
||||||
"esbuild-node-externals": "1.22.0",
|
"esbuild-node-externals": "1.20.1",
|
||||||
"eslint": "10.3.0",
|
"eslint": "10.0.3",
|
||||||
"eslint-config-next": "16.2.6",
|
"eslint-config-next": "16.1.7",
|
||||||
"postcss": "8.5.14",
|
"postcss": "8.5.8",
|
||||||
"prettier": "3.8.3",
|
"prettier": "3.8.1",
|
||||||
"react-email": "6.1.4",
|
"react-email": "5.2.10",
|
||||||
"tailwindcss": "4.3.0",
|
"tailwindcss": "4.2.2",
|
||||||
"tsc-alias": "1.8.17",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.22.0",
|
"tsx": "4.21.0",
|
||||||
"typescript": "6.0.3",
|
"typescript": "5.9.3",
|
||||||
"typescript-eslint": "8.59.3"
|
"typescript-eslint": "8.56.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.27.4",
|
||||||
"dompurify": "3.4.0",
|
"dompurify": "3.3.2"
|
||||||
"postcss": "8.5.14"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,12 +148,6 @@ export enum ActionsEnum {
|
|||||||
updateAlertRule = "updateAlertRule",
|
updateAlertRule = "updateAlertRule",
|
||||||
deleteAlertRule = "deleteAlertRule",
|
deleteAlertRule = "deleteAlertRule",
|
||||||
listAlertRules = "listAlertRules",
|
listAlertRules = "listAlertRules",
|
||||||
listOrgLabels = "listOrgLabels",
|
|
||||||
createOrgLabel = "createOrgLabel",
|
|
||||||
updateOrgLabel = "updateOrgLabel",
|
|
||||||
deleteOrgLabel = "deleteOrgLabel",
|
|
||||||
attachLabelToItem = "attachLabelToItem",
|
|
||||||
detachLabelFromItem = "detachLabelFromItem",
|
|
||||||
getAlertRule = "getAlertRule",
|
getAlertRule = "getAlertRule",
|
||||||
createHealthCheck = "createHealthCheck",
|
createHealthCheck = "createHealthCheck",
|
||||||
updateHealthCheck = "updateHealthCheck",
|
updateHealthCheck = "updateHealthCheck",
|
||||||
|
|||||||
+16
-113
@@ -162,89 +162,6 @@ export const resources = pgTable("resources", {
|
|||||||
wildcard: boolean("wildcard").notNull().default(false)
|
wildcard: boolean("wildcard").notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const labels = pgTable("labels", {
|
|
||||||
labelId: serial("labelId").primaryKey(),
|
|
||||||
name: varchar("name").notNull(),
|
|
||||||
color: varchar("color").notNull(),
|
|
||||||
orgId: varchar("orgId")
|
|
||||||
.references(() => orgs.orgId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const siteLabels = pgTable(
|
|
||||||
"siteLabels",
|
|
||||||
{
|
|
||||||
siteLabelId: serial("siteLabelId").primaryKey(),
|
|
||||||
siteId: integer("siteId")
|
|
||||||
.references(() => sites.siteId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const resourceLabels = pgTable(
|
|
||||||
"resourceLabels",
|
|
||||||
{
|
|
||||||
resourceLabelId: serial("resourceLabelId").primaryKey(),
|
|
||||||
resourceId: integer("resourceId")
|
|
||||||
.references(() => resources.resourceId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const siteResourceLabels = pgTable(
|
|
||||||
"siteResourceLabels",
|
|
||||||
{
|
|
||||||
siteResourceLabelId: serial("siteResourceLabelId").primaryKey(),
|
|
||||||
siteResourceId: integer("siteResourceId")
|
|
||||||
.references(() => siteResources.siteResourceId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const clientLabels = pgTable(
|
|
||||||
"clientLabels",
|
|
||||||
{
|
|
||||||
clientLabelId: serial("clientLabelId").primaryKey(),
|
|
||||||
clientId: integer("clientId")
|
|
||||||
.references(() => clients.clientId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const targets = pgTable("targets", {
|
export const targets = pgTable("targets", {
|
||||||
targetId: serial("targetId").primaryKey(),
|
targetId: serial("targetId").primaryKey(),
|
||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
@@ -279,11 +196,9 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
siteId: integer("siteId")
|
siteId: integer("siteId").references(() => sites.siteId, {
|
||||||
.references(() => sites.siteId, {
|
onDelete: "cascade"
|
||||||
onDelete: "cascade"
|
}).notNull(),
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
name: varchar("name"),
|
name: varchar("name"),
|
||||||
hcEnabled: boolean("hcEnabled").notNull().default(false),
|
hcEnabled: boolean("hcEnabled").notNull().default(false),
|
||||||
hcPath: varchar("hcPath"),
|
hcPath: varchar("hcPath"),
|
||||||
@@ -1182,30 +1097,19 @@ export const roundTripMessageTracker = pgTable("roundTripMessageTracker", {
|
|||||||
complete: boolean("complete").notNull().default(false)
|
complete: boolean("complete").notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const statusHistory = pgTable(
|
export const statusHistory = pgTable("statusHistory", {
|
||||||
"statusHistory",
|
id: serial("id").primaryKey(),
|
||||||
{
|
entityType: varchar("entityType").notNull(),
|
||||||
id: serial("id").primaryKey(),
|
entityId: integer("entityId").notNull(),
|
||||||
entityType: varchar("entityType").notNull(),
|
orgId: varchar("orgId")
|
||||||
entityId: integer("entityId").notNull(),
|
.notNull()
|
||||||
orgId: varchar("orgId")
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
.notNull()
|
status: varchar("status").notNull(),
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
timestamp: integer("timestamp").notNull(),
|
||||||
status: varchar("status").notNull(),
|
}, (table) => [
|
||||||
timestamp: integer("timestamp").notNull()
|
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
||||||
},
|
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
||||||
(table) => [
|
]);
|
||||||
index("idx_statusHistory_entity").on(
|
|
||||||
table.entityType,
|
|
||||||
table.entityId,
|
|
||||||
table.timestamp
|
|
||||||
),
|
|
||||||
index("idx_statusHistory_org_timestamp").on(
|
|
||||||
table.orgId,
|
|
||||||
table.timestamp
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
export type Org = InferSelectModel<typeof orgs>;
|
export type Org = InferSelectModel<typeof orgs>;
|
||||||
export type User = InferSelectModel<typeof users>;
|
export type User = InferSelectModel<typeof users>;
|
||||||
@@ -1275,4 +1179,3 @@ export type RoundTripMessageTracker = InferSelectModel<
|
|||||||
>;
|
>;
|
||||||
export type Network = InferSelectModel<typeof networks>;
|
export type Network = InferSelectModel<typeof networks>;
|
||||||
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
||||||
export type Label = InferSelectModel<typeof labels>;
|
|
||||||
|
|||||||
@@ -183,95 +183,6 @@ export const resources = sqliteTable("resources", {
|
|||||||
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false)
|
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const labels = sqliteTable("labels", {
|
|
||||||
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
color: text("color").notNull(),
|
|
||||||
orgId: text("orgId")
|
|
||||||
.references(() => orgs.orgId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const siteLabels = sqliteTable(
|
|
||||||
"siteLabels",
|
|
||||||
{
|
|
||||||
siteLabelId: integer("siteLabelId").primaryKey({ autoIncrement: true }),
|
|
||||||
siteId: integer("siteId")
|
|
||||||
.references(() => sites.siteId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const resourceLabels = sqliteTable(
|
|
||||||
"resourceLabels",
|
|
||||||
{
|
|
||||||
resourceLabelId: integer("resourceLabelId").primaryKey({
|
|
||||||
autoIncrement: true
|
|
||||||
}),
|
|
||||||
resourceId: integer("resourceId")
|
|
||||||
.references(() => resources.resourceId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const siteResourceLabels = sqliteTable(
|
|
||||||
"siteResourceLabels",
|
|
||||||
{
|
|
||||||
siteResourceLabelId: integer("siteResourceLabelId").primaryKey({
|
|
||||||
autoIncrement: true
|
|
||||||
}),
|
|
||||||
siteResourceId: integer("siteResourceId")
|
|
||||||
.references(() => siteResources.siteResourceId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const clientLabels = sqliteTable(
|
|
||||||
"clientLabels",
|
|
||||||
{
|
|
||||||
clientLabelId: integer("clientLabelId").primaryKey({
|
|
||||||
autoIncrement: true
|
|
||||||
}),
|
|
||||||
clientId: integer("clientId")
|
|
||||||
.references(() => clients.clientId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
labelId: integer("labelId")
|
|
||||||
.references(() => labels.labelId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
},
|
|
||||||
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
|
|
||||||
);
|
|
||||||
|
|
||||||
export const targets = sqliteTable("targets", {
|
export const targets = sqliteTable("targets", {
|
||||||
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
|
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
|
||||||
resourceId: integer("resourceId")
|
resourceId: integer("resourceId")
|
||||||
@@ -308,11 +219,9 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
siteId: integer("siteId")
|
siteId: integer("siteId").references(() => sites.siteId, {
|
||||||
.references(() => sites.siteId, {
|
onDelete: "cascade"
|
||||||
onDelete: "cascade"
|
}).notNull(),
|
||||||
})
|
|
||||||
.notNull(),
|
|
||||||
name: text("name"),
|
name: text("name"),
|
||||||
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -1287,30 +1196,19 @@ export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
|
|||||||
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
|
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const statusHistory = sqliteTable(
|
export const statusHistory = sqliteTable("statusHistory", {
|
||||||
"statusHistory",
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
{
|
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
||||||
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
orgId: text("orgId")
|
||||||
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
.notNull()
|
||||||
orgId: text("orgId")
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
.notNull()
|
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
timestamp: integer("timestamp").notNull(), // unix epoch seconds
|
||||||
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
}, (table) => [
|
||||||
timestamp: integer("timestamp").notNull() // unix epoch seconds
|
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
||||||
},
|
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
||||||
(table) => [
|
]);
|
||||||
index("idx_statusHistory_entity").on(
|
|
||||||
table.entityType,
|
|
||||||
table.entityId,
|
|
||||||
table.timestamp
|
|
||||||
),
|
|
||||||
index("idx_statusHistory_org_timestamp").on(
|
|
||||||
table.orgId,
|
|
||||||
table.timestamp
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
export type Org = InferSelectModel<typeof orgs>;
|
export type Org = InferSelectModel<typeof orgs>;
|
||||||
export type User = InferSelectModel<typeof users>;
|
export type User = InferSelectModel<typeof users>;
|
||||||
@@ -1380,4 +1278,3 @@ export type RoundTripMessageTracker = InferSelectModel<
|
|||||||
typeof roundTripMessageTracker
|
typeof roundTripMessageTracker
|
||||||
>;
|
>;
|
||||||
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
export type StatusHistory = InferSelectModel<typeof statusHistory>;
|
||||||
export type Label = InferSelectModel<typeof labels>;
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
#! /usr/bin/env node
|
#! /usr/bin/env node
|
||||||
import "./extendZod";
|
import "./extendZod.ts";
|
||||||
|
|
||||||
import { runSetupFunctions } from "./setup";
|
import { runSetupFunctions } from "./setup";
|
||||||
import { createApiServer } from "./apiServer";
|
import { createApiServer } from "./apiServer";
|
||||||
|
|||||||
@@ -152,11 +152,17 @@ function getOpenApiDocumentation() {
|
|||||||
|
|
||||||
if (!hasExistingResponses) {
|
if (!hasExistingResponses) {
|
||||||
def.route.responses = {
|
def.route.responses = {
|
||||||
"*": {
|
"200": {
|
||||||
description: "",
|
description: "Successful response",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: z.object({})
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ export enum TierFeature {
|
|||||||
DomainNamespaces = "domainNamespaces", // handle downgrade by removing custom domain namespaces
|
DomainNamespaces = "domainNamespaces", // handle downgrade by removing custom domain namespaces
|
||||||
StandaloneHealthChecks = "standaloneHealthChecks",
|
StandaloneHealthChecks = "standaloneHealthChecks",
|
||||||
AlertingRules = "alertingRules",
|
AlertingRules = "alertingRules",
|
||||||
WildcardSubdomain = "wildcardSubdomain",
|
WildcardSubdomain = "wildcardSubdomain"
|
||||||
Labels = "labels"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tierMatrix: Record<TierFeature, Tier[]> = {
|
export const tierMatrix: Record<TierFeature, Tier[]> = {
|
||||||
[TierFeature.Labels]: ["tier2", "tier3", "enterprise"],
|
|
||||||
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
|
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
|
||||||
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
|
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
|
||||||
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
|
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
|
||||||
|
|||||||
@@ -154,19 +154,8 @@ class AdaptiveCache {
|
|||||||
keys(): string[] {
|
keys(): string[] {
|
||||||
return localCache.keys();
|
return localCache.keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get keys with a specific prefix
|
|
||||||
* @param prefix - Key prefix to match
|
|
||||||
* @returns Array of matching keys
|
|
||||||
*/
|
|
||||||
async keysWithPrefix(prefix: string): Promise<string[]> {
|
|
||||||
const allKeys = localCache.keys();
|
|
||||||
return allKeys.filter((key) => key.startsWith(prefix));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export singleton instance
|
// Export singleton instance
|
||||||
export const cache = new AdaptiveCache();
|
export const cache = new AdaptiveCache();
|
||||||
export const regionalCache = cache; // Alias for compatability with the private version
|
|
||||||
export default cache;
|
export default cache;
|
||||||
|
|||||||
+7
-1
@@ -873,7 +873,13 @@ export const portRangeStringSchema = z
|
|||||||
message:
|
message:
|
||||||
'Port range must be "*" for all ports, or a comma-separated list of ports and ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535, and ranges must have start <= end.'
|
'Port range must be "*" for all ports, or a comma-separated list of ports and ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535, and ranges must have start <= end.'
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
.openapi({
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
'Port range string. Use "*" for all ports, a comma-separated list of ports, or ranges (e.g., "80,443,8000-9000"). Ports must be between 1 and 65535.',
|
||||||
|
example: "80,443,8000-9000"
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a port range string into an array of port range objects
|
* Parses a port range string into an array of port range objects
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
userOrgRoles,
|
userOrgRoles,
|
||||||
userSiteResources
|
userSiteResources
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { and, count, eq, inArray, ne } from "drizzle-orm";
|
import { and, eq, inArray, ne } from "drizzle-orm";
|
||||||
|
|
||||||
import { deletePeer as newtDeletePeer } from "@server/routers/newt/peers";
|
import { deletePeer as newtDeletePeer } from "@server/routers/newt/peers";
|
||||||
import {
|
import {
|
||||||
@@ -39,11 +39,6 @@ import {
|
|||||||
removePeerData,
|
removePeerData,
|
||||||
removeTargets as removeSubnetProxyTargets
|
removeTargets as removeSubnetProxyTargets
|
||||||
} from "@server/routers/client/targets";
|
} from "@server/routers/client/targets";
|
||||||
import { lockManager } from "#dynamic/lib/lock";
|
|
||||||
|
|
||||||
// TTL for rebuild-association locks. These functions can fan out into many
|
|
||||||
// peer/proxy updates, so give them a generous window.
|
|
||||||
const REBUILD_ASSOCIATIONS_LOCK_TTL_MS = 120000;
|
|
||||||
|
|
||||||
export async function getClientSiteResourceAccess(
|
export async function getClientSiteResourceAccess(
|
||||||
siteResource: SiteResource,
|
siteResource: SiteResource,
|
||||||
@@ -166,23 +161,6 @@ export async function rebuildClientAssociationsFromSiteResource(
|
|||||||
pubKey: string | null;
|
pubKey: string | null;
|
||||||
subnet: string | null;
|
subnet: string | null;
|
||||||
}[];
|
}[];
|
||||||
}> {
|
|
||||||
return await lockManager.withLock(
|
|
||||||
`rebuild-client-associations:site-resource:${siteResource.siteResourceId}`,
|
|
||||||
() => rebuildClientAssociationsFromSiteResourceImpl(siteResource, trx),
|
|
||||||
REBUILD_ASSOCIATIONS_LOCK_TTL_MS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function rebuildClientAssociationsFromSiteResourceImpl(
|
|
||||||
siteResource: SiteResource,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<{
|
|
||||||
mergedAllClients: {
|
|
||||||
clientId: number;
|
|
||||||
pubKey: string | null;
|
|
||||||
subnet: string | null;
|
|
||||||
}[];
|
|
||||||
}> {
|
}> {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`rebuildClientAssociations: [rebuildClientAssociationsFromSiteResource] START siteResourceId=${siteResource.siteResourceId} networkId=${siteResource.networkId} orgId=${siteResource.orgId}`
|
`rebuildClientAssociations: [rebuildClientAssociationsFromSiteResource] START siteResourceId=${siteResource.siteResourceId} networkId=${siteResource.networkId} orgId=${siteResource.orgId}`
|
||||||
@@ -561,29 +539,6 @@ async function handleMessagesForSiteClients(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the number of sites on each of these clients so we can log it and make decisions about whether to send messages based on it
|
|
||||||
const clientSiteCounts: Record<number, number> = {};
|
|
||||||
if (clientsToProcess.size > 0) {
|
|
||||||
const clientIdsToProcess = Array.from(clientsToProcess.keys());
|
|
||||||
const siteCounts = await trx
|
|
||||||
.select({
|
|
||||||
clientId: clientSitesAssociationsCache.clientId,
|
|
||||||
siteCount: count(clientSitesAssociationsCache.siteId)
|
|
||||||
})
|
|
||||||
.from(clientSitesAssociationsCache)
|
|
||||||
.where(
|
|
||||||
inArray(
|
|
||||||
clientSitesAssociationsCache.clientId,
|
|
||||||
clientIdsToProcess
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.groupBy(clientSitesAssociationsCache.clientId);
|
|
||||||
|
|
||||||
for (const row of siteCounts) {
|
|
||||||
clientSiteCounts[row.clientId] = Number(row.siteCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const client of clientsToProcess.values()) {
|
for (const client of clientsToProcess.values()) {
|
||||||
// UPDATE THE NEWT
|
// UPDATE THE NEWT
|
||||||
if (!client.subnet || !client.pubKey) {
|
if (!client.subnet || !client.pubKey) {
|
||||||
@@ -627,14 +582,7 @@ async function handleMessagesForSiteClients(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isAdd) {
|
if (isAdd) {
|
||||||
if (clientSiteCounts[client.clientId] > 250) {
|
// TODO: if we are in jit mode here should we really be sending this?
|
||||||
// skip adding the peer if we have more than 250 sites because we are in jit mode anyway
|
|
||||||
logger.info(
|
|
||||||
`rebuildClientAssociations: Client ${client.clientId} has ${clientSiteCounts[client.clientId]} sites so skipping adding peer to newt and olm because it is likely in jit mode`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await initPeerAddHandshake(
|
await initPeerAddHandshake(
|
||||||
// this will kick off the add peer process for the client
|
// this will kick off the add peer process for the client
|
||||||
client.clientId,
|
client.clientId,
|
||||||
@@ -652,24 +600,9 @@ async function handleMessagesForSiteClients(
|
|||||||
exitNodeJobs.push(updateClientSiteDestinations(client, trx));
|
exitNodeJobs.push(updateClientSiteDestinations(client, trx));
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(exitNodeJobs).catch((error) => {
|
await Promise.all(exitNodeJobs);
|
||||||
logger.error(
|
await Promise.all(newtJobs); // do the servers first to make sure they are ready?
|
||||||
`rebuildClientAssociations: Error updating client site destinations for site ${site.siteId}:`,
|
await Promise.all(olmJobs);
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Promise.all(newtJobs).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
`rebuildClientAssociations: Error updating Newt peers for site ${site.siteId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Promise.all(olmJobs).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
`rebuildClientAssociations: Error updating Olm peers for site ${site.siteId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PeerDestination {
|
interface PeerDestination {
|
||||||
@@ -952,17 +885,6 @@ async function handleSubnetProxyTargetUpdates(
|
|||||||
export async function rebuildClientAssociationsFromClient(
|
export async function rebuildClientAssociationsFromClient(
|
||||||
client: Client,
|
client: Client,
|
||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
|
||||||
return await lockManager.withLock(
|
|
||||||
`rebuild-client-associations:client:${client.clientId}`,
|
|
||||||
() => rebuildClientAssociationsFromClientImpl(client, trx),
|
|
||||||
REBUILD_ASSOCIATIONS_LOCK_TTL_MS
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function rebuildClientAssociationsFromClientImpl(
|
|
||||||
client: Client,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let newSiteResourceIds: number[] = [];
|
let newSiteResourceIds: number[] = [];
|
||||||
|
|
||||||
@@ -1235,12 +1157,6 @@ async function handleMessagesForClientSites(
|
|||||||
const olmJobs: Promise<any>[] = [];
|
const olmJobs: Promise<any>[] = [];
|
||||||
const exitNodeJobs: Promise<any>[] = [];
|
const exitNodeJobs: Promise<any>[] = [];
|
||||||
|
|
||||||
const totalSitesOnClient = await trx
|
|
||||||
.select({ count: count(clientSitesAssociationsCache.siteId) })
|
|
||||||
.from(clientSitesAssociationsCache)
|
|
||||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId))
|
|
||||||
.then((rows) => Number(rows[0].count));
|
|
||||||
|
|
||||||
for (const siteData of sitesData) {
|
for (const siteData of sitesData) {
|
||||||
const site = siteData.sites;
|
const site = siteData.sites;
|
||||||
const exitNode = siteData.exitNodes;
|
const exitNode = siteData.exitNodes;
|
||||||
@@ -1301,14 +1217,7 @@ async function handleMessagesForClientSites(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalSitesOnClient > 250) {
|
// TODO: if we are in jit mode here should we really be sending this?
|
||||||
// skip adding the site if we have more than 250 because we are in jit mode anyway
|
|
||||||
logger.info(
|
|
||||||
`rebuildClientAssociations: Client ${client.clientId} has ${totalSitesOnClient} sites so skipping adding peer to newt and olm because it is likely in jit mode`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await initPeerAddHandshake(
|
await initPeerAddHandshake(
|
||||||
// this will kick off the add peer process for the client
|
// this will kick off the add peer process for the client
|
||||||
client.clientId,
|
client.clientId,
|
||||||
@@ -1336,24 +1245,9 @@ async function handleMessagesForClientSites(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all(exitNodeJobs).catch((error) => {
|
await Promise.all(exitNodeJobs);
|
||||||
logger.error(
|
await Promise.all(newtJobs);
|
||||||
`rebuildClientAssociations: Error updating client site destinations for client ${client.clientId}:`,
|
await Promise.all(olmJobs);
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Promise.all(newtJobs).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
`rebuildClientAssociations: Error updating Newt peers for client ${client.clientId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Promise.all(olmJobs).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
`rebuildClientAssociations: Error updating Olm peers for client ${client.clientId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessagesForClientResources(
|
async function handleMessagesForClientResources(
|
||||||
@@ -1634,195 +1528,3 @@ async function handleMessagesForClientResources(
|
|||||||
|
|
||||||
await Promise.all([...proxyJobs, ...olmJobs]);
|
await Promise.all([...proxyJobs, ...olmJobs]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClientAssociationsCacheVerification = {
|
|
||||||
clientId: number;
|
|
||||||
consistent: boolean;
|
|
||||||
// What permissions say the cache should contain
|
|
||||||
expectedSiteResourceIds: number[];
|
|
||||||
expectedSiteIds: number[];
|
|
||||||
// What the cache currently contains
|
|
||||||
actualSiteResourceIds: number[];
|
|
||||||
actualSiteIds: number[];
|
|
||||||
// Diff
|
|
||||||
missingSiteResourceIds: number[]; // present in expected, missing from cache
|
|
||||||
extraSiteResourceIds: number[]; // present in cache, not in expected
|
|
||||||
missingSiteIds: number[];
|
|
||||||
extraSiteIds: number[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// verifyClientAssociationsCache walks the same permission-derivation logic as
|
|
||||||
// rebuildClientAssociationsFromClient but does NOT modify the database. It
|
|
||||||
// returns the expected vs actual cache contents and a boolean indicating
|
|
||||||
// whether the cache is in sync with what permissions imply.
|
|
||||||
export async function verifyClientAssociationsCache(
|
|
||||||
client: Client,
|
|
||||||
trx: Transaction | typeof db = db
|
|
||||||
): Promise<ClientAssociationsCacheVerification> {
|
|
||||||
let newSiteResourceIds: number[] = [];
|
|
||||||
|
|
||||||
// 1. Direct client associations
|
|
||||||
const directSiteResources = await trx
|
|
||||||
.select({ siteResourceId: clientSiteResources.siteResourceId })
|
|
||||||
.from(clientSiteResources)
|
|
||||||
.innerJoin(
|
|
||||||
siteResources,
|
|
||||||
eq(siteResources.siteResourceId, clientSiteResources.siteResourceId)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(clientSiteResources.clientId, client.clientId),
|
|
||||||
eq(siteResources.orgId, client.orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
newSiteResourceIds.push(
|
|
||||||
...directSiteResources.map((r) => r.siteResourceId)
|
|
||||||
);
|
|
||||||
|
|
||||||
// 2. User-based and role-based access (if client has a userId)
|
|
||||||
if (client.userId) {
|
|
||||||
const userSiteResourceIds = await trx
|
|
||||||
.select({ siteResourceId: userSiteResources.siteResourceId })
|
|
||||||
.from(userSiteResources)
|
|
||||||
.innerJoin(
|
|
||||||
siteResources,
|
|
||||||
eq(
|
|
||||||
siteResources.siteResourceId,
|
|
||||||
userSiteResources.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userSiteResources.userId, client.userId),
|
|
||||||
eq(siteResources.orgId, client.orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
newSiteResourceIds.push(
|
|
||||||
...userSiteResourceIds.map((r) => r.siteResourceId)
|
|
||||||
);
|
|
||||||
|
|
||||||
const roleIds = await trx
|
|
||||||
.select({ roleId: userOrgRoles.roleId })
|
|
||||||
.from(userOrgRoles)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userOrgRoles.userId, client.userId),
|
|
||||||
eq(userOrgRoles.orgId, client.orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then((rows) => rows.map((row) => row.roleId));
|
|
||||||
|
|
||||||
if (roleIds.length > 0) {
|
|
||||||
const roleSiteResourceIds = await trx
|
|
||||||
.select({ siteResourceId: roleSiteResources.siteResourceId })
|
|
||||||
.from(roleSiteResources)
|
|
||||||
.innerJoin(
|
|
||||||
siteResources,
|
|
||||||
eq(
|
|
||||||
siteResources.siteResourceId,
|
|
||||||
roleSiteResources.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(roleSiteResources.roleId, roleIds),
|
|
||||||
eq(siteResources.orgId, client.orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
newSiteResourceIds.push(
|
|
||||||
...roleSiteResourceIds.map((r) => r.siteResourceId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newSiteResourceIds = Array.from(new Set(newSiteResourceIds));
|
|
||||||
|
|
||||||
const newSiteResources =
|
|
||||||
newSiteResourceIds.length > 0
|
|
||||||
? await trx
|
|
||||||
.select()
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
inArray(siteResources.siteResourceId, newSiteResourceIds)
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const networkIds = Array.from(
|
|
||||||
new Set(
|
|
||||||
newSiteResources
|
|
||||||
.map((sr) => sr.networkId)
|
|
||||||
.filter((id): id is number => id !== null)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const newSiteIds =
|
|
||||||
networkIds.length > 0
|
|
||||||
? await trx
|
|
||||||
.select({ siteId: siteNetworks.siteId })
|
|
||||||
.from(siteNetworks)
|
|
||||||
.where(inArray(siteNetworks.networkId, networkIds))
|
|
||||||
.then((rows) =>
|
|
||||||
Array.from(new Set(rows.map((r) => r.siteId)))
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
// Read the existing cache state
|
|
||||||
const existingResourceAssociations = await trx
|
|
||||||
.select({
|
|
||||||
siteResourceId: clientSiteResourcesAssociationsCache.siteResourceId
|
|
||||||
})
|
|
||||||
.from(clientSiteResourcesAssociationsCache)
|
|
||||||
.where(
|
|
||||||
eq(clientSiteResourcesAssociationsCache.clientId, client.clientId)
|
|
||||||
);
|
|
||||||
const existingSiteResourceIds = existingResourceAssociations.map(
|
|
||||||
(r) => r.siteResourceId
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingSiteAssociations = await trx
|
|
||||||
.select({ siteId: clientSitesAssociationsCache.siteId })
|
|
||||||
.from(clientSitesAssociationsCache)
|
|
||||||
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
|
||||||
const existingSiteIds = existingSiteAssociations.map((s) => s.siteId);
|
|
||||||
|
|
||||||
const expectedSiteResourceSet = new Set(newSiteResourceIds);
|
|
||||||
const actualSiteResourceSet = new Set(existingSiteResourceIds);
|
|
||||||
const expectedSiteSet = new Set(newSiteIds);
|
|
||||||
const actualSiteSet = new Set(existingSiteIds);
|
|
||||||
|
|
||||||
const missingSiteResourceIds = newSiteResourceIds.filter(
|
|
||||||
(id) => !actualSiteResourceSet.has(id)
|
|
||||||
);
|
|
||||||
const extraSiteResourceIds = existingSiteResourceIds.filter(
|
|
||||||
(id) => !expectedSiteResourceSet.has(id)
|
|
||||||
);
|
|
||||||
const missingSiteIds = newSiteIds.filter((id) => !actualSiteSet.has(id));
|
|
||||||
const extraSiteIds = existingSiteIds.filter(
|
|
||||||
(id) => !expectedSiteSet.has(id)
|
|
||||||
);
|
|
||||||
|
|
||||||
const consistent =
|
|
||||||
missingSiteResourceIds.length === 0 &&
|
|
||||||
extraSiteResourceIds.length === 0 &&
|
|
||||||
missingSiteIds.length === 0 &&
|
|
||||||
extraSiteIds.length === 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
clientId: client.clientId,
|
|
||||||
consistent,
|
|
||||||
expectedSiteResourceIds: Array.from(expectedSiteResourceSet).sort(
|
|
||||||
(a, b) => a - b
|
|
||||||
),
|
|
||||||
expectedSiteIds: Array.from(expectedSiteSet).sort((a, b) => a - b),
|
|
||||||
actualSiteResourceIds: Array.from(actualSiteResourceSet).sort(
|
|
||||||
(a, b) => a - b
|
|
||||||
),
|
|
||||||
actualSiteIds: Array.from(actualSiteSet).sort((a, b) => a - b),
|
|
||||||
missingSiteResourceIds: missingSiteResourceIds.sort((a, b) => a - b),
|
|
||||||
extraSiteResourceIds: extraSiteResourceIds.sort((a, b) => a - b),
|
|
||||||
missingSiteIds: missingSiteIds.sort((a, b) => a - b),
|
|
||||||
extraSiteIds: extraSiteIds.sort((a, b) => a - b)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
export function getFirstString(value: unknown): string | undefined {
|
|
||||||
if (typeof value === "string") {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value) && typeof value[0] === "string") {
|
|
||||||
return value[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, logsDb, statusHistory } from "@server/db";
|
import { db, logsDb, statusHistory } from "@server/db";
|
||||||
import { and, eq, gte, asc } from "drizzle-orm";
|
import { and, eq, gte, asc } from "drizzle-orm";
|
||||||
import { regionalCache as cache } from "#dynamic/lib/cache";
|
import cache from "@server/lib/cache";
|
||||||
|
|
||||||
const STATUS_HISTORY_CACHE_TTL = 60; // seconds
|
const STATUS_HISTORY_CACHE_TTL = 60; // seconds
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ export async function invalidateStatusHistoryCache(
|
|||||||
entityId: number
|
entityId: number
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const prefix = `statusHistory:${entityType}:${entityId}:`;
|
const prefix = `statusHistory:${entityType}:${entityId}:`;
|
||||||
const keys = await cache.keysWithPrefix(prefix);
|
const keys = cache.keys().filter((k) => k.startsWith(prefix));
|
||||||
if (keys.length > 0) {
|
if (keys.length > 0) {
|
||||||
await cache.del(keys);
|
await cache.del(keys);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { resourceAccessToken, resources, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyAccessTokenAccess(
|
export async function verifyApiKeyAccessTokenAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,7 +12,7 @@ export async function verifyApiKeyAccessTokenAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const apiKey = req.apiKey;
|
const apiKey = req.apiKey;
|
||||||
const accessTokenId = getFirstString(req.params.accessTokenId);
|
const accessTokenId = req.params.accessTokenId;
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return next(
|
return next(
|
||||||
@@ -21,12 +20,6 @@ export async function verifyApiKeyAccessTokenAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessTokenId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid access token ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [accessToken] = await db
|
const [accessToken] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resourceAccessToken)
|
.from(resourceAccessToken)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { apiKeys, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq, or } from "drizzle-orm";
|
import { and, eq, or } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyApiKeyAccess(
|
export async function verifyApiKeyApiKeyAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -15,10 +14,8 @@ export async function verifyApiKeyApiKeyAccess(
|
|||||||
const { apiKey: callerApiKey } = req;
|
const { apiKey: callerApiKey } = req;
|
||||||
|
|
||||||
const apiKeyId =
|
const apiKeyId =
|
||||||
getFirstString(req.params.apiKeyId) ||
|
req.params.apiKeyId || req.body.apiKeyId || req.query.apiKeyId;
|
||||||
getFirstString(req.body.apiKeyId) ||
|
const orgId = req.params.orgId;
|
||||||
getFirstString(req.query.apiKeyId);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!callerApiKey) {
|
if (!callerApiKey) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { db, domains, orgDomains, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyDomainAccess(
|
export async function verifyApiKeyDomainAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,10 +12,8 @@ export async function verifyApiKeyDomainAccess(
|
|||||||
try {
|
try {
|
||||||
const apiKey = req.apiKey;
|
const apiKey = req.apiKey;
|
||||||
const domainId =
|
const domainId =
|
||||||
getFirstString(req.params.domainId) ||
|
req.params.domainId || req.body.domainId || req.query.domainId;
|
||||||
getFirstString(req.body.domainId) ||
|
const orgId = req.params.orgId;
|
||||||
getFirstString(req.query.domainId);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return next(
|
return next(
|
||||||
@@ -30,12 +27,6 @@ export async function verifyApiKeyDomainAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orgId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (apiKey.isRoot) {
|
if (apiKey.isRoot) {
|
||||||
// Root keys can access any domain in any org
|
// Root keys can access any domain in any org
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { idp, idpOrg, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyIdpAccess(
|
export async function verifyApiKeyIdpAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,12 +12,8 @@ export async function verifyApiKeyIdpAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const apiKey = req.apiKey;
|
const apiKey = req.apiKey;
|
||||||
const idpIdRaw =
|
const idpId = req.params.idpId || req.body.idpId || req.query.idpId;
|
||||||
getFirstString(req.params.idpId) ||
|
const orgId = req.params.orgId;
|
||||||
getFirstString(req.body.idpId) ||
|
|
||||||
getFirstString(req.query.idpId);
|
|
||||||
const idpId = Number.parseInt(idpIdRaw ?? "", 10);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return next(
|
return next(
|
||||||
@@ -32,7 +27,7 @@ export async function verifyApiKeyIdpAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(idpId)) {
|
if (!idpId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid IDP ID")
|
createHttpError(HttpCode.BAD_REQUEST, "Invalid IDP ID")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyOrgAccess(
|
export async function verifyApiKeyOrgAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,7 +12,7 @@ export async function verifyApiKeyOrgAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const apiKeyId = req.apiKey?.apiKeyId;
|
const apiKeyId = req.apiKey?.apiKeyId;
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
if (!apiKeyId) {
|
if (!apiKeyId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -46,7 +45,7 @@ export async function verifyApiKeyOrgAccess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.apiKeyOrg) {
|
if (!req.apiKeyOrg) {
|
||||||
return next(
|
next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
"Key does not have access to this organization"
|
"Key does not have access to this organization"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { siteResources, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeySiteResourceAccess(
|
export async function verifyApiKeySiteResourceAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,8 +12,7 @@ export async function verifyApiKeySiteResourceAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const apiKey = req.apiKey;
|
const apiKey = req.apiKey;
|
||||||
const siteResourceIdRaw = getFirstString(req.params.siteResourceId);
|
const siteResourceId = parseInt(req.params.siteResourceId);
|
||||||
const siteResourceId = Number.parseInt(siteResourceIdRaw ?? "", 10);
|
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return next(
|
return next(
|
||||||
@@ -22,7 +20,7 @@ export async function verifyApiKeySiteResourceAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(siteResourceId)) {
|
if (!siteResourceId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { resources, targets, apiKeyOrg } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyTargetAccess(
|
export async function verifyApiKeyTargetAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -13,8 +12,7 @@ export async function verifyApiKeyTargetAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const apiKey = req.apiKey;
|
const apiKey = req.apiKey;
|
||||||
const targetIdRaw = getFirstString(req.params.targetId);
|
const targetId = parseInt(req.params.targetId);
|
||||||
const targetId = Number.parseInt(targetIdRaw ?? "", 10);
|
|
||||||
|
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
return next(
|
return next(
|
||||||
@@ -22,7 +20,7 @@ export async function verifyApiKeyTargetAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(targetId)) {
|
if (isNaN(targetId)) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid target ID")
|
createHttpError(HttpCode.BAD_REQUEST, "Invalid target ID")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import { canUserAccessResource } from "@server/auth/canUserAccessResource";
|
import { canUserAccessResource } from "@server/auth/canUserAccessResource";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyAccessTokenAccess(
|
export async function verifyAccessTokenAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -15,7 +14,7 @@ export async function verifyAccessTokenAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const accessTokenId = getFirstString(req.params.accessTokenId);
|
const accessTokenId = req.params.accessTokenId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -23,12 +22,6 @@ export async function verifyAccessTokenAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!accessTokenId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid access token ID")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [accessToken] = await db
|
const [accessToken] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resourceAccessToken)
|
.from(resourceAccessToken)
|
||||||
@@ -94,7 +87,7 @@ export async function verifyAccessTokenAccess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!req.userOrg) {
|
if (!req.userOrg) {
|
||||||
return next(
|
next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
"User does not have access to this organization"
|
"User does not have access to this organization"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import createHttpError from "http-errors";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyApiKeyAccess(
|
export async function verifyApiKeyAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -15,24 +14,9 @@ export async function verifyApiKeyAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const apiKeyIdFromParams = getFirstString(req.params?.apiKeyId);
|
const apiKeyId =
|
||||||
const apiKeyIdFromBody = getFirstString(req.body?.apiKeyId);
|
req.params.apiKeyId || req.body.apiKeyId || req.query.apiKeyId;
|
||||||
|
const orgId = req.params.orgId;
|
||||||
if (
|
|
||||||
apiKeyIdFromParams &&
|
|
||||||
apiKeyIdFromBody &&
|
|
||||||
apiKeyIdFromParams !== apiKeyIdFromBody
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"API key ID provided in both URL and body with different values"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const apiKeyId = apiKeyIdFromParams || apiKeyIdFromBody;
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -120,7 +104,10 @@ export async function verifyApiKeyAccess(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req.userOrgRoleIds = await getUserOrgRoleIds(req.userOrg.userId, orgId);
|
req.userOrgRoleIds = await getUserOrgRoleIds(
|
||||||
|
req.userOrg.userId,
|
||||||
|
orgId
|
||||||
|
);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import createHttpError from "http-errors";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyDomainAccess(
|
export async function verifyDomainAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -15,8 +14,9 @@ export async function verifyDomainAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const domainId = getFirstString(req.params.domainId);
|
const domainId =
|
||||||
const orgId = getFirstString(req.params.orgId);
|
req.params.domainId;
|
||||||
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -62,7 +62,10 @@ export async function verifyDomainAccess(
|
|||||||
.select()
|
.select()
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(
|
.where(
|
||||||
and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))
|
and(
|
||||||
|
eq(userOrgs.userId, userId),
|
||||||
|
eq(userOrgs.orgId, orgId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
req.userOrg = userOrgRole[0];
|
req.userOrg = userOrgRole[0];
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import createHttpError from "http-errors";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyLimits(
|
export async function verifyLimits(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -14,10 +13,7 @@ export async function verifyLimits(
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgId =
|
const orgId = req.userOrgId || req.apiKeyOrg?.orgId || req.params.orgId;
|
||||||
req.userOrgId ||
|
|
||||||
req.apiKeyOrg?.orgId ||
|
|
||||||
getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return next(); // its fine if we silently fail here because this is not critical to operation or security and its better user experience if we dont fail
|
return next(); // its fine if we silently fail here because this is not critical to operation or security and its better user experience if we dont fail
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import createHttpError from "http-errors";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyOrgAccess(
|
export async function verifyOrgAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -14,7 +13,7 @@ export async function verifyOrgAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import {
|
import { db, userOrgs, siteProvisioningKeys, siteProvisioningKeyOrg } from "@server/db";
|
||||||
db,
|
|
||||||
userOrgs,
|
|
||||||
siteProvisioningKeys,
|
|
||||||
siteProvisioningKeyOrg
|
|
||||||
} from "@server/db";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifySiteProvisioningKeyAccess(
|
export async function verifySiteProvisioningKeyAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -19,10 +13,8 @@ export async function verifySiteProvisioningKeyAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const siteProvisioningKeyId = getFirstString(
|
const siteProvisioningKeyId = req.params.siteProvisioningKeyId;
|
||||||
req.params.siteProvisioningKeyId
|
const orgId = req.params.orgId;
|
||||||
);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -88,7 +80,10 @@ export async function verifySiteProvisioningKeyAccess(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(userOrgs.userId, userId),
|
eq(userOrgs.userId, userId),
|
||||||
eq(userOrgs.orgId, row.siteProvisioningKeyOrg.orgId)
|
eq(
|
||||||
|
userOrgs.orgId,
|
||||||
|
row.siteProvisioningKeyOrg.orgId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import { canUserAccessResource } from "../auth/canUserAccessResource";
|
import { canUserAccessResource } from "../auth/canUserAccessResource";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyTargetAccess(
|
export async function verifyTargetAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -15,8 +14,7 @@ export async function verifyTargetAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const targetIdRaw = getFirstString(req.params.targetId);
|
const targetId = parseInt(req.params.targetId);
|
||||||
const targetId = Number.parseInt(targetIdRaw ?? "", 10);
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { userOrgs } from "@server/db";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyUserIsOrgOwner(
|
export async function verifyUserIsOrgOwner(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -12,7 +11,7 @@ export async function verifyUserIsOrgOwner(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const orgId = req.params.orgId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
+1
-145
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
import NodeCache from "node-cache";
|
import NodeCache from "node-cache";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { redisManager, regionalRedisManager } from "@server/private/lib/redis";
|
import { redisManager } from "@server/private/lib/redis";
|
||||||
|
|
||||||
// Create local cache with maxKeys limit to prevent memory leaks
|
// Create local cache with maxKeys limit to prevent memory leaks
|
||||||
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
|
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
|
||||||
@@ -298,147 +298,3 @@ class AdaptiveCache {
|
|||||||
// Export singleton instance
|
// Export singleton instance
|
||||||
export const cache = new AdaptiveCache();
|
export const cache = new AdaptiveCache();
|
||||||
export default cache;
|
export default cache;
|
||||||
|
|
||||||
/**
|
|
||||||
* Regional adaptive cache backed by the in-cluster Redis instance.
|
|
||||||
* Falls back to a local NodeCache when the regional Redis is unavailable.
|
|
||||||
* Use this for data that is regional in nature (e.g. status history) so
|
|
||||||
* reads are served from the same cluster the user is hitting.
|
|
||||||
*/
|
|
||||||
const regionalLocalCache = new NodeCache({
|
|
||||||
stdTTL: 3600,
|
|
||||||
checkperiod: 120,
|
|
||||||
maxKeys: 10000
|
|
||||||
});
|
|
||||||
|
|
||||||
class RegionalAdaptiveCache {
|
|
||||||
private useRedis(): boolean {
|
|
||||||
return (
|
|
||||||
regionalRedisManager.isRedisEnabled() &&
|
|
||||||
regionalRedisManager.getHealthStatus().isHealthy
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(key: string, value: any, ttl?: number): Promise<boolean> {
|
|
||||||
const effectiveTtl = ttl === 0 ? undefined : ttl;
|
|
||||||
const redisTtl = ttl === 0 ? undefined : (ttl ?? 3600);
|
|
||||||
|
|
||||||
if (this.useRedis()) {
|
|
||||||
try {
|
|
||||||
const serialized = JSON.stringify(value);
|
|
||||||
const success = await regionalRedisManager.set(
|
|
||||||
key,
|
|
||||||
serialized,
|
|
||||||
redisTtl
|
|
||||||
);
|
|
||||||
if (success) {
|
|
||||||
logger.debug(`[regional] Set key in Redis: ${key}`);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
`[regional] Redis set error for key ${key}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const success = regionalLocalCache.set(key, value, effectiveTtl || 0);
|
|
||||||
if (success) logger.debug(`[regional] Set key in local cache: ${key}`);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get<T = any>(key: string): Promise<T | undefined> {
|
|
||||||
if (this.useRedis()) {
|
|
||||||
try {
|
|
||||||
const value = await regionalRedisManager.get(key);
|
|
||||||
if (value !== null) {
|
|
||||||
logger.debug(`[regional] Cache hit in Redis: ${key}`);
|
|
||||||
return JSON.parse(value) as T;
|
|
||||||
}
|
|
||||||
logger.debug(`[regional] Cache miss in Redis: ${key}`);
|
|
||||||
return undefined;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
`[regional] Redis get error for key ${key}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = regionalLocalCache.get<T>(key);
|
|
||||||
if (value !== undefined) {
|
|
||||||
logger.debug(`[regional] Cache hit in local cache: ${key}`);
|
|
||||||
} else {
|
|
||||||
logger.debug(`[regional] Cache miss in local cache: ${key}`);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async del(key: string | string[]): Promise<number> {
|
|
||||||
const keys = Array.isArray(key) ? key : [key];
|
|
||||||
let deletedCount = 0;
|
|
||||||
|
|
||||||
if (this.useRedis()) {
|
|
||||||
try {
|
|
||||||
for (const k of keys) {
|
|
||||||
const success = await regionalRedisManager.del(k);
|
|
||||||
if (success) {
|
|
||||||
deletedCount++;
|
|
||||||
logger.debug(`[regional] Deleted key from Redis: ${k}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (deletedCount === keys.length) return deletedCount;
|
|
||||||
deletedCount = 0;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[regional] Redis del error:`, error);
|
|
||||||
deletedCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const k of keys) {
|
|
||||||
const count = regionalLocalCache.del(k);
|
|
||||||
if (count > 0) {
|
|
||||||
deletedCount++;
|
|
||||||
logger.debug(`[regional] Deleted key from local cache: ${k}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deletedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
async has(key: string): Promise<boolean> {
|
|
||||||
if (this.useRedis()) {
|
|
||||||
try {
|
|
||||||
const value = await regionalRedisManager.get(key);
|
|
||||||
return value !== null;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(
|
|
||||||
`[regional] Redis has error for key ${key}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return regionalLocalCache.has(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns keys matching the given prefix from whichever backend is active.
|
|
||||||
* Redis uses a KEYS scan; local cache filters in-memory keys.
|
|
||||||
*/
|
|
||||||
async keysWithPrefix(prefix: string): Promise<string[]> {
|
|
||||||
if (this.useRedis()) {
|
|
||||||
try {
|
|
||||||
return await regionalRedisManager.keys(`${prefix}*`);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[regional] Redis keys error:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return regionalLocalCache.keys().filter((k) => k.startsWith(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentBackend(): "redis" | "local" {
|
|
||||||
return this.useRedis() ? "redis" : "local";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const regionalCache = new RegionalAdaptiveCache();
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ import { LogStreamingManager } from "./LogStreamingManager";
|
|||||||
*/
|
*/
|
||||||
export const logStreamingManager = new LogStreamingManager();
|
export const logStreamingManager = new LogStreamingManager();
|
||||||
|
|
||||||
if (build !== "saas") {
|
if (build != "saas") { // this is handled separately in the saas build, so we don't want to start it here
|
||||||
// this is handled separately in the saas build, so we don't want to start it here
|
|
||||||
logStreamingManager.start();
|
logStreamingManager.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,25 +73,6 @@ export const privateConfigSchema = z
|
|||||||
.object({
|
.object({
|
||||||
rejectUnauthorized: z.boolean().optional().default(true)
|
rejectUnauthorized: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
.optional(),
|
|
||||||
regional_redis: z
|
|
||||||
.object({
|
|
||||||
host: z.string(),
|
|
||||||
port: portSchema,
|
|
||||||
password: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.transform(getEnvOrYaml("REGIONAL_REDIS_PASSWORD")),
|
|
||||||
db: z.int().nonnegative().optional().default(0),
|
|
||||||
tls: z
|
|
||||||
.object({
|
|
||||||
rejectUnauthorized: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.default(true)
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
})
|
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
|
|||||||
@@ -855,163 +855,3 @@ class RedisManager {
|
|||||||
export const redisManager = new RedisManager();
|
export const redisManager = new RedisManager();
|
||||||
export const redis = redisManager.getClient();
|
export const redis = redisManager.getClient();
|
||||||
export default redisManager;
|
export default redisManager;
|
||||||
|
|
||||||
/**
|
|
||||||
* Lightweight Redis manager for the regional (in-cluster) Redis instance.
|
|
||||||
* Connects only when `redis.regional_redis` is present in the private config
|
|
||||||
* and `flags.enable_redis` is true. No pub/sub — designed for low-latency
|
|
||||||
* caching of regionally-scoped data.
|
|
||||||
*/
|
|
||||||
class RegionalRedisManager {
|
|
||||||
private writeClient: Redis | null = null;
|
|
||||||
private readClient: Redis | null = null;
|
|
||||||
private isEnabled: boolean = false;
|
|
||||||
private isHealthy: boolean = false;
|
|
||||||
private connectionTimeout: number = 5000;
|
|
||||||
private commandTimeout: number = 5000;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
if (build === "oss") return;
|
|
||||||
|
|
||||||
const cfg = privateConfig.getRawPrivateConfig();
|
|
||||||
if (!cfg.flags.enable_redis || !cfg.redis?.regional_redis) return;
|
|
||||||
|
|
||||||
this.isEnabled = true;
|
|
||||||
this.initializeClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getConfig(): RedisOptions {
|
|
||||||
const r = privateConfig.getRawPrivateConfig().redis!.regional_redis!;
|
|
||||||
const opts: RedisOptions = {
|
|
||||||
host: r.host,
|
|
||||||
port: r.port,
|
|
||||||
password: r.password,
|
|
||||||
db: r.db
|
|
||||||
};
|
|
||||||
if (r.tls) {
|
|
||||||
opts.tls = { rejectUnauthorized: r.tls.rejectUnauthorized ?? true };
|
|
||||||
}
|
|
||||||
return opts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private initializeClients(): void {
|
|
||||||
const cfg = this.getConfig();
|
|
||||||
const baseOpts = {
|
|
||||||
...cfg,
|
|
||||||
enableReadyCheck: false,
|
|
||||||
maxRetriesPerRequest: 3,
|
|
||||||
keepAlive: 10000,
|
|
||||||
connectTimeout: this.connectionTimeout,
|
|
||||||
commandTimeout: this.commandTimeout
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.writeClient = new Redis(baseOpts);
|
|
||||||
// redis-1 (replica) handles reads; fall back to primary if not resolvable
|
|
||||||
this.readClient = new Redis({
|
|
||||||
...baseOpts,
|
|
||||||
host: cfg.host!.replace(/^(.*?)(\.\S+)$/, (_, h, rest) => {
|
|
||||||
// Derive replica hostname from the headless service pattern:
|
|
||||||
// redis.redis.svc.cluster.local -> redis-1.redis-headless.redis.svc.cluster.local
|
|
||||||
// If it doesn't look like a k8s service, just use the same host
|
|
||||||
return h + rest;
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
// For simplicity use same host for both; callers can always read from primary
|
|
||||||
// The real replica routing is handled by the StatefulSet headless service
|
|
||||||
this.readClient = this.writeClient;
|
|
||||||
|
|
||||||
this.writeClient.on("ready", () => {
|
|
||||||
logger.info("Regional Redis client ready");
|
|
||||||
this.isHealthy = true;
|
|
||||||
});
|
|
||||||
this.writeClient.on("error", (err) => {
|
|
||||||
logger.error("Regional Redis client error:", err);
|
|
||||||
this.isHealthy = false;
|
|
||||||
});
|
|
||||||
this.writeClient.on("reconnecting", () => {
|
|
||||||
logger.info("Regional Redis client reconnecting...");
|
|
||||||
this.isHealthy = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("Regional Redis client initialized");
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Failed to initialize regional Redis client:", error);
|
|
||||||
this.isEnabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public isRedisEnabled(): boolean {
|
|
||||||
return this.isEnabled && this.writeClient !== null && this.isHealthy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getHealthStatus() {
|
|
||||||
return { isEnabled: this.isEnabled, isHealthy: this.isHealthy };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async set(
|
|
||||||
key: string,
|
|
||||||
value: string,
|
|
||||||
ttl?: number
|
|
||||||
): Promise<boolean> {
|
|
||||||
if (!this.isRedisEnabled() || !this.writeClient) return false;
|
|
||||||
try {
|
|
||||||
if (ttl) {
|
|
||||||
await this.writeClient.setex(key, ttl, value);
|
|
||||||
} else {
|
|
||||||
await this.writeClient.set(key, value);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Regional Redis SET error:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async get(key: string): Promise<string | null> {
|
|
||||||
if (!this.isRedisEnabled() || !this.readClient) return null;
|
|
||||||
try {
|
|
||||||
return await this.readClient.get(key);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Regional Redis GET error:", error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async del(key: string): Promise<boolean> {
|
|
||||||
if (!this.isRedisEnabled() || !this.writeClient) return false;
|
|
||||||
try {
|
|
||||||
await this.writeClient.del(key);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Regional Redis DEL error:", error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async keys(pattern: string): Promise<string[]> {
|
|
||||||
if (!this.isRedisEnabled() || !this.readClient) return [];
|
|
||||||
try {
|
|
||||||
return await this.readClient.keys(pattern);
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Regional Redis KEYS error:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async disconnect(): Promise<void> {
|
|
||||||
try {
|
|
||||||
if (this.writeClient) {
|
|
||||||
await this.writeClient.quit();
|
|
||||||
this.writeClient = null;
|
|
||||||
}
|
|
||||||
this.readClient = null;
|
|
||||||
logger.info("Regional Redis client disconnected");
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error disconnecting regional Redis client:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const regionalRedisManager = new RegionalRedisManager();
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { eq, and } from "drizzle-orm";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyCertificateAccess(
|
export async function verifyCertificateAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -28,43 +27,11 @@ export async function verifyCertificateAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
// Assume user/org access is already verified
|
// Assume user/org access is already verified
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const orgId = req.params.orgId;
|
||||||
|
const certId =
|
||||||
const certIdFromParams = getFirstString(req.params?.certId);
|
req.params.certId || req.body?.certId || req.query?.certId;
|
||||||
const certIdFromBody = getFirstString(req.body?.certId);
|
let domainId =
|
||||||
|
req.params.domainId || req.body?.domainId || req.query?.domainId;
|
||||||
if (
|
|
||||||
certIdFromParams &&
|
|
||||||
certIdFromBody &&
|
|
||||||
certIdFromParams !== certIdFromBody
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Certificate ID provided in both URL and body with different values"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const certId = certIdFromParams || certIdFromBody;
|
|
||||||
|
|
||||||
const domainIdFromParams = getFirstString(req.params?.domainId);
|
|
||||||
const domainIdFromBody = getFirstString(req.body?.domainId);
|
|
||||||
|
|
||||||
if (
|
|
||||||
domainIdFromParams &&
|
|
||||||
domainIdFromBody &&
|
|
||||||
domainIdFromParams !== domainIdFromBody
|
|
||||||
) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Domain ID provided in both URL and body with different values"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domainId = domainIdFromParams || domainIdFromBody;
|
|
||||||
|
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -98,7 +65,7 @@ export async function verifyCertificateAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
domainId = cert.domainId ?? undefined;
|
domainId = cert.domainId;
|
||||||
if (!domainId) {
|
if (!domainId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { and, eq } from "drizzle-orm";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyIdpAccess(
|
export async function verifyIdpAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -26,12 +25,8 @@ export async function verifyIdpAccess(
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const idpIdRaw =
|
const idpId = req.params.idpId || req.body.idpId || req.query.idpId;
|
||||||
getFirstString(req.params.idpId) ||
|
const orgId = req.params.orgId;
|
||||||
getFirstString(req.body?.idpId) ||
|
|
||||||
getFirstString(req.query?.idpId);
|
|
||||||
const idpId = Number.parseInt(idpIdRaw ?? "", 10);
|
|
||||||
const orgId = getFirstString(req.params.orgId);
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -45,7 +40,7 @@ export async function verifyIdpAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(idpId)) {
|
if (!idpId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid key ID")
|
createHttpError(HttpCode.BAD_REQUEST, "Invalid key ID")
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { and, eq } from "drizzle-orm";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
|
|
||||||
export async function verifyRemoteExitNodeAccess(
|
export async function verifyRemoteExitNodeAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -26,11 +25,11 @@ export async function verifyRemoteExitNodeAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId; // Assuming you have user information in the request
|
const userId = req.user!.userId; // Assuming you have user information in the request
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const orgId = req.params.orgId;
|
||||||
const remoteExitNodeId =
|
const remoteExitNodeId =
|
||||||
getFirstString(req.params.remoteExitNodeId) ||
|
req.params.remoteExitNodeId ||
|
||||||
getFirstString(req.body?.remoteExitNodeId) ||
|
req.body.remoteExitNodeId ||
|
||||||
getFirstString(req.query?.remoteExitNodeId);
|
req.query.remoteExitNodeId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -38,15 +37,6 @@ export async function verifyRemoteExitNodeAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orgId || !remoteExitNodeId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"Invalid organization or remote exit node ID"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [remoteExitNode] = await db
|
const [remoteExitNode] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function verifyValidSubscription(tiers: Tier[]) {
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
if (build !== "saas") {
|
if (build != "saas") {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -202,7 +202,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createAlertRule(
|
export async function createAlertRule(
|
||||||
|
|||||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteAlertRule(
|
export async function deleteAlertRule(
|
||||||
|
|||||||
@@ -49,7 +49,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getAlertRule(
|
export async function getAlertRule(
|
||||||
|
|||||||
@@ -95,7 +95,22 @@ registry.registerPath({
|
|||||||
query: querySchema,
|
query: querySchema,
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listAlertRules(
|
export async function listAlertRules(
|
||||||
|
|||||||
@@ -164,7 +164,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateAlertRule(
|
export async function updateAlertRule(
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type { NextFunction, Request, Response } from "express";
|
|||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string(),
|
orgId: z.string(),
|
||||||
approvalId: z.string().transform(Number).pipe(z.int().positive())
|
approvalId: z.coerce.number().int().positive()
|
||||||
});
|
});
|
||||||
|
|
||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { z } from "zod";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
queryAccessAuditLogsParams,
|
queryAccessAuditLogsParams,
|
||||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
|||||||
query: queryAccessAuditLogsQuery,
|
query: queryAccessAuditLogsQuery,
|
||||||
params: queryAccessAuditLogsParams
|
params: queryAccessAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function exportAccessAuditLogs(
|
export async function exportAccessAuditLogs(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { z } from "zod";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
queryActionAuditLogsParams,
|
queryActionAuditLogsParams,
|
||||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
|||||||
query: queryActionAuditLogsQuery,
|
query: queryActionAuditLogsQuery,
|
||||||
params: queryActionAuditLogsParams
|
params: queryActionAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function exportActionAuditLogs(
|
export async function exportActionAuditLogs(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { z } from "zod";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
queryConnectionAuditLogsParams,
|
queryConnectionAuditLogsParams,
|
||||||
@@ -37,7 +38,22 @@ registry.registerPath({
|
|||||||
query: queryConnectionAuditLogsQuery,
|
query: queryConnectionAuditLogsQuery,
|
||||||
params: queryConnectionAuditLogsParams
|
params: queryConnectionAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function exportConnectionAuditLogs(
|
export async function exportConnectionAuditLogs(
|
||||||
|
|||||||
@@ -324,7 +324,22 @@ registry.registerPath({
|
|||||||
query: queryAccessAuditLogsQuery,
|
query: queryAccessAuditLogsQuery,
|
||||||
params: queryAccessAuditLogsParams
|
params: queryAccessAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function queryAccessAuditLogs(
|
export async function queryAccessAuditLogs(
|
||||||
|
|||||||
@@ -165,7 +165,22 @@ registry.registerPath({
|
|||||||
query: queryActionAuditLogsQuery,
|
query: queryActionAuditLogsQuery,
|
||||||
params: queryActionAuditLogsParams
|
params: queryActionAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function queryActionAuditLogs(
|
export async function queryActionAuditLogs(
|
||||||
|
|||||||
@@ -439,7 +439,22 @@ registry.registerPath({
|
|||||||
query: queryConnectionAuditLogsQuery,
|
query: queryConnectionAuditLogsQuery,
|
||||||
params: queryConnectionAuditLogsParams
|
params: queryConnectionAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function queryConnectionAuditLogs(
|
export async function queryConnectionAuditLogs(
|
||||||
|
|||||||
@@ -39,7 +39,22 @@ const getOrgSchema = z.strictObject({
|
|||||||
// request: {
|
// request: {
|
||||||
// params: getOrgSchema
|
// params: getOrgSchema
|
||||||
// },
|
// },
|
||||||
// responses: {}
|
// responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// });
|
// });
|
||||||
|
|
||||||
export async function getOrgUsage(
|
export async function getOrgUsage(
|
||||||
|
|||||||
@@ -115,7 +115,22 @@ registry.registerPath({
|
|||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getCertificate(
|
export async function getCertificate(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
const restartCertificateParamsSchema = z.strictObject({
|
const restartCertificateParamsSchema = z.strictObject({
|
||||||
certId: z.string().transform(stoi).pipe(z.int().positive()),
|
certId: z.coerce.number().int().positive(),
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -36,11 +36,26 @@ registry.registerPath({
|
|||||||
tags: ["Certificate"],
|
tags: ["Certificate"],
|
||||||
request: {
|
request: {
|
||||||
params: z.object({
|
params: z.object({
|
||||||
certId: z.string().transform(stoi).pipe(z.int().positive()),
|
certId: z.coerce.number().int().positive(),
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function restartCertificate(
|
export async function restartCertificate(
|
||||||
|
|||||||
@@ -42,7 +42,22 @@ registry.registerPath({
|
|||||||
params: paramsSchema,
|
params: paramsSchema,
|
||||||
query: querySchema
|
query: querySchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function checkDomainNamespaceAvailability(
|
export async function checkDomainNamespaceAvailability(
|
||||||
|
|||||||
@@ -73,7 +73,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
query: querySchema
|
query: querySchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listDomainNamespaces(
|
export async function listDomainNamespaces(
|
||||||
|
|||||||
+16
-1
@@ -58,7 +58,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createEventStreamingDestination(
|
export async function createEventStreamingDestination(
|
||||||
|
|||||||
+16
-1
@@ -38,7 +38,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteEventStreamingDestination(
|
export async function deleteEventStreamingDestination(
|
||||||
|
|||||||
@@ -88,7 +88,22 @@ registry.registerPath({
|
|||||||
query: querySchema,
|
query: querySchema,
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listEventStreamingDestinations(
|
export async function listEventStreamingDestinations(
|
||||||
|
|||||||
+16
-1
@@ -61,7 +61,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateEventStreamingDestination(
|
export async function updateEventStreamingDestination(
|
||||||
|
|||||||
@@ -31,8 +31,6 @@ import * as siteProvisioning from "#private/routers/siteProvisioning";
|
|||||||
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
|
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
|
||||||
import * as alertRule from "#private/routers/alertRule";
|
import * as alertRule from "#private/routers/alertRule";
|
||||||
import * as healthChecks from "#private/routers/healthChecks";
|
import * as healthChecks from "#private/routers/healthChecks";
|
||||||
import * as labels from "#private/routers/labels";
|
|
||||||
import * as client from "@server/routers/client";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
@@ -734,59 +732,6 @@ authenticated.get(
|
|||||||
alertRule.getAlertRule
|
alertRule.getAlertRule
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/org/:orgId/labels",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyValidSubscription(tierMatrix.labels),
|
|
||||||
verifyUserHasAction(ActionsEnum.listOrgLabels),
|
|
||||||
labels.listOrgLabels
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/org/:orgId/labels",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyValidSubscription(tierMatrix.labels),
|
|
||||||
verifyUserHasAction(ActionsEnum.createOrgLabel),
|
|
||||||
labels.createOrgLabel
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.patch(
|
|
||||||
"/org/:orgId/label/:labelId",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyValidSubscription(tierMatrix.labels),
|
|
||||||
verifyUserHasAction(ActionsEnum.updateOrgLabel),
|
|
||||||
labels.updateOrgLabel
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.delete(
|
|
||||||
"/org/:orgId/label/:labelId",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyUserHasAction(ActionsEnum.deleteOrgLabel),
|
|
||||||
labels.deleteOrgLabel
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.put(
|
|
||||||
"/org/:orgId/label/:labelId/attach",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyValidSubscription(tierMatrix.labels),
|
|
||||||
verifyUserHasAction(ActionsEnum.attachLabelToItem),
|
|
||||||
labels.attachLabelToItem
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.put(
|
|
||||||
"/org/:orgId/label/:labelId/detach",
|
|
||||||
verifyValidLicense,
|
|
||||||
verifyOrgAccess,
|
|
||||||
verifyValidSubscription(tierMatrix.labels),
|
|
||||||
verifyUserHasAction(ActionsEnum.detachLabelFromItem),
|
|
||||||
labels.detachLabelFromItem
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/health-checks",
|
"/org/:orgId/health-checks",
|
||||||
verifyValidLicense,
|
verifyValidLicense,
|
||||||
@@ -830,15 +775,3 @@ authenticated.get(
|
|||||||
verifyUserHasAction(ActionsEnum.getTarget),
|
verifyUserHasAction(ActionsEnum.getTarget),
|
||||||
healthChecks.getHealthCheckStatusHistory
|
healthChecks.getHealthCheckStatusHistory
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.get(
|
|
||||||
"/client/:clientId/verify-associations-cache",
|
|
||||||
verifyClientAccess,
|
|
||||||
client.verifyClientAssociationsCache
|
|
||||||
);
|
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/client/:clientId/rebuild-associations-cache",
|
|
||||||
verifyClientAccess,
|
|
||||||
client.rebuildClientAssociationsCacheRoute
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -16,44 +16,40 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { response as sendResponse } from "@server/lib/response";
|
import { response as sendResponse } from "@server/lib/response";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
|
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
|
||||||
|
|
||||||
export interface CreateNewLicenseResponse {
|
export interface CreateNewLicenseResponse {
|
||||||
data: Data;
|
data: Data
|
||||||
success: boolean;
|
success: boolean
|
||||||
error: boolean;
|
error: boolean
|
||||||
message: string;
|
message: string
|
||||||
status: number;
|
status: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Data {
|
export interface Data {
|
||||||
licenseKey: LicenseKey;
|
licenseKey: LicenseKey
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LicenseKey {
|
export interface LicenseKey {
|
||||||
id: number;
|
id: number
|
||||||
instanceName: any;
|
instanceName: any
|
||||||
instanceId: string;
|
instanceId: string
|
||||||
licenseKey: string;
|
licenseKey: string
|
||||||
tier: string;
|
tier: string
|
||||||
type: string;
|
type: string
|
||||||
quantity: number;
|
quantity: number
|
||||||
quantity_2: number;
|
quantity_2: number
|
||||||
isValid: boolean;
|
isValid: boolean
|
||||||
updatedAt: string;
|
updatedAt: string
|
||||||
createdAt: string;
|
createdAt: string
|
||||||
expiresAt: string;
|
expiresAt: string
|
||||||
paidFor: boolean;
|
paidFor: boolean
|
||||||
orgId: string;
|
orgId: string
|
||||||
metadata: string;
|
metadata: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createNewLicense(
|
export async function createNewLicense(orgId: string, licenseData: any): Promise<CreateNewLicenseResponse> {
|
||||||
orgId: string,
|
|
||||||
licenseData: any
|
|
||||||
): Promise<CreateNewLicenseResponse> {
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/${orgId}/create`, // this says enterprise but it does both
|
`${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/${orgId}/create`, // this says enterprise but it does both
|
||||||
@@ -84,7 +80,7 @@ export async function generateNewLicense(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const { orgId } = req.params;
|
||||||
|
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { response as sendResponse } from "@server/lib/response";
|
import { response as sendResponse } from "@server/lib/response";
|
||||||
import { getFirstString } from "@server/lib/requestParams";
|
|
||||||
import privateConfig from "#private/lib/config";
|
import privateConfig from "#private/lib/config";
|
||||||
import {
|
import {
|
||||||
GeneratedLicenseKey,
|
GeneratedLicenseKey,
|
||||||
@@ -56,7 +55,7 @@ export async function listSaasLicenseKeys(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const orgId = getFirstString(req.params.orgId);
|
const { orgId } = req.params;
|
||||||
|
|
||||||
if (!orgId) {
|
if (!orgId) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -68,7 +68,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createHealthCheck(
|
export async function createHealthCheck(
|
||||||
|
|||||||
@@ -41,7 +41,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteHealthCheck(
|
export async function deleteHealthCheck(
|
||||||
|
|||||||
@@ -68,7 +68,22 @@ registry.registerPath({
|
|||||||
params: paramsSchema,
|
params: paramsSchema,
|
||||||
query: querySchema
|
query: querySchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listHealthChecks(
|
export async function listHealthChecks(
|
||||||
|
|||||||
@@ -97,7 +97,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateHealthCheck(
|
export async function updateHealthCheck(
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
clients,
|
|
||||||
clientLabels,
|
|
||||||
db,
|
|
||||||
labels,
|
|
||||||
resourceLabels,
|
|
||||||
resources,
|
|
||||||
siteLabels,
|
|
||||||
siteResourceLabels,
|
|
||||||
siteResources,
|
|
||||||
sites
|
|
||||||
} from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty(),
|
|
||||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachLabelBodySchema = z.strictObject({
|
|
||||||
siteId: z.number().int().optional(),
|
|
||||||
resourceId: z.number().int().optional(),
|
|
||||||
siteResourceId: z.number().int().optional(),
|
|
||||||
clientId: z.number().int().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function attachLabelToItem(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId, labelId } = parsedParams.data;
|
|
||||||
|
|
||||||
const parsedBody = attachLabelBodySchema.safeParse(req.body);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId, resourceId, siteResourceId, clientId } =
|
|
||||||
parsedBody.data;
|
|
||||||
|
|
||||||
if (!siteId && !resourceId && !siteResourceId && !clientId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [existing] = await db
|
|
||||||
.select()
|
|
||||||
.from(labels)
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Label with Id ${labelId} not found`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
const siteCount = await db.$count(
|
|
||||||
sites,
|
|
||||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (siteCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site with Id ${siteId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
|
||||||
await db
|
|
||||||
.insert(siteLabels)
|
|
||||||
.values({
|
|
||||||
labelId,
|
|
||||||
siteId
|
|
||||||
})
|
|
||||||
.onConflictDoNothing();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceId) {
|
|
||||||
const resourceCount = await db.$count(
|
|
||||||
resources,
|
|
||||||
and(
|
|
||||||
eq(resources.resourceId, resourceId),
|
|
||||||
eq(resources.orgId, orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Resource with Id ${resourceId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
|
||||||
await db
|
|
||||||
.insert(resourceLabels)
|
|
||||||
.values({
|
|
||||||
labelId,
|
|
||||||
resourceId
|
|
||||||
})
|
|
||||||
.onConflictDoNothing();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteResourceId) {
|
|
||||||
const resourceCount = await db.$count(
|
|
||||||
siteResources,
|
|
||||||
and(
|
|
||||||
eq(siteResources.siteResourceId, siteResourceId),
|
|
||||||
eq(siteResources.orgId, orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`SiteResource with Id ${siteResourceId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
|
||||||
await db
|
|
||||||
.insert(siteResourceLabels)
|
|
||||||
.values({
|
|
||||||
labelId,
|
|
||||||
siteResourceId
|
|
||||||
})
|
|
||||||
.onConflictDoNothing();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientId) {
|
|
||||||
const clientCount = await db.$count(
|
|
||||||
clients,
|
|
||||||
and(
|
|
||||||
eq(clients.clientId, clientId),
|
|
||||||
eq(clients.orgId, orgId),
|
|
||||||
isNull(clients.userId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clientCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Client with Id ${clientId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// idempotent, calling this endpoint multiple times should attach the label only once
|
|
||||||
await db
|
|
||||||
.insert(clientLabels)
|
|
||||||
.values({
|
|
||||||
labelId,
|
|
||||||
clientId
|
|
||||||
})
|
|
||||||
.onConflictDoNothing();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: {},
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Label attached successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
import {
|
|
||||||
db,
|
|
||||||
labels,
|
|
||||||
resourceLabels,
|
|
||||||
resources,
|
|
||||||
siteLabels,
|
|
||||||
sites
|
|
||||||
} from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty()
|
|
||||||
});
|
|
||||||
|
|
||||||
const bodySchema = z.strictObject({
|
|
||||||
name: z.string().nonempty(),
|
|
||||||
color: z
|
|
||||||
.string()
|
|
||||||
.regex(/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i)
|
|
||||||
.nonempty(),
|
|
||||||
siteId: z.number().int().optional(),
|
|
||||||
resourceId: z.number().int().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function createOrgLabel(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
|
||||||
|
|
||||||
const parsedBody = bodySchema.safeParse(req.body);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, color, siteId, resourceId } = parsedBody.data;
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
const siteCount = await db.$count(
|
|
||||||
sites,
|
|
||||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (siteCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
`Site with Id ${siteId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceId) {
|
|
||||||
const resourceCount = await db.$count(
|
|
||||||
resources,
|
|
||||||
and(
|
|
||||||
eq(resources.resourceId, resourceId),
|
|
||||||
eq(resources.orgId, orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
`Resource with Id ${resourceId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = await db.transaction(async (tx) => {
|
|
||||||
const [label] = await tx
|
|
||||||
.insert(labels)
|
|
||||||
.values({
|
|
||||||
name,
|
|
||||||
color,
|
|
||||||
orgId
|
|
||||||
})
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
await tx.insert(siteLabels).values({
|
|
||||||
siteId,
|
|
||||||
labelId: label.labelId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceId) {
|
|
||||||
await tx.insert(resourceLabels).values({
|
|
||||||
resourceId,
|
|
||||||
labelId: label.labelId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return label;
|
|
||||||
});
|
|
||||||
|
|
||||||
return response<CreateOrEditLabelResponse>(res, {
|
|
||||||
data: { label },
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Org Label created successfully",
|
|
||||||
status: HttpCode.CREATED
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
import { db, labels } from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty(),
|
|
||||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function deleteOrgLabel(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId, labelId } = parsedParams.data;
|
|
||||||
|
|
||||||
const [existing] = await db
|
|
||||||
.select()
|
|
||||||
.from(labels)
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return next(createHttpError(HttpCode.NOT_FOUND, "Label not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.delete(labels)
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Label deleted successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
clients,
|
|
||||||
clientLabels,
|
|
||||||
db,
|
|
||||||
labels,
|
|
||||||
resourceLabels,
|
|
||||||
resources,
|
|
||||||
siteLabels,
|
|
||||||
siteResourceLabels,
|
|
||||||
siteResources,
|
|
||||||
sites
|
|
||||||
} from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, eq, isNull } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty(),
|
|
||||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
|
||||||
});
|
|
||||||
|
|
||||||
const detachLabelBodySchema = z.strictObject({
|
|
||||||
siteId: z.number().int().optional(),
|
|
||||||
resourceId: z.number().int().optional(),
|
|
||||||
siteResourceId: z.number().int().optional(),
|
|
||||||
clientId: z.number().int().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function detachLabelFromItem(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId, labelId } = parsedParams.data;
|
|
||||||
|
|
||||||
const parsedBody = detachLabelBodySchema.safeParse(req.body);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { siteId, resourceId, siteResourceId, clientId } =
|
|
||||||
parsedBody.data;
|
|
||||||
|
|
||||||
if (!siteId && !resourceId && !siteResourceId && !clientId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [existing] = await db
|
|
||||||
.select()
|
|
||||||
.from(labels)
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Label with Id ${labelId} not found`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteId) {
|
|
||||||
const siteCount = await db.$count(
|
|
||||||
sites,
|
|
||||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (siteCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Site with Id ${siteId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.delete(siteLabels)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteLabels.labelId, labelId),
|
|
||||||
eq(siteLabels.siteId, siteId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceId) {
|
|
||||||
const resourceCount = await db.$count(
|
|
||||||
resources,
|
|
||||||
and(
|
|
||||||
eq(resources.resourceId, resourceId),
|
|
||||||
eq(resources.orgId, orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Resource with Id ${resourceId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.delete(resourceLabels)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(resourceLabels.labelId, labelId),
|
|
||||||
eq(resourceLabels.resourceId, resourceId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteResourceId) {
|
|
||||||
const resourceCount = await db.$count(
|
|
||||||
siteResources,
|
|
||||||
and(
|
|
||||||
eq(siteResources.siteResourceId, siteResourceId),
|
|
||||||
eq(siteResources.orgId, orgId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (resourceCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`SiteResource with Id ${siteResourceId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.delete(siteResourceLabels)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteResourceLabels.labelId, labelId),
|
|
||||||
eq(siteResourceLabels.siteResourceId, siteResourceId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientId) {
|
|
||||||
const clientCount = await db.$count(
|
|
||||||
clients,
|
|
||||||
and(
|
|
||||||
eq(clients.clientId, clientId),
|
|
||||||
eq(clients.orgId, orgId),
|
|
||||||
isNull(clients.userId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clientCount === 0) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Client with Id ${clientId} doesn't exist.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.delete(clientLabels)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(clientLabels.labelId, labelId),
|
|
||||||
eq(clientLabels.clientId, clientId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
|
||||||
data: {},
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Label detached successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export * from "./listOrgLabels";
|
|
||||||
export * from "./createOrgLabel";
|
|
||||||
export * from "./updateOrgLabel";
|
|
||||||
export * from "./attachLabelToItem";
|
|
||||||
export * from "./detachLabelFromItem";
|
|
||||||
export * from "./deleteOrgLabel";
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { db, labels } from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, asc, eq, like, sql } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty()
|
|
||||||
});
|
|
||||||
|
|
||||||
const listLabelsSchema = z.object({
|
|
||||||
pageSize: z.coerce
|
|
||||||
.number<string>() // for prettier formatting
|
|
||||||
.int()
|
|
||||||
.positive()
|
|
||||||
.optional()
|
|
||||||
.catch(20)
|
|
||||||
.default(20)
|
|
||||||
.openapi({
|
|
||||||
type: "integer",
|
|
||||||
default: 20,
|
|
||||||
description: "Number of items per page"
|
|
||||||
}),
|
|
||||||
page: z.coerce
|
|
||||||
.number<string>() // for prettier formatting
|
|
||||||
.int()
|
|
||||||
.min(0)
|
|
||||||
.optional()
|
|
||||||
.catch(1)
|
|
||||||
.default(1)
|
|
||||||
.openapi({
|
|
||||||
type: "integer",
|
|
||||||
default: 1,
|
|
||||||
description: "Page number to retrieve"
|
|
||||||
}),
|
|
||||||
query: z.string().optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
function queryLabelsBase() {
|
|
||||||
return db
|
|
||||||
.select({
|
|
||||||
labelId: labels.labelId,
|
|
||||||
name: labels.name,
|
|
||||||
color: labels.color
|
|
||||||
})
|
|
||||||
.from(labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function listOrgLabels(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
): Promise<any> {
|
|
||||||
try {
|
|
||||||
const parsedQuery = listLabelsSchema.safeParse(req.query);
|
|
||||||
if (!parsedQuery.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedQuery.error)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const { orgId } = parsedParams.data;
|
|
||||||
|
|
||||||
if (req.user && orgId && orgId !== req.userOrgId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.FORBIDDEN,
|
|
||||||
"User does not have access to this organization"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { pageSize, page, query } = parsedQuery.data;
|
|
||||||
|
|
||||||
const conditions = [and(eq(labels.orgId, orgId))];
|
|
||||||
|
|
||||||
if (query) {
|
|
||||||
conditions.push(
|
|
||||||
like(
|
|
||||||
sql`LOWER(${labels.name})`,
|
|
||||||
"%" + query.toLowerCase() + "%"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseQuery = queryLabelsBase().where(and(...conditions));
|
|
||||||
|
|
||||||
// we need to add `as` so that drizzle filters the result as a subquery
|
|
||||||
const countQuery = db.$count(
|
|
||||||
queryLabelsBase()
|
|
||||||
.where(and(...conditions))
|
|
||||||
.as("filtered_labels")
|
|
||||||
);
|
|
||||||
|
|
||||||
const labelListQuery = baseQuery
|
|
||||||
.limit(pageSize)
|
|
||||||
.offset(pageSize * (page - 1))
|
|
||||||
.orderBy(asc(labels.name));
|
|
||||||
|
|
||||||
const [totalCount, rows] = await Promise.all([
|
|
||||||
countQuery,
|
|
||||||
labelListQuery
|
|
||||||
]);
|
|
||||||
|
|
||||||
return response<ListOrgLabelsResponse>(res, {
|
|
||||||
data: {
|
|
||||||
labels: rows,
|
|
||||||
pagination: {
|
|
||||||
total: totalCount,
|
|
||||||
pageSize,
|
|
||||||
page
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Labels retrieved successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { db, labels } from "@server/db";
|
|
||||||
import response from "@server/lib/response";
|
|
||||||
import logger from "@server/logger";
|
|
||||||
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
import { NextFunction, Request, Response } from "express";
|
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { fromError } from "zod-validation-error";
|
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
|
||||||
orgId: z.string().nonempty(),
|
|
||||||
labelId: z.string().transform(Number).pipe(z.int().positive())
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateLabelBodySchema = z.strictObject({
|
|
||||||
name: z.string().min(1).max(255).optional(),
|
|
||||||
color: z
|
|
||||||
.string()
|
|
||||||
.regex(/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i)
|
|
||||||
.nonempty()
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function updateOrgLabel(
|
|
||||||
req: Request,
|
|
||||||
res: Response,
|
|
||||||
next: NextFunction
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const parsedParams = paramsSchema.safeParse(req.params);
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedParams.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { orgId, labelId } = parsedParams.data;
|
|
||||||
|
|
||||||
const parsedBody = updateLabelBodySchema.safeParse(req.body);
|
|
||||||
if (!parsedBody.success) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.BAD_REQUEST,
|
|
||||||
fromError(parsedBody.error).toString()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [existing] = await db
|
|
||||||
.select()
|
|
||||||
.from(labels)
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
|
|
||||||
|
|
||||||
if (!existing) {
|
|
||||||
return next(createHttpError(HttpCode.NOT_FOUND, "Label not found"));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, color } = parsedBody.data;
|
|
||||||
|
|
||||||
const [label] = await db
|
|
||||||
.update(labels)
|
|
||||||
.set({
|
|
||||||
name,
|
|
||||||
color
|
|
||||||
})
|
|
||||||
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
return response<CreateOrEditLabelResponse>(res, {
|
|
||||||
data: {
|
|
||||||
label
|
|
||||||
},
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Label updated successfully",
|
|
||||||
status: HttpCode.OK
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(error);
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,6 +26,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, InferInsertModel } from "drizzle-orm";
|
import { eq, InferInsertModel } from "drizzle-orm";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import { validateLocalPath } from "@app/lib/validateLocalPath";
|
||||||
import config from "#private/lib/config";
|
import config from "#private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
@@ -34,9 +35,78 @@ const paramsSchema = z.strictObject({
|
|||||||
|
|
||||||
const bodySchema = z.strictObject({
|
const bodySchema = z.strictObject({
|
||||||
logoUrl: z
|
logoUrl: z
|
||||||
.string()
|
.union([
|
||||||
.optional()
|
z.literal(""),
|
||||||
.transform((val) => (val === "" ? null : val)),
|
z
|
||||||
|
.string()
|
||||||
|
.superRefine(async (urlOrPath, ctx) => {
|
||||||
|
const parseResult = z.url().safeParse(urlOrPath);
|
||||||
|
if (!parseResult.success) {
|
||||||
|
if (build !== "enterprise") {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: "Must be a valid URL"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
validateLocalPath(urlOrPath);
|
||||||
|
} catch (error) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: "Must be either a valid image URL or a valid pathname starting with `/` and not containing query parameters, `..` or `*`"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlOrPath, {
|
||||||
|
method: "HEAD"
|
||||||
|
}).catch(() => {
|
||||||
|
// If HEAD fails (CORS or method not allowed), try GET
|
||||||
|
return fetch(urlOrPath, { 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) {
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.transform((val) => (val === "" ? null : val))
|
||||||
|
.nullish(),
|
||||||
logoWidth: z.coerce.number<number>().min(1),
|
logoWidth: z.coerce.number<number>().min(1),
|
||||||
logoHeight: z.coerce.number<number>().min(1),
|
logoHeight: z.coerce.number<number>().min(1),
|
||||||
resourceTitle: z.string(),
|
resourceTitle: z.string(),
|
||||||
|
|||||||
@@ -63,7 +63,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createOrgOidcIdp(
|
export async function createOrgOidcIdp(
|
||||||
|
|||||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteOrgIdp(
|
export async function deleteOrgIdp(
|
||||||
|
|||||||
@@ -56,7 +56,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getOrgIdp(
|
export async function getOrgIdp(
|
||||||
|
|||||||
@@ -72,7 +72,22 @@ registry.registerPath({
|
|||||||
query: querySchema,
|
query: querySchema,
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listOrgIdps(
|
export async function listOrgIdps(
|
||||||
|
|||||||
@@ -70,7 +70,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function updateOrgOidcIdp(
|
export async function updateOrgOidcIdp(
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error";
|
|||||||
import { sendTerminateClient } from "@server/routers/client/terminate";
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
|
|
||||||
const reGenerateSecretParamsSchema = z.strictObject({
|
const reGenerateSecretParamsSchema = z.strictObject({
|
||||||
clientId: z.string().transform(Number).pipe(z.int().positive())
|
clientId: z.coerce.number().int().positive()
|
||||||
});
|
});
|
||||||
|
|
||||||
const reGenerateSecretBodySchema = z.strictObject({
|
const reGenerateSecretBodySchema = z.strictObject({
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { getAllowedIps } from "@server/routers/target/helpers";
|
|||||||
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
import { disconnectClient, sendToClient } from "#private/routers/ws";
|
||||||
|
|
||||||
const updateSiteParamsSchema = z.strictObject({
|
const updateSiteParamsSchema = z.strictObject({
|
||||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
siteId: z.coerce.number().int().positive()
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateSiteBodySchema = z.strictObject({
|
const updateSiteBodySchema = z.strictObject({
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import {
|
|||||||
logsDb,
|
logsDb,
|
||||||
newts,
|
newts,
|
||||||
roles,
|
roles,
|
||||||
roleSiteResources,
|
|
||||||
roundTripMessageTracker,
|
roundTripMessageTracker,
|
||||||
siteResources,
|
siteResources,
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
@@ -93,7 +92,22 @@ export type SignSshKeyResponse = {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// },
|
// },
|
||||||
// responses: {}
|
// responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// });
|
// });
|
||||||
|
|
||||||
export async function signSshKey(
|
export async function signSshKey(
|
||||||
@@ -362,26 +376,9 @@ export async function signSshKey(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const roleRows = await db
|
const roleRows = await db
|
||||||
.select({
|
.select()
|
||||||
sshSudoCommands: roles.sshSudoCommands,
|
|
||||||
sshUnixGroups: roles.sshUnixGroups,
|
|
||||||
sshCreateHomeDir: roles.sshCreateHomeDir,
|
|
||||||
sshSudoMode: roles.sshSudoMode
|
|
||||||
})
|
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.innerJoin(
|
.where(inArray(roles.roleId, roleIds));
|
||||||
roleSiteResources,
|
|
||||||
eq(roleSiteResources.roleId, roles.roleId)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(roles.roleId, roleIds),
|
|
||||||
eq(
|
|
||||||
roleSiteResources.siteResourceId,
|
|
||||||
resource.siteResourceId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const parsedSudoCommands: string[] = [];
|
const parsedSudoCommands: string[] = [];
|
||||||
const parsedGroupsSet = new Set<string>();
|
const parsedGroupsSet = new Set<string>();
|
||||||
@@ -397,17 +394,13 @@ export async function signSshKey(
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const grps = JSON.parse(roleRow?.sshUnixGroups ?? "[]");
|
const grps = JSON.parse(roleRow?.sshUnixGroups ?? "[]");
|
||||||
if (Array.isArray(grps))
|
if (Array.isArray(grps)) grps.forEach((g: string) => parsedGroupsSet.add(g));
|
||||||
grps.forEach((g: string) => parsedGroupsSet.add(g));
|
|
||||||
} catch {
|
} catch {
|
||||||
// skip
|
// skip
|
||||||
}
|
}
|
||||||
if (roleRow?.sshCreateHomeDir === true) homedir = true;
|
if (roleRow?.sshCreateHomeDir === true) homedir = true;
|
||||||
const m = roleRow?.sshSudoMode ?? "none";
|
const m = roleRow?.sshSudoMode ?? "none";
|
||||||
if (
|
if (sudoModeOrder[m as keyof typeof sudoModeOrder] > sudoModeOrder[sudoMode]) {
|
||||||
sudoModeOrder[m as keyof typeof sudoModeOrder] >
|
|
||||||
sudoModeOrder[sudoMode]
|
|
||||||
) {
|
|
||||||
sudoMode = m as "none" | "commands" | "full";
|
sudoMode = m as "none" | "commands" | "full";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
|
|||||||
|
|
||||||
const addUserRoleParamsSchema = z.strictObject({
|
const addUserRoleParamsSchema = z.strictObject({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
roleId: z.string().transform(stoi).pipe(z.number())
|
roleId: z.coerce.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -38,7 +38,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: addUserRoleParamsSchema
|
params: addUserRoleParamsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function addUserRole(
|
export async function addUserRole(
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
|
|||||||
|
|
||||||
const removeUserRoleParamsSchema = z.strictObject({
|
const removeUserRoleParamsSchema = z.strictObject({
|
||||||
userId: z.string(),
|
userId: z.string(),
|
||||||
roleId: z.string().transform(stoi).pipe(z.number())
|
roleId: z.coerce.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -39,7 +39,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: removeUserRoleParamsSchema
|
params: removeUserRoleParamsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function removeUserRole(
|
export async function removeUserRole(
|
||||||
|
|||||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: deleteAccessTokenParamsSchema
|
params: deleteAccessTokenParamsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteAccessToken(
|
export async function deleteAccessToken(
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const generateAccessTokenBodySchema = z.strictObject({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const generateAccssTokenParamsSchema = z.strictObject({
|
export const generateAccssTokenParamsSchema = z.strictObject({
|
||||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
resourceId: z.coerce.number().int().positive()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type GenerateAccessTokenResponse = Omit<
|
export type GenerateAccessTokenResponse = Omit<
|
||||||
@@ -54,7 +54,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function generateAccessToken(
|
export async function generateAccessToken(
|
||||||
|
|||||||
@@ -129,7 +129,22 @@ registry.registerPath({
|
|||||||
}),
|
}),
|
||||||
query: listAccessTokensSchema
|
query: listAccessTokensSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -143,7 +158,22 @@ registry.registerPath({
|
|||||||
}),
|
}),
|
||||||
query: listAccessTokensSchema
|
query: listAccessTokensSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listAccessTokens(
|
export async function listAccessTokens(
|
||||||
|
|||||||
@@ -48,7 +48,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createOrgApiKey(
|
export async function createOrgApiKey(
|
||||||
|
|||||||
@@ -22,7 +22,22 @@ registry.registerPath({
|
|||||||
request: {
|
request: {
|
||||||
params: paramsSchema
|
params: paramsSchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteApiKey(
|
export async function deleteApiKey(
|
||||||
|
|||||||
@@ -53,7 +53,22 @@ registry.registerPath({
|
|||||||
params: paramsSchema,
|
params: paramsSchema,
|
||||||
query: querySchema
|
query: querySchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listApiKeyActions(
|
export async function listApiKeyActions(
|
||||||
|
|||||||
@@ -57,7 +57,22 @@ registry.registerPath({
|
|||||||
params: paramsSchema,
|
params: paramsSchema,
|
||||||
query: querySchema
|
query: querySchema
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listOrgApiKeys(
|
export async function listOrgApiKeys(
|
||||||
|
|||||||
@@ -36,7 +36,22 @@ registry.registerPath({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function setApiKeyActions(
|
export async function setApiKeyActions(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { OpenAPITags } from "@server/openApi";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { z } from "zod";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import {
|
import {
|
||||||
queryAccessAuditLogsQuery,
|
queryAccessAuditLogsQuery,
|
||||||
@@ -28,7 +29,22 @@ registry.registerPath({
|
|||||||
}),
|
}),
|
||||||
params: queryRequestAuditLogsParams
|
params: queryRequestAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function exportRequestAuditLogs(
|
export async function exportRequestAuditLogs(
|
||||||
|
|||||||
@@ -156,7 +156,22 @@ registry.registerPath({
|
|||||||
query: queryAccessAuditLogsQuery,
|
query: queryAccessAuditLogsQuery,
|
||||||
params: queryRequestAuditLogsParams
|
params: queryRequestAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type QueryRequestAnalyticsResponse = Awaited<ReturnType<typeof query>>;
|
export type QueryRequestAnalyticsResponse = Awaited<ReturnType<typeof query>>;
|
||||||
|
|||||||
@@ -227,7 +227,22 @@ registry.registerPath({
|
|||||||
query: queryAccessAuditLogsQuery,
|
query: queryAccessAuditLogsQuery,
|
||||||
params: queryRequestAuditLogsParams
|
params: queryRequestAuditLogsParams
|
||||||
},
|
},
|
||||||
responses: {}
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.unknown().nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function queryUniqueFilterAttributes(
|
async function queryUniqueFilterAttributes(
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
|
|
||||||
export const params = z.strictObject({
|
export const params = z.strictObject({
|
||||||
token: z.string(),
|
token: z.string(),
|
||||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
resourceId: z.coerce.number().int().positive()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CheckResourceSessionParams = z.infer<typeof params>;
|
export type CheckResourceSessionParams = z.infer<typeof params>;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user