Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot] 30dc7e1a2f Bump the npm-dependencies group across 1 directory with 17 updates
Bumps the npm-dependencies group with 17 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1131.0` | `3.1135.0` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `14.0.1` | `14.0.2` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.102.8` | `5.103.1` |
| [@tanstack/react-table](https://github.com/TanStack/table/tree/HEAD/packages/react-table) | `8.21.3` | `9.2.4` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `5.4.1` | `5.4.2` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.45.0` | `1.47.0` |
| [moment](https://github.com/moment/moment) | `2.30.1` | `2.31.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.14.4` | `4.14.5` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `10.0.9` | `10.0.10` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.52.1` | `5.52.4` |
| [tailwind-merge](https://github.com/dcastil/tailwind-merge/tree/HEAD/packages/tailwind-merge) | `3.6.0` | `3.7.0` |
| [zod](https://github.com/colinhacks/zod) | `4.6.2` | `4.6.5` |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `2.24.1` | `2.28.0` |
| [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools) | `5.102.8` | `5.103.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.5.1` | `26.6.1` |
| [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer) | `8.0.1` | `8.0.2` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.8` |



Updates `@aws-sdk/client-s3` from 3.1131.0 to 3.1135.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1135.0/clients/client-s3)

Updates `@simplewebauthn/server` from 14.0.1 to 14.0.2
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v14.0.2/packages/server)

Updates `@tanstack/react-query` from 5.102.8 to 5.103.1
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.103.1/packages/react-query)

Updates `@tanstack/react-table` from 8.21.3 to 9.2.4
- [Release notes](https://github.com/TanStack/table/releases)
- [Changelog](https://github.com/TanStack/table/blob/main/packages/react-table/CHANGELOG.md)
- [Commits](https://github.com/TanStack/table/commits/@tanstack/react-table@9.2.4/packages/react-table)

Updates `js-yaml` from 5.4.1 to 5.4.2
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/5.4.1...5.4.2)

Updates `lucide-react` from 1.45.0 to 1.47.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.47.0/packages/lucide-react)

Updates `moment` from 2.30.1 to 2.31.0
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.30.1...2.31.0)

Updates `next-intl` from 4.14.4 to 4.14.5
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.14.4...v4.14.5)

Updates `nodemailer` from 10.0.9 to 10.0.10
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v10.0.9...v10.0.10)

Updates `posthog-node` from 5.52.1 to 5.52.4
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.52.4/packages/node)

Updates `tailwind-merge` from 3.6.0 to 3.7.0
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/commits/tailwind-merge@3.7.0/packages/tailwind-merge)

Updates `zod` from 4.6.2 to 4.6.5
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.6.2...v4.6.5)

Updates `@dotenvx/dotenvx` from 2.24.1 to 2.28.0
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v2.24.1...v2.28.0)

Updates `@tanstack/react-query-devtools` from 5.102.8 to 5.103.1
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query-devtools/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query-devtools@5.103.1/packages/react-query-devtools)

Updates `@types/node` from 26.5.1 to 26.6.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/nodemailer` from 8.0.1 to 8.0.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/nodemailer)

Updates `prettier` from 3.9.6 to 3.9.8
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.9.6...3.9.8)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1135.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@simplewebauthn/server"
  dependency-version: 14.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.103.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-table"
  dependency-version: 9.2.4
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: npm-dependencies
- dependency-name: js-yaml
  dependency-version: 5.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: lucide-react
  dependency-version: 1.47.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: moment
  dependency-version: 2.31.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: next-intl
  dependency-version: 4.14.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: nodemailer
  dependency-version: 10.0.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: posthog-node
  dependency-version: 5.52.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: tailwind-merge
  dependency-version: 3.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: zod
  dependency-version: 4.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 2.28.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.103.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/node"
  dependency-version: 26.6.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-dependencies
- dependency-name: "@types/nodemailer"
  dependency-version: 8.0.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
- dependency-name: prettier
  dependency-version: 3.9.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-21 01:39:05 +00:00
23 changed files with 820 additions and 530 deletions
+11 -18
View File
@@ -3,25 +3,18 @@ name: ESLint
permissions: permissions:
contents: read contents: read
# Disabled from running on PRs: typescript-eslint does not yet support
# TypeScript 7 (which this repo is on), so eslint currently crashes on
# every run. Kept as workflow_dispatch so it can still be triggered
# manually, and re-enabled on pull_request once upstream support lands.
# https://github.com/typescript-eslint/typescript-eslint/issues/10940
# on:
# pull_request:
# paths:
# - '**/*.js'
# - '**/*.jsx'
# - '**/*.ts'
# - '**/*.tsx'
# - '.eslintrc*'
# - 'package.json'
# - 'yarn.lock'
# - 'pnpm-lock.yaml'
# - 'package-lock.json'
on: on:
workflow_dispatch: pull_request:
paths:
- '**/*.js'
- '**/*.jsx'
- '**/*.ts'
- '**/*.tsx'
- '.eslintrc*'
- 'package.json'
- 'yarn.lock'
- 'pnpm-lock.yaml'
- 'package-lock.json'
jobs: jobs:
Linter: Linter:
+1 -1
View File
@@ -1,7 +1,7 @@
name: pangolin name: pangolin
services: services:
pangolin: pangolin:
image: docker.io/fosrl/pangolin:ee-postgresql-latest image: docker.io/fosrl/pangolin:ee-latest
container_name: pangolin container_name: pangolin
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+1 -1
View File
@@ -1,7 +1,7 @@
name: pangolin name: pangolin
services: services:
pangolin: pangolin:
image: docker.io/fosrl/pangolin:ee-postgresql-latest image: docker.io/fosrl/pangolin:ee-latest
container_name: pangolin container_name: pangolin
restart: unless-stopped restart: unless-stopped
volumes: volumes:
+3 -6
View File
@@ -132,7 +132,7 @@
"siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.", "siteRestartDialogMessage": "Are you sure you want to restart the WireGuard tunnel for <b>{name}</b>? The site will briefly lose connectivity.",
"siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.", "siteRestartWarning": "The site will briefly disconnect while the tunnel restarts.",
"siteRestarted": "Site restarted", "siteRestarted": "Site restarted",
"siteRestartedDescription": "The site has been restarted.", "siteRestartedDescription": "The WireGuard tunnel has been restarted.",
"siteErrorRestart": "Failed to restart site", "siteErrorRestart": "Failed to restart site",
"siteErrorRestartDescription": "An error occurred while restarting the site.", "siteErrorRestartDescription": "An error occurred while restarting the site.",
"siteSettingDescription": "Configure the settings on the site", "siteSettingDescription": "Configure the settings on the site",
@@ -834,7 +834,7 @@
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.",
"rulesErrorValidation": "Invalid rules", "rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}", "rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorInvalidMatchTypeDescription": "Select a valid match type (path, IP, CIDR, country, region, ASN, or method).", "rulesErrorInvalidMatchTypeDescription": "Select a valid match type (path, IP, CIDR, country, region, or ASN).",
"rulesErrorValueRequired": "Enter a value for this rule.", "rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country", "rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.", "rulesErrorInvalidCountryDescription": "Select a valid country.",
@@ -4400,8 +4400,5 @@
"sessionToolbarShow": "Show toolbar", "sessionToolbarShow": "Show toolbar",
"sessionToolbarHide": "Hide toolbar", "sessionToolbarHide": "Hide toolbar",
"actionUpdateSiteApprovals": "Update Site Approvals", "actionUpdateSiteApprovals": "Update Site Approvals",
"check": "Check", "check": "Check"
"rulesErrorInvalidMethod": "Invalid HTTP method",
"rulesErrorInvalidMethodDescription": "Select at least one HTTP method.",
"rulesSelectMethods": "Select methods"
} }
+697 -96
View File
File diff suppressed because it is too large Load Diff
+17 -17
View File
@@ -33,7 +33,7 @@
}, },
"dependencies": { "dependencies": {
"@asteasolutions/zod-to-openapi": "9.1.0", "@asteasolutions/zod-to-openapi": "9.1.0",
"@aws-sdk/client-s3": "3.1131.0", "@aws-sdk/client-s3": "3.1135.0",
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz", "@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz", "@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
"@headlessui/react": "2.2.10", "@headlessui/react": "2.2.10",
@@ -66,10 +66,10 @@
"@react-email/render": "2.1.0", "@react-email/render": "2.1.0",
"@react-email/tailwind": "2.0.7", "@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "14.0.0", "@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "14.0.1", "@simplewebauthn/server": "14.0.2",
"@tailwindcss/forms": "0.5.11", "@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.102.8", "@tanstack/react-query": "5.103.1",
"@tanstack/react-table": "8.21.3", "@tanstack/react-table": "9.2.4",
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0", "@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0", "@xterm/xterm": "^6.0.0",
@@ -96,20 +96,20 @@
"input-otp": "1.5.0", "input-otp": "1.5.0",
"ioredis": "6.0.0", "ioredis": "6.0.0",
"jmespath": "0.16.0", "jmespath": "0.16.0",
"js-yaml": "5.4.1", "js-yaml": "5.4.2",
"jsonwebtoken": "9.0.3", "jsonwebtoken": "9.0.3",
"lru-cache": "11.5.2", "lru-cache": "11.5.2",
"lucide-react": "1.45.0", "lucide-react": "1.47.0",
"maxmind": "5.0.7", "maxmind": "5.0.7",
"moment": "2.30.1", "moment": "2.31.0",
"next": "16.3.5", "next": "16.3.5",
"next-intl": "4.14.4", "next-intl": "4.14.5",
"next-themes": "0.4.6", "next-themes": "0.4.6",
"nextjs-toploader": "3.9.17", "nextjs-toploader": "3.9.17",
"nodemailer": "10.0.9", "nodemailer": "10.0.10",
"oslo": "1.2.1", "oslo": "1.2.1",
"pg": "8.23.0", "pg": "8.23.0",
"posthog-node": "5.52.1", "posthog-node": "5.52.4",
"qrcode.react": "4.2.0", "qrcode.react": "4.2.0",
"react": "19.3.0", "react": "19.3.0",
"react-day-picker": "10.0.1", "react-day-picker": "10.0.1",
@@ -123,7 +123,7 @@
"sshpk": "1.18.0", "sshpk": "1.18.0",
"stripe": "22.6.2", "stripe": "22.6.2",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0", "tailwind-merge": "3.7.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.1",
@@ -135,15 +135,15 @@
"ws": "8.21.3", "ws": "8.21.3",
"yaml": "2.9.1", "yaml": "2.9.1",
"yargs": "18.1.0", "yargs": "18.1.0",
"zod": "4.6.2", "zod": "4.6.5",
"zod-validation-error": "5.0.0" "zod-validation-error": "5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@dotenvx/dotenvx": "2.24.1", "@dotenvx/dotenvx": "2.28.0",
"@esbuild-plugins/tsconfig-paths": "0.1.2", "@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/ui": "^6.9.5", "@react-email/ui": "^6.9.5",
"@tailwindcss/postcss": "4.3.3", "@tailwindcss/postcss": "4.3.3",
"@tanstack/react-query-devtools": "5.102.8", "@tanstack/react-query-devtools": "5.103.1",
"@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",
@@ -155,8 +155,8 @@
"@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": "26.5.1", "@types/node": "26.6.1",
"@types/nodemailer": "8.0.1", "@types/nodemailer": "8.0.2",
"@types/nprogress": "0.2.3", "@types/nprogress": "0.2.3",
"@types/pg": "8.23.1", "@types/pg": "8.23.1",
"@types/react": "19.3.0", "@types/react": "19.3.0",
@@ -174,7 +174,7 @@
"eslint": "10.10.0", "eslint": "10.10.0",
"eslint-config-next": "16.3.5", "eslint-config-next": "16.3.5",
"postcss": "8.5.28", "postcss": "8.5.28",
"prettier": "3.9.6", "prettier": "3.9.8",
"react-email": "6.9.5", "react-email": "6.9.5",
"tailwindcss": "4.3.3", "tailwindcss": "4.3.3",
"tsc-alias": "1.9.5", "tsc-alias": "1.9.5",
+42 -71
View File
@@ -4,18 +4,42 @@
"iPad2,2": "iPad 2", "iPad2,2": "iPad 2",
"iPad2,3": "iPad 2", "iPad2,3": "iPad 2",
"iPad2,4": "iPad 2", "iPad2,4": "iPad 2",
"iPad2,5": "iPad mini",
"iPad2,6": "iPad mini",
"iPad2,7": "iPad mini",
"iPad3,1": "iPad 3rd Gen", "iPad3,1": "iPad 3rd Gen",
"iPad3,2": "iPad 3rd Gen",
"iPad3,3": "iPad 3rd Gen", "iPad3,3": "iPad 3rd Gen",
"iPad3,2": "iPad 3rd Gen",
"iPad3,4": "iPad 4th Gen", "iPad3,4": "iPad 4th Gen",
"iPad3,5": "iPad 4th Gen", "iPad3,5": "iPad 4th Gen",
"iPad3,6": "iPad 4th Gen", "iPad3,6": "iPad 4th Gen",
"iPad6,11": "iPad 9.7 5th Gen",
"iPad6,12": "iPad 9.7 5th Gen",
"iPad7,5": "iPad 9.7 6th Gen",
"iPad7,6": "iPad 9.7 6th Gen",
"iPad7,11": "iPad 10.2 7th Gen",
"iPad7,12": "iPad 10.2 7th Gen",
"iPad11,6": "iPad 10.2 8th Gen",
"iPad11,7": "iPad 10.2 8th Gen",
"iPad12,1": "iPad 10.2 9th Gen",
"iPad12,2": "iPad 10.2 9th Gen",
"iPad13,18": "iPad 10.9 10th Gen",
"iPad13,19": "iPad 10.9 10th Gen",
"iPad4,1": "iPad Air", "iPad4,1": "iPad Air",
"iPad4,2": "iPad Air", "iPad4,2": "iPad Air",
"iPad4,3": "iPad Air", "iPad4,3": "iPad Air",
"iPad5,3": "iPad Air 2",
"iPad5,4": "iPad Air 2",
"iPad11,3": "iPad Air 3rd Gen",
"iPad11,4": "iPad Air 3rd Gen",
"iPad13,1": "iPad Air 4th Gen",
"iPad13,2": "iPad Air 4th Gen",
"iPad13,16": "iPad Air 5th Gen",
"iPad13,17": "iPad Air 5th Gen",
"iPad14,8": "iPad Air M2 11",
"iPad14,9": "iPad Air M2 11",
"iPad14,10": "iPad Air M2 13",
"iPad14,11": "iPad Air M2 13",
"iPad2,5": "iPad mini",
"iPad2,6": "iPad mini",
"iPad2,7": "iPad mini",
"iPad4,4": "iPad mini 2", "iPad4,4": "iPad mini 2",
"iPad4,5": "iPad mini 2", "iPad4,5": "iPad mini 2",
"iPad4,6": "iPad mini 2", "iPad4,6": "iPad mini 2",
@@ -24,22 +48,18 @@
"iPad4,9": "iPad mini 3", "iPad4,9": "iPad mini 3",
"iPad5,1": "iPad mini 4", "iPad5,1": "iPad mini 4",
"iPad5,2": "iPad mini 4", "iPad5,2": "iPad mini 4",
"iPad5,3": "iPad Air 2", "iPad11,1": "iPad mini 5th Gen",
"iPad5,4": "iPad Air 2", "iPad11,2": "iPad mini 5th Gen",
"iPad6,3": "iPad Pro 9.7", "iPad14,1": "iPad mini 6th Gen",
"iPad6,4": "iPad Pro 9.7", "iPad14,2": "iPad mini 6th Gen",
"iPad6,7": "iPad Pro 12.9", "iPad6,7": "iPad Pro 12.9",
"iPad6,8": "iPad Pro 12.9", "iPad6,8": "iPad Pro 12.9",
"iPad6,11": "iPad 9.7 5th Gen", "iPad6,3": "iPad Pro 9.7",
"iPad6,12": "iPad 9.7 5th Gen", "iPad6,4": "iPad Pro 9.7",
"iPad7,1": "iPad Pro 12.9",
"iPad7,2": "iPad Pro 12.9",
"iPad7,3": "iPad Pro 10.5", "iPad7,3": "iPad Pro 10.5",
"iPad7,4": "iPad Pro 10.5", "iPad7,4": "iPad Pro 10.5",
"iPad7,5": "iPad 9.7 6th Gen", "iPad7,1": "iPad Pro 12.9",
"iPad7,6": "iPad 9.7 6th Gen", "iPad7,2": "iPad Pro 12.9",
"iPad7,11": "iPad 10.2 7th Gen",
"iPad7,12": "iPad 10.2 7th Gen",
"iPad8,1": "iPad Pro 11", "iPad8,1": "iPad Pro 11",
"iPad8,2": "iPad Pro 11", "iPad8,2": "iPad Pro 11",
"iPad8,3": "iPad Pro 11", "iPad8,3": "iPad Pro 11",
@@ -52,16 +72,6 @@
"iPad8,10": "iPad Pro 11", "iPad8,10": "iPad Pro 11",
"iPad8,11": "iPad Pro 12.9", "iPad8,11": "iPad Pro 12.9",
"iPad8,12": "iPad Pro 12.9", "iPad8,12": "iPad Pro 12.9",
"iPad11,1": "iPad mini 5th Gen",
"iPad11,2": "iPad mini 5th Gen",
"iPad11,3": "iPad Air 3rd Gen",
"iPad11,4": "iPad Air 3rd Gen",
"iPad11,6": "iPad 10.2 8th Gen",
"iPad11,7": "iPad 10.2 8th Gen",
"iPad12,1": "iPad 10.2 9th Gen",
"iPad12,2": "iPad 10.2 9th Gen",
"iPad13,1": "iPad Air 4th Gen",
"iPad13,2": "iPad Air 4th Gen",
"iPad13,4": "iPad Pro 11", "iPad13,4": "iPad Pro 11",
"iPad13,5": "iPad Pro 11", "iPad13,5": "iPad Pro 11",
"iPad13,6": "iPad Pro 11", "iPad13,6": "iPad Pro 11",
@@ -70,40 +80,14 @@
"iPad13,9": "iPad Pro 12.9", "iPad13,9": "iPad Pro 12.9",
"iPad13,10": "iPad Pro 12.9", "iPad13,10": "iPad Pro 12.9",
"iPad13,11": "iPad Pro 12.9", "iPad13,11": "iPad Pro 12.9",
"iPad13,16": "iPad Air M1 5th Gen",
"iPad13,17": "iPad Air M1 5th Gen",
"iPad13,18": "iPad 10.9 10th Gen",
"iPad13,19": "iPad 10.9 10th Gen",
"iPad14,1": "iPad mini 6th Gen",
"iPad14,2": "iPad mini 6th Gen",
"iPad14,3": "iPad Pro 11", "iPad14,3": "iPad Pro 11",
"iPad14,4": "iPad Pro 11", "iPad14,4": "iPad Pro 11",
"iPad14,5": "iPad Pro 12.9", "iPad14,5": "iPad Pro 12.9",
"iPad14,6": "iPad Pro 12.9", "iPad14,6": "iPad Pro 12.9",
"iPad14,8": "iPad Air M2 11",
"iPad14,9": "iPad Air M2 11",
"iPad14,10": "iPad Air M2 13",
"iPad14,11": "iPad Air M2 13",
"iPad15,3": "iPad Air M3 11",
"iPad15,4": "iPad Air M3 11",
"iPad15,5": "iPad Air M3 13",
"iPad15,6": "iPad Air M3 13",
"iPad15,7": "iPad A16 - 11th Gen",
"iPad15,8": "iPad A16 - 11th Gen",
"iPad16,1": "iPad mini A17 Pro - 7th Gen",
"iPad16,2": "iPad mini A17 Pro - 7th Gen",
"iPad16,3": "iPad Pro M4 11", "iPad16,3": "iPad Pro M4 11",
"iPad16,4": "iPad Pro M4 11", "iPad16,4": "iPad Pro M4 11",
"iPad16,5": "iPad Pro M4 13", "iPad16,5": "iPad Pro M4 13",
"iPad16,6": "iPad Pro M4 13", "iPad16,6": "iPad Pro M4 13",
"iPad16,8": "iPad Air M4 11",
"iPad16,9": "iPad Air M4 11",
"iPad16,10": "iPad Air M4 13",
"iPad16,11": "iPad Air M4 13",
"iPad17,1": "iPad Pro M5 11",
"iPad17,2": "iPad Pro M5 11",
"iPad17,3": "iPad Pro M5 13",
"iPad17,4": "iPad Pro M5 13",
"iPhone1,1": "iPhone", "iPhone1,1": "iPhone",
"iPhone1,2": "iPhone 3G", "iPhone1,2": "iPhone 3G",
"iPhone2,1": "iPhone 3GS", "iPhone2,1": "iPhone 3GS",
@@ -117,20 +101,20 @@
"iPhone5,4": "iPhone 5c", "iPhone5,4": "iPhone 5c",
"iPhone6,1": "iPhone 5s", "iPhone6,1": "iPhone 5s",
"iPhone6,2": "iPhone 5s", "iPhone6,2": "iPhone 5s",
"iPhone7,1": "iPhone 6 Plus",
"iPhone7,2": "iPhone 6", "iPhone7,2": "iPhone 6",
"iPhone7,1": "iPhone 6 Plus",
"iPhone8,1": "iPhone 6s", "iPhone8,1": "iPhone 6s",
"iPhone8,2": "iPhone 6s Plus", "iPhone8,2": "iPhone 6s Plus",
"iPhone8,4": "iPhone SE", "iPhone8,4": "iPhone SE",
"iPhone9,1": "iPhone 7", "iPhone9,1": "iPhone 7",
"iPhone9,2": "iPhone 7 Plus",
"iPhone9,3": "iPhone 7", "iPhone9,3": "iPhone 7",
"iPhone9,2": "iPhone 7 Plus",
"iPhone9,4": "iPhone 7 Plus", "iPhone9,4": "iPhone 7 Plus",
"iPhone10,1": "iPhone 8", "iPhone10,1": "iPhone 8",
"iPhone10,2": "iPhone 8 Plus",
"iPhone10,3": "iPhone X",
"iPhone10,4": "iPhone 8", "iPhone10,4": "iPhone 8",
"iPhone10,2": "iPhone 8 Plus",
"iPhone10,5": "iPhone 8 Plus", "iPhone10,5": "iPhone 8 Plus",
"iPhone10,3": "iPhone X",
"iPhone10,6": "iPhone X", "iPhone10,6": "iPhone X",
"iPhone11,2": "iPhone Xs", "iPhone11,2": "iPhone Xs",
"iPhone11,6": "iPhone Xs Max", "iPhone11,6": "iPhone Xs Max",
@@ -143,10 +127,10 @@
"iPhone13,2": "iPhone 12", "iPhone13,2": "iPhone 12",
"iPhone13,3": "iPhone 12 Pro", "iPhone13,3": "iPhone 12 Pro",
"iPhone13,4": "iPhone 12 Pro Max", "iPhone13,4": "iPhone 12 Pro Max",
"iPhone14,2": "iPhone 13 Pro",
"iPhone14,3": "iPhone 13 Pro Max",
"iPhone14,4": "iPhone 13 mini", "iPhone14,4": "iPhone 13 mini",
"iPhone14,5": "iPhone 13", "iPhone14,5": "iPhone 13",
"iPhone14,2": "iPhone 13 Pro",
"iPhone14,3": "iPhone 13 Pro Max",
"iPhone14,6": "iPhone SE", "iPhone14,6": "iPhone SE",
"iPhone14,7": "iPhone 14", "iPhone14,7": "iPhone 14",
"iPhone14,8": "iPhone 14 Plus", "iPhone14,8": "iPhone 14 Plus",
@@ -156,19 +140,6 @@
"iPhone15,5": "iPhone 15 Plus", "iPhone15,5": "iPhone 15 Plus",
"iPhone16,1": "iPhone 15 Pro", "iPhone16,1": "iPhone 15 Pro",
"iPhone16,2": "iPhone 15 Pro Max", "iPhone16,2": "iPhone 15 Pro Max",
"iPhone17,1": "iPhone 16 Pro",
"iPhone17,2": "iPhone 16 Pro Max",
"iPhone17,3": "iPhone 16",
"iPhone17,4": "iPhone 16 Plus",
"iPhone17,5": "iPhone 16e",
"iPhone18,1": "iPhone 17 Pro",
"iPhone18,2": "iPhone 17 Pro Max",
"iPhone18,3": "iPhone 17",
"iPhone18,4": "iPhone Air",
"iPhone18,5": "iPhone 17e",
"iPhone19,2": "iPhone 18 Pro",
"iPhone19,3": "iPhone 18 Pro Max",
"iPhone19,7": "iPhone 18 Pro Max",
"iPod1,1": "iPod touch Original", "iPod1,1": "iPod touch Original",
"iPod2,1": "iPod touch 2nd", "iPod2,1": "iPod touch 2nd",
"iPod3,1": "iPod touch 3rd Gen", "iPod3,1": "iPod touch 3rd Gen",
-2
View File
@@ -1137,7 +1137,6 @@ export const resourceRules = pgTable("resourceRules", {
| "COUNTRY_IS_NOT" | "COUNTRY_IS_NOT"
| "ASN" | "ASN"
| "REGION" | "REGION"
| "METHOD"
>() >()
.notNull(), // CIDR, PATH, IP .notNull(), // CIDR, PATH, IP
value: varchar("value").notNull() value: varchar("value").notNull()
@@ -1162,7 +1161,6 @@ export const resourcePolicyRules = pgTable("resourcePolicyRules", {
| "COUNTRY_IS_NOT" | "COUNTRY_IS_NOT"
| "ASN" | "ASN"
| "REGION" | "REGION"
| "METHOD"
>() >()
.notNull(), .notNull(),
value: varchar("value").notNull() value: varchar("value").notNull()
-2
View File
@@ -1409,7 +1409,6 @@ export const resourceRules = sqliteTable("resourceRules", {
| "COUNTRY_IS_NOT" | "COUNTRY_IS_NOT"
| "ASN" | "ASN"
| "REGION" | "REGION"
| "METHOD"
>() >()
.notNull(), // CIDR, PATH, IP .notNull(), // CIDR, PATH, IP
value: text("value").notNull() value: text("value").notNull()
@@ -1466,7 +1465,6 @@ export const resourcePolicyRules = sqliteTable("resourcePolicyRules", {
| "COUNTRY_IS_NOT" | "COUNTRY_IS_NOT"
| "ASN" | "ASN"
| "REGION" | "REGION"
| "METHOD"
>() >()
.notNull(), .notNull(),
value: text("value").notNull() value: text("value").notNull()
@@ -235,7 +235,7 @@ export const AlertNotification = (props: AlertNotificationProps) => {
} }
] ]
: []), : []),
{ label: "Time (UTC)", value: new Date().toUTCString() }, { label: "Time", value: new Date().toUTCString() },
...dataItems ...dataItems
]; ];
@@ -265,8 +265,8 @@ export const AlertNotification = (props: AlertNotificationProps) => {
</EmailText> </EmailText>
{isTestAlert && ( {isTestAlert && (
<EmailText> <EmailText>
This is a test alert. No action is required, and This is a test alert. No action is required,
no real event has occurred. and no real event has occurred.
</EmailText> </EmailText>
)} )}
+1 -15
View File
@@ -48,13 +48,7 @@ import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { pickPort } from "@server/routers/target/helpers"; import { pickPort } from "@server/routers/target/helpers";
import { and, asc, eq, isNotNull, ne } from "drizzle-orm"; import { and, asc, eq, isNotNull, ne } from "drizzle-orm";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
isValidCIDR,
isValidHttpMethodList,
isValidIP,
isValidUrlGlobPattern,
parseHttpMethodList
} from "../validators";
import { Config, isTargetsOnlyResource, TargetData } from "./types"; import { Config, isTargetsOnlyResource, TargetData } from "./types";
import { getOrCreateLabelIds, syncResourceLabels } from "./labels"; import { getOrCreateLabelIds, syncResourceLabels } from "./labels";
import { findOrgUsersByIdentifier } from "./findOrgUser"; import { findOrgUsersByIdentifier } from "./findOrgUser";
@@ -1459,10 +1453,6 @@ function getRuleValue(match: string, value: string) {
if (match === "COUNTRY" || match === "COUNTRY_IS_NOT") { if (match === "COUNTRY" || match === "COUNTRY_IS_NOT") {
return value.toUpperCase(); return value.toUpperCase();
} }
// normalize the method list so it is stored as "POST,PUT"
if (match === "METHOD") {
return parseHttpMethodList(value).join(",");
}
return value; return value;
} }
@@ -1483,10 +1473,6 @@ function validateRule(rule: any) {
if (!isValidRegionId(rule.value)) { if (!isValidRegionId(rule.value)) {
throw new Error(`Invalid region ID provided: ${rule.value}`); throw new Error(`Invalid region ID provided: ${rule.value}`);
} }
} else if (rule.match === "method") {
if (!isValidHttpMethodList(rule.value)) {
throw new Error(`Invalid HTTP method provided: ${rule.value}`);
}
} }
} }
+12 -16
View File
@@ -19,13 +19,7 @@ import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names"; import { getUniqueResourcePolicyName } from "@server/db/names";
import { hashPassword } from "@server/auth/password"; import { hashPassword } from "@server/auth/password";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg"; import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import { import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
isValidCIDR,
isValidHttpMethodList,
isValidIP,
isValidUrlGlobPattern,
ResourceRuleMatchType
} from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { findOrgUsersByIdentifier } from "./findOrgUser"; import { findOrgUsersByIdentifier } from "./findOrgUser";
@@ -72,13 +66,6 @@ export async function updateResourcePolicies(
throw new Error( throw new Error(
`Invalid URL glob pattern provided in resource policy '${policyNiceId}': ${rule.value}` `Invalid URL glob pattern provided in resource policy '${policyNiceId}': ${rule.value}`
); );
} else if (
rule.match === "method" &&
!isValidHttpMethodList(rule.value)
) {
throw new Error(
`Invalid HTTP method provided in resource policy '${policyNiceId}': ${rule.value}`
);
} }
} }
@@ -352,8 +339,17 @@ function getRuleAction(input: string): "ACCEPT" | "DROP" | "PASS" {
return "PASS"; return "PASS";
} }
function getRuleMatch(input: string): ResourceRuleMatchType { function getRuleMatch(
return input.toUpperCase() as ResourceRuleMatchType; input: string
): "CIDR" | "IP" | "PATH" | "COUNTRY" | "COUNTRY_IS_NOT" | "ASN" | "REGION" {
return input.toUpperCase() as
| "CIDR"
| "IP"
| "PATH"
| "COUNTRY"
| "COUNTRY_IS_NOT"
| "ASN"
| "REGION";
} }
async function syncRolePolicies( async function syncRolePolicies(
+1 -24
View File
@@ -3,7 +3,6 @@ import { existsSync } from "node:fs";
import { portRangeStringSchema } from "@server/lib/ip"; import { portRangeStringSchema } from "@server/lib/ip";
import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema"; import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema";
import { isValidRegionId } from "@server/db/regions"; import { isValidRegionId } from "@server/db/regions";
import { isValidHttpMethodList } from "@server/lib/validators";
import { wildcardSubdomainSchema } from "@server/lib/schemas"; import { wildcardSubdomainSchema } from "@server/lib/schemas";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { import {
@@ -128,16 +127,7 @@ export const AuthSchema = z.object({
export const RuleSchema = z export const RuleSchema = z
.object({ .object({
action: z.enum(["allow", "deny", "pass"]), action: z.enum(["allow", "deny", "pass"]),
match: z.enum([ match: z.enum(["cidr", "path", "ip", "country", "country_is_not", "asn", "region"]),
"cidr",
"path",
"ip",
"country",
"country_is_not",
"asn",
"region",
"method"
]),
value: z.coerce.string(), value: z.coerce.string(),
priority: z.int().optional(), priority: z.int().optional(),
enabled: z.boolean().optional().default(true) enabled: z.boolean().optional().default(true)
@@ -217,19 +207,6 @@ export const RuleSchema = z
message: message:
"Value must be a valid UN M.49 region or subregion ID when match is 'region'" "Value must be a valid UN M.49 region or subregion ID when match is 'region'"
} }
)
.refine(
(rule) => {
if (rule.match === "method") {
return isValidHttpMethodList(rule.value);
}
return true;
},
{
path: ["value"],
message:
"Value must be a comma-separated list of HTTP methods when match is 'method', e.g. 'POST,PUT'"
}
); );
export const HeaderSchema = z.object({ export const HeaderSchema = z.object({
+2 -41
View File
@@ -1,10 +1,9 @@
import { import {
getResourceRuleValueValidationError, getResourceRuleValueValidationError,
isValidDomain, isValidDomain,
isValidUrlGlobPattern, isValidUrlGlobPattern
parseHttpMethodList
} from "./validators"; } from "./validators";
import { assertEquals, assertEqualsObj } from "@test/assert"; import { assertEquals } from "@test/assert";
function runTests() { function runTests() {
console.log("Running domain validation tests..."); console.log("Running domain validation tests...");
@@ -296,44 +295,6 @@ function runTests() {
"Invalid ASN should return an error" "Invalid ASN should return an error"
); );
// HTTP method validation tests
assertEquals(
getResourceRuleValueValidationError("METHOD", "POST"),
null,
"Single HTTP method should be valid"
);
assertEquals(
getResourceRuleValueValidationError("METHOD", " post , Put "),
null,
"Method list should be valid with mixed case and whitespace"
);
assertEquals(
getResourceRuleValueValidationError("METHOD", "PROPFIND"),
null,
"Extension methods such as the WebDAV verbs should be valid"
);
assertEquals(
getResourceRuleValueValidationError("METHOD", ""),
"Invalid HTTP method provided",
"Empty method list should return an error"
);
assertEquals(
getResourceRuleValueValidationError("METHOD", ",,"),
"Invalid HTTP method provided",
"Method list of only separators should return an error"
);
assertEquals(
getResourceRuleValueValidationError("METHOD", "GET POST"),
"Invalid HTTP method provided",
"Space separated methods should return an error"
);
assertEqualsObj(
parseHttpMethodList(" get ,post, "),
["GET", "POST"],
"Method list should be normalized to uppercase without empty entries"
);
console.log("All tests passed!"); console.log("All tests passed!");
} }
+1 -42
View File
@@ -76,46 +76,9 @@ export const RESOURCE_RULE_MATCH_TYPES = [
"COUNTRY", "COUNTRY",
"COUNTRY_IS_NOT", "COUNTRY_IS_NOT",
"ASN", "ASN",
"REGION", "REGION"
"METHOD"
] as const; ] as const;
// The methods offered in the UI: the eight from RFC 9110 plus PATCH (RFC 5789)
// and QUERY (RFC 10008). A METHOD rule is not limited to these, since
// isValidHttpMethodList accepts any method token, so blueprints and the API can
// also target extension methods such as the WebDAV verbs.
export const HTTP_METHODS = [
"GET",
"HEAD",
"POST",
"PUT",
"PATCH",
"DELETE",
"OPTIONS",
"TRACE",
"CONNECT",
"QUERY"
] as const;
// RFC 9110 token, minus the characters that would collide with the
// comma-separated list encoding.
const HTTP_METHOD_REGEX = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
export function parseHttpMethodList(value: string): string[] {
return value
.split(",")
.map((method) => method.trim().toUpperCase())
.filter((method) => method.length > 0);
}
export function isValidHttpMethodList(value: string): boolean {
const methods = parseHttpMethodList(value);
return (
methods.length > 0 &&
methods.every((method) => HTTP_METHOD_REGEX.test(method))
);
}
export type ResourceRuleMatchType = (typeof RESOURCE_RULE_MATCH_TYPES)[number]; export type ResourceRuleMatchType = (typeof RESOURCE_RULE_MATCH_TYPES)[number];
export function getResourceRuleValueValidationError( export function getResourceRuleValueValidationError(
@@ -138,10 +101,6 @@ export function getResourceRuleValueValidationError(
return COUNTRIES.some((country) => country.code === value) return COUNTRIES.some((country) => country.code === value)
? null ? null
: "Invalid country code provided"; : "Invalid country code provided";
case "METHOD":
return isValidHttpMethodList(value)
? null
: "Invalid HTTP method provided";
case "ASN": case "ASN":
const normalizedValue = value.trim().toUpperCase(); const normalizedValue = value.trim().toUpperCase();
return /^AS\d+$/.test(normalizedValue) || return /^AS\d+$/.test(normalizedValue) ||
+2 -18
View File
@@ -40,7 +40,6 @@ import {
import config from "@server/lib/config"; import config from "@server/lib/config";
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip"; import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
import { isPathAllowed } from "@server/lib/pathMatch"; import { isPathAllowed } from "@server/lib/pathMatch";
import { parseHttpMethodList } from "@server/lib/validators";
import { response } from "@server/lib/response"; import { response } from "@server/lib/response";
import logger from "@server/logger"; import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
@@ -164,7 +163,6 @@ export async function verifyResourceSession(
path, path,
headers, headers,
query, query,
method,
badgerVersion badgerVersion
} = parsedBody.data; } = parsedBody.data;
@@ -295,8 +293,7 @@ export async function verifyResourceSession(
clientIp, clientIp,
path, path,
ipCC, ipCC,
ipAsn, ipAsn
method
); );
if (action == "ACCEPT") { if (action == "ACCEPT") {
@@ -1432,8 +1429,7 @@ async function checkRules(
clientIp: string | undefined, clientIp: string | undefined,
path: string | undefined, path: string | undefined,
ipCC?: string, ipCC?: string,
ipAsn?: number, ipAsn?: number
method?: string
): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> { ): Promise<"ACCEPT" | "DROP" | "PASS" | undefined> {
const ruleCacheKey = `rules:${resourceId}`; const ruleCacheKey = `rules:${resourceId}`;
@@ -1508,24 +1504,12 @@ async function checkRules(
(await isIpInRegion(ipCC, rule.value)) (await isIpInRegion(ipCC, rule.value))
) { ) {
return rule.action as any; return rule.action as any;
} else if (
method &&
rule.match == "METHOD" &&
isMethodAllowed(rule.value, method)
) {
return rule.action as any;
} }
} }
return; return;
} }
// rule.value holds a comma-separated list of HTTP methods, e.g. "POST,PUT".
function isMethodAllowed(ruleValue: string, method: string): boolean {
const requestMethod = method.toUpperCase();
return parseHttpMethodList(ruleValue).includes(requestMethod);
}
export { isPathAllowed }; export { isPathAllowed };
async function isIpInGeoIP( async function isIpInGeoIP(
-24
View File
@@ -1,24 +0,0 @@
import { sendToClient } from "#dynamic/routers/ws";
// Error codes for registration failures
export const NewtErrorCodes = {
NO_AVAILABLE_SUBNET: {
code: "NO_AVAILABLE_SUBNET",
message:
"No available subnet could be assigned to this site on its exit node. Please contact your administrator to increase the available address space for this exit node's subnet."
}
} as const;
// Helper function to send registration error
export async function sendNewtError(
error: (typeof NewtErrorCodes)[keyof typeof NewtErrorCodes],
newtId: string
) {
sendToClient(newtId, {
type: "newt/error",
data: {
code: error.code,
message: error.message
}
});
}
@@ -14,7 +14,6 @@ import { getUniqueSubnetForExitNode } from "@server/lib/exitNodes";
import { fetchContainers } from "./dockerSocket"; import { fetchContainers } from "./dockerSocket";
import { buildTargetConfigurationForNewtClient } from "./buildConfiguration"; import { buildTargetConfigurationForNewtClient } from "./buildConfiguration";
import { canCompress } from "@server/lib/clientVersionChecks"; import { canCompress } from "@server/lib/clientVersionChecks";
import { NewtErrorCodes, sendNewtError } from "./error";
export const handleNewtRegisterMessage: MessageHandler = async (context) => { export const handleNewtRegisterMessage: MessageHandler = async (context) => {
const { message, client, sendToClient } = context; const { message, client, sendToClient } = context;
@@ -117,7 +116,6 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
logger.error( logger.error(
`No available subnets found for the new exit node id ${exitNodeId} and site id ${siteId}` `No available subnets found for the new exit node id ${exitNodeId} and site id ${siteId}`
); );
sendNewtError(NewtErrorCodes.NO_AVAILABLE_SUBNET, newt.newtId);
return; return;
} }
-5
View File
@@ -94,11 +94,6 @@ export const OlmErrorCodes = {
HOLEPUNCH_MISSING: { HOLEPUNCH_MISSING: {
code: "HOLEPUNCH_MISSING", code: "HOLEPUNCH_MISSING",
message: `Unable to coordinate client P2P connection. Please ensure your client can reach the server on UDP port ${udpPort} and try registering again.` message: `Unable to coordinate client P2P connection. Please ensure your client can reach the server on UDP port ${udpPort} and try registering again.`
},
NO_AVAILABLE_SUBNET: {
code: "NO_AVAILABLE_SUBNET",
message:
"No available subnet could be assigned to this client on the selected exit node. Please contact your administrator to increase the available address space for this exit node's subnet."
} }
} as const; } as const;
@@ -347,7 +347,6 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
`[handleOlmRegisterMessage] No available subnets found for exit node id ${exitNodeId} and client id ${client.clientId}`, `[handleOlmRegisterMessage] No available subnets found for exit node id ${exitNodeId} and client id ${client.clientId}`,
{ orgId: client.orgId, clientId: client.clientId } { orgId: client.orgId, clientId: client.clientId }
); );
sendOlmError(OlmErrorCodes.NO_AVAILABLE_SUBNET, olm.olmId);
return; return;
} }
+10 -21
View File
@@ -122,20 +122,11 @@ export default async function ResourceAuthPage(props: {
if (searchParams.redirect) { if (searchParams.redirect) {
try { try {
const redirectTarget = new URL(searchParams.redirect);
const serverResourceHost = new URL(authInfo.url).host; const serverResourceHost = new URL(authInfo.url).host;
const redirectHost = redirectTarget.host; const redirectHost = new URL(searchParams.redirect).host;
const redirectPort = redirectTarget.port; const redirectPort = new URL(searchParams.redirect).port;
const serverResourceHostWithPort = `${serverResourceHost}:${redirectPort}`; const serverResourceHostWithPort = `${serverResourceHost}:${redirectPort}`;
// URL parses a host out of any scheme that uses "//", so a target
// like javascript://resource-host/... matches the comparisons
// below. The target is later assigned to window.location, so only
// http(s) is accepted here.
const isHttpTarget =
redirectTarget.protocol === "http:" ||
redirectTarget.protocol === "https:";
const wildcardMatchesRedirect = ( const wildcardMatchesRedirect = (
wildcardDomain: string, wildcardDomain: string,
host: string host: string
@@ -145,16 +136,14 @@ export default async function ResourceAuthPage(props: {
return host.endsWith(suffix) && host.length > suffix.length; return host.endsWith(suffix) && host.length > suffix.length;
}; };
if ( if (serverResourceHost === redirectHost) {
isHttpTarget && redirectUrl = searchParams.redirect;
(serverResourceHost === redirectHost || } else if (serverResourceHostWithPort === redirectHost) {
serverResourceHostWithPort === redirectHost || redirectUrl = searchParams.redirect;
(authInfo.wildcard && } else if (
authInfo.wildcard &&
authInfo.fullDomain && authInfo.fullDomain &&
wildcardMatchesRedirect( wildcardMatchesRedirect(authInfo.fullDomain, redirectHost)
authInfo.fullDomain,
redirectHost
)))
) { ) {
redirectUrl = searchParams.redirect; redirectUrl = searchParams.redirect;
} }
@@ -294,7 +283,7 @@ export default async function ResourceAuthPage(props: {
loginIdps = idpsRes.data.data.idps.map((idp) => ({ loginIdps = idpsRes.data.data.idps.map((idp) => ({
idpId: idp.idpId, idpId: idp.idpId,
name: idp.name, name: idp.name,
variant: idp.variant ?? idp.type variant: idp.type
})) as LoginFormIDP[]; })) as LoginFormIDP[];
} }
@@ -37,7 +37,6 @@ import { cn } from "@app/lib/cn";
import { MAJOR_ASNS } from "@server/db/asns"; import { MAJOR_ASNS } from "@server/db/asns";
import { COUNTRIES } from "@server/db/countries"; import { COUNTRIES } from "@server/db/countries";
import { REGIONS, getRegionNameById } from "@server/db/regions"; import { REGIONS, getRegionNameById } from "@server/db/regions";
import { HTTP_METHODS, parseHttpMethodList } from "@server/lib/validators";
import { import {
ColumnDef, ColumnDef,
flexRender, flexRender,
@@ -64,8 +63,7 @@ import {
} from "react"; } from "react";
import { import {
validatePolicyRulePriority, validatePolicyRulePriority,
validatePolicyRuleValue, validatePolicyRuleValue
type PolicyRuleMatchType
} from "./policy-access-rule-validation"; } from "./policy-access-rule-validation";
import { import {
buildDisplayPrioritiesForResourceOverlay, buildDisplayPrioritiesForResourceOverlay,
@@ -114,80 +112,6 @@ function getColumnClassName(columnId: string) {
return ""; return "";
} }
// A METHOD rule stores its methods as a comma-separated list in rule.value,
// e.g. "POST,PUT". Only the common methods are offered here; a value set
// through a blueprint or the API may contain other methods (the WebDAV verbs,
// for instance), so those are kept and shown rather than dropped on edit.
function RuleMethodSelect({
value,
disabled,
placeholder,
onChange
}: {
value: string;
disabled: boolean;
placeholder: string;
onChange: (value: string) => void;
}) {
const selected = parseHttpMethodList(value);
const knownMethods: readonly string[] = HTTP_METHODS;
const options = [
...knownMethods,
...selected.filter((method) => !knownMethods.includes(method))
];
function toggle(method: string) {
const next = selected.includes(method)
? selected.filter((m) => m !== method)
: [...selected, method];
// keep a stable order so the stored value does not churn on every edit
onChange(options.filter((m) => next.includes(m)).join(","));
}
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
disabled={disabled}
className="w-full min-w-0 justify-between"
>
<span className="truncate">
{selected.length > 0 ? selected.join(", ") : placeholder}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="min-w-50 p-0">
<Command>
<CommandList>
<CommandGroup>
{options.map((method) => (
<CommandItem
key={method}
value={method}
onSelect={() => toggle(method)}
>
<Check
className={`mr-2 h-4 w-4 ${
selected.includes(method)
? "opacity-100"
: "opacity-0"
}`}
/>
{method}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
);
}
export function PolicyAccessRulesTable({ export function PolicyAccessRulesTable({
rules, rules,
onRulesChange, onRulesChange,
@@ -309,8 +233,7 @@ export function PolicyAccessRulesTable({
COUNTRY: t("country"), COUNTRY: t("country"),
COUNTRY_IS_NOT: t("countryIsNot"), COUNTRY_IS_NOT: t("countryIsNot"),
ASN: "ASN", ASN: "ASN",
REGION: t("region"), REGION: t("region")
METHOD: t("method")
}), }),
[t] [t]
); );
@@ -515,7 +438,16 @@ export function PolicyAccessRulesTable({
<Select <Select
defaultValue={row.original.match} defaultValue={row.original.match}
disabled={readonly || isRuleLocked(row.original)} disabled={readonly || isRuleLocked(row.original)}
onValueChange={(value: PolicyRuleMatchType) => onValueChange={(
value:
| "CIDR"
| "IP"
| "PATH"
| "COUNTRY"
| "COUNTRY_IS_NOT"
| "ASN"
| "REGION"
) =>
updateRule(row.original.ruleId, { updateRule(row.original.ruleId, {
match: value, match: value,
value: value:
@@ -526,8 +458,6 @@ export function PolicyAccessRulesTable({
? "AS15169" ? "AS15169"
: value === "REGION" : value === "REGION"
? "021" ? "021"
: value === "METHOD"
? "GET"
: row.original.value : row.original.value
}) })
} }
@@ -543,9 +473,6 @@ export function PolicyAccessRulesTable({
<SelectItem value="CIDR"> <SelectItem value="CIDR">
{RuleMatch.CIDR} {RuleMatch.CIDR}
</SelectItem> </SelectItem>
<SelectItem value="METHOD">
{RuleMatch.METHOD}
</SelectItem>
{isMaxmindAvailable && ( {isMaxmindAvailable && (
<> <>
<SelectItem value="COUNTRY"> <SelectItem value="COUNTRY">
@@ -852,15 +779,6 @@ export function PolicyAccessRulesTable({
</Command> </Command>
</PopoverContent> </PopoverContent>
</Popover> </Popover>
) : row.original.match === "METHOD" ? (
<RuleMethodSelect
value={row.original.value}
disabled={readonly || isRuleLocked(row.original)}
placeholder={t("rulesSelectMethods")}
onChange={(value) =>
updateRule(row.original.ruleId, { value })
}
/>
) : ( ) : (
<Input <Input
defaultValue={row.original.value} defaultValue={row.original.value}
@@ -2,7 +2,6 @@ import { COUNTRIES } from "@server/db/countries";
import { isValidRegionId } from "@server/db/regions"; import { isValidRegionId } from "@server/db/regions";
import { import {
isValidCIDR, isValidCIDR,
isValidHttpMethodList,
isValidIP, isValidIP,
isValidUrlGlobPattern isValidUrlGlobPattern
} from "@server/lib/validators"; } from "@server/lib/validators";
@@ -20,8 +19,7 @@ export const POLICY_RULE_MATCH_TYPES = [
"COUNTRY", "COUNTRY",
"COUNTRY_IS_NOT", "COUNTRY_IS_NOT",
"ASN", "ASN",
"REGION", "REGION"
"METHOD"
] as const; ] as const;
export type PolicyRuleMatchType = (typeof POLICY_RULE_MATCH_TYPES)[number]; export type PolicyRuleMatchType = (typeof POLICY_RULE_MATCH_TYPES)[number];
@@ -86,10 +84,6 @@ export function createPolicyRuleValueSchema(t: TranslateFn, match: string) {
(value) => COUNTRIES.some((country) => country.code === value), (value) => COUNTRIES.some((country) => country.code === value),
{ message: t("rulesErrorInvalidCountryDescription") } { message: t("rulesErrorInvalidCountryDescription") }
); );
case "METHOD":
return required.refine(isValidHttpMethodList, {
message: t("rulesErrorInvalidMethodDescription")
});
case "ASN": case "ASN":
return required.refine( return required.refine(
(value) => { (value) => {