Compare commits

...

20 Commits

Author SHA1 Message Date
dependabot[bot]
896e887cc7 Bump @dotenvx/dotenvx in the dev-minor-updates group
Bumps the dev-minor-updates group with 1 update: [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx).


Updates `@dotenvx/dotenvx` from 1.69.1 to 1.70.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/v1.69.1...v1.70.0)

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.70.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 02:57:20 +00:00
Owen Schwartz
ebe1c7a297 Improve OpenAPI response payload typing for Swagger data schemas (#3102)
* Fix custom parser OpenAPI types and add structured default response schema

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Document all registerPath responses and normalize OpenAPI parser schemas

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Add concrete OpenAPI data schemas for selected routes

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Reformat generated OpenAPI response schemas for readability

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Remove obsolete stoi import from blueprint OpenAPI route

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-31 11:10:38 -07:00
Shlee
b0d1291cff Installer: Bootstrap optional PostgreSQL/Redis (#3152)
* Make optional postgres and redis in installer
2026-05-29 09:43:59 -07:00
Owen Schwartz
1215aa8122 Merge pull request #3184 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-1701004488
Bump the prod-minor-updates group with 9 updates
2026-05-28 20:36:43 -07:00
Owen Schwartz
d318a756a8 Merge pull request #3183 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-60744307c2
Bump the dev-patch-updates group with 4 updates
2026-05-28 20:36:17 -07:00
Owen Schwartz
b3c1e49c0c Merge pull request #3185 from fosrl/dependabot/npm_and_yarn/stripe-22.2.0
Bump stripe from 20.4.1 to 22.2.0
2026-05-28 20:35:52 -07:00
Owen Schwartz
dc12b00502 Merge pull request #3186 from fosrl/dependabot/npm_and_yarn/lucide-react-1.17.0
Bump lucide-react from 0.577.0 to 1.17.0
2026-05-28 20:35:39 -07:00
Owen Schwartz
1e27acbf88 Merge pull request #2980 from rinseaid/blueprint-auto-create-roles
Auto-create roles referenced in blueprints
2026-05-28 20:10:53 -07:00
dependabot[bot]
4012cc658d Bump lucide-react from 0.577.0 to 1.17.0
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.577.0 to 1.17.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.17.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:44:09 +00:00
dependabot[bot]
84d7a87609 Bump stripe from 20.4.1 to 22.2.0
Bumps [stripe](https://github.com/stripe/stripe-node) from 20.4.1 to 22.2.0.
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v20.4.1...v22.2.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-version: 22.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:43:42 +00:00
dependabot[bot]
9a92be532a Bump the prod-minor-updates group with 9 updates
Bumps the prod-minor-updates group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1047.0` | `3.1056.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.2.2` | `5.4.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.1.0` | `8.2.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.10.1` | `5.11.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.12.0` | `4.13.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.20.0` | `8.21.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.34.1` | `5.35.6` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.75.0` | `7.76.1` |
| [ws](https://github.com/websockets/ws) | `8.20.1` | `8.21.0` |


Updates `@aws-sdk/client-s3` from 3.1047.0 to 3.1056.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.1056.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.2.2 to 5.4.0
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.2.2...v5.4.0)

Updates `helmet` from 8.1.0 to 8.2.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.1.0...v8.2.0)

Updates `ioredis` from 5.10.1 to 5.11.0
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.10.1...v5.11.0)

Updates `next-intl` from 4.12.0 to 4.13.0
- [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.12.0...v4.13.0)

Updates `pg` from 8.20.0 to 8.21.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.21.0/packages/pg)

Updates `posthog-node` from 5.34.1 to 5.35.6
- [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.35.6/packages/node)

Updates `react-hook-form` from 7.75.0 to 7.76.1
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.75.0...v7.76.1)

Updates `ws` from 8.20.1 to 8.21.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.20.1...8.21.0)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1056.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: helmet
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ioredis
  dependency-version: 5.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: pg
  dependency-version: 8.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.35.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.76.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ws
  dependency-version: 8.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:43:25 +00:00
dependabot[bot]
18ac542e30 Bump the dev-patch-updates group with 4 updates
Bumps the dev-patch-updates group with 4 updates: [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [postcss](https://github.com/postcss/postcss) and [tsx](https://github.com/privatenumber/tsx).


Updates `@tanstack/react-query-devtools` from 5.100.10 to 5.100.14
- [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.100.14/packages/react-query-devtools)

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

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

Updates `tsx` from 4.22.0 to 4.22.3
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.0...v4.22.3)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.100.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: postcss
  dependency-version: 8.5.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tsx
  dependency-version: 4.22.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:36:20 +00:00
Owen Schwartz
c74b423bae Merge pull request #3119 from Adityakk9031/#3086
Sort resource filter options in audit logs
2026-05-28 15:50:27 -07:00
Aditya kumar singh
73e9e830c3 Sort resource filter options in audit logs 2026-05-20 11:13:50 +05:30
copilot-swe-agent[bot]
81ed391efb Remove obsolete stoi import from blueprint OpenAPI route
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:30:23 +00:00
copilot-swe-agent[bot]
f3bee70c23 Reformat generated OpenAPI response schemas for readability
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:28:39 +00:00
copilot-swe-agent[bot]
15a9eb28d9 Add concrete OpenAPI data schemas for selected routes
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:25:53 +00:00
copilot-swe-agent[bot]
a0a093ed0b Document all registerPath responses and normalize OpenAPI parser schemas
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 06:43:10 +00:00
copilot-swe-agent[bot]
9cec711427 Fix custom parser OpenAPI types and add structured default response schema
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 06:38:44 +00:00
rinseaid
4786fc3a31 Auto-create roles referenced in blueprints
When a blueprint references a role that doesn't exist, create it
automatically with default permissions (getOrg, getResource,
listResources) instead of throwing an error or silently dropping
the association.
2026-05-03 13:37:47 -04:00
191 changed files with 3420 additions and 760 deletions

View File

@@ -37,3 +37,8 @@ flags:
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
{{if .IsPostgreSQL}}
postgres:
connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin
{{end}}

View File

@@ -1,7 +1,7 @@
name: pangolin
services:
pangolin:
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{if .IsPostgreSQL}}postgresql-{{end}}{{.PangolinVersion}}
container_name: pangolin
restart: unless-stopped
deploy:
@@ -10,6 +10,20 @@ services:
memory: 1g
reservations:
memory: 256m
{{if or .IsPostgreSQL .IsRedis}}
depends_on:
{{if .IsPostgreSQL}}
postgres:
condition: service_healthy
{{end}}
{{if .IsRedis}}
redis:
condition: service_healthy
{{end}}
networks:
- default
- backend
{{end}}
volumes:
- ./config:/app/config
healthcheck:
@@ -60,8 +74,56 @@ services:
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
{{if .IsPostgreSQL}}
postgres:
image: postgres:18
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: pangolin
POSTGRES_PASSWORD: {{.IsPostgreSQLPass}}
POSTGRES_DB: pangolin
volumes:
- ./postgres18:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pangolin"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
{{end}}
{{if .IsRedis}}
redis:
image: redis:8-trixie
container_name: redis
restart: unless-stopped
command: >
redis-server
--save 3600 1000
--appendonly yes
--requirepass {{.IsRedisPass}}
volumes:
- ./redis8:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "{{.IsRedisPass}}", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
networks:
- backend
{{end}}
networks:
default:
driver: bridge
name: pangolin
name: pangolin_frontend
{{if .EnableIPv6}} enable_ipv6: true{{end}}
{{if or .IsPostgreSQL .IsRedis}}
backend:
driver: bridge
name: pangolin_backend
internal: true
{{end}}

View File

@@ -0,0 +1,6 @@
{{if .IsRedis}}
redis:
host: "redis"
port: 6379
password: "{{.IsRedisPass}}"
{{end}}

View File

@@ -57,6 +57,10 @@ type Config struct {
EnableMaxMind bool
Secret string
IsEnterprise bool
IsPostgreSQL bool
IsPostgreSQLPass string
IsRedis bool
IsRedisPass string
}
type SupportedContainer string
@@ -486,6 +490,17 @@ func collectUserInput() Config {
fmt.Println("\n=== Basic Configuration ===")
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
if config.IsEnterprise {
config.IsRedis = readBool("Do you want to run the Redis containers locally? Required for HA.")
if config.IsRedis {
config.IsRedisPass = readPassword("Enter a unique password for the Redis service.")
}
}
config.IsPostgreSQL = readBool("Do you want to run the PostgreSQL containers locally? Otherwise, default to the local SQLite database only.", false)
if config.IsPostgreSQL {
config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.")
}
config.BaseDomain = readString("Enter your base domain (no subdomain e.g. example.com)", "")

814
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -33,10 +33,10 @@
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "8.5.0",
"@aws-sdk/client-s3": "3.1047.0",
"@aws-sdk/client-s3": "3.1056.0",
"@faker-js/faker": "10.4.0",
"@headlessui/react": "2.2.10",
"@hookform/resolvers": "5.2.2",
"@hookform/resolvers": "5.4.0",
"@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2",
"@oslojs/crypto": "1.0.1",
@@ -83,37 +83,37 @@
"express": "5.2.1",
"express-rate-limit": "8.5.2",
"glob": "13.0.6",
"helmet": "8.1.0",
"helmet": "8.2.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.10.1",
"ioredis": "5.11.0",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "0.577.0",
"lucide-react": "1.17.0",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "16.2.6",
"next-intl": "4.12.0",
"next-intl": "4.13.0",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "8.0.9",
"oslo": "1.2.1",
"pg": "8.20.0",
"posthog-node": "5.34.1",
"pg": "8.21.0",
"posthog-node": "5.35.6",
"qrcode.react": "4.2.0",
"react": "19.2.6",
"react-day-picker": "9.14.0",
"react-dom": "19.2.6",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.75.0",
"react-hook-form": "7.76.1",
"react-icons": "5.6.0",
"recharts": "3.8.1",
"reodotdev": "1.1.0",
"semver": "7.8.1",
"sshpk": "1.18.0",
"stripe": "20.4.1",
"stripe": "22.2.0",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0",
"topojson-client": "3.1.0",
@@ -124,18 +124,18 @@
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.20.1",
"ws": "8.21.0",
"yaml": "2.9.0",
"yargs": "18.0.0",
"zod": "4.4.3",
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.69.1",
"@dotenvx/dotenvx": "1.70.0",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/ui": "^6.5.0",
"@tailwindcss/postcss": "4.3.0",
"@tanstack/react-query-devtools": "5.100.10",
"@tanstack/react-query-devtools": "5.100.14",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
@@ -150,7 +150,7 @@
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.20.0",
"@types/react": "19.2.14",
"@types/react": "19.2.15",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "1.17.4",
@@ -164,18 +164,18 @@
"esbuild-node-externals": "1.22.0",
"eslint": "10.4.0",
"eslint-config-next": "16.2.6",
"postcss": "8.5.14",
"postcss": "8.5.15",
"prettier": "3.8.3",
"react-email": "6.5.0",
"tailwindcss": "4.3.0",
"tsc-alias": "1.8.17",
"tsx": "4.22.0",
"tsx": "4.22.3",
"typescript": "6.0.3",
"typescript-eslint": "8.60.0"
},
"overrides": {
"esbuild": "0.28.0",
"dompurify": "3.4.0",
"postcss": "8.5.14"
"postcss": "8.5.15"
}
}

View File

@@ -152,11 +152,17 @@ function getOpenApiDocumentation() {
if (!hasExistingResponses) {
def.route.responses = {
"*": {
description: "",
"200": {
description: "Successful response",
content: {
"application/json": {
schema: z.object({})
schema: z.object({
data: z.unknown().nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}

View File

@@ -3,6 +3,7 @@ import {
clientSiteResources,
domains,
orgDomains,
roleActions,
roles,
roleSiteResources,
Site,
@@ -19,6 +20,7 @@ import { sites } from "@server/db";
import { eq, and, ne, inArray, or, isNotNull } from "drizzle-orm";
import { Config } from "./types";
import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { getNextAvailableAliasAddress } from "../ip";
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
@@ -332,8 +334,7 @@ export async function updateClientResources(
}
if (resourceData.roles.length > 0) {
// Re-add specified roles but we need to get the roleIds from the role name in the array
const rolesToUpdate = await trx
const existingRoles = await trx
.select()
.from(roles)
.where(
@@ -343,7 +344,28 @@ export async function updateClientResources(
)
);
const roleIds = rolesToUpdate.map((role) => role.roleId);
const foundNames = new Set(existingRoles.map((r) => r.name));
const missingNames = resourceData.roles.filter(
(n) => !foundNames.has(n)
);
for (const name of missingNames) {
const [created] = await trx
.insert(roles)
.values({ name, orgId })
.returning();
await trx.insert(roleActions).values(
defaultRoleAllowedActions.map((action) => ({
roleId: created.roleId,
actionId: action,
orgId
}))
);
existingRoles.push(created);
logger.info(`Auto-created role "${name}" in org ${orgId} from blueprint`);
}
const roleIds = existingRoles.map((role) => role.roleId);
await trx
.insert(roleSiteResources)
@@ -444,8 +466,7 @@ export async function updateClientResources(
});
if (resourceData.roles.length > 0) {
// get roleIds from role names
const rolesToUpdate = await trx
const existingRoles = await trx
.select()
.from(roles)
.where(
@@ -455,7 +476,28 @@ export async function updateClientResources(
)
);
const roleIds = rolesToUpdate.map((role) => role.roleId);
const foundNames = new Set(existingRoles.map((r) => r.name));
const missingNames = resourceData.roles.filter(
(n) => !foundNames.has(n)
);
for (const name of missingNames) {
const [created] = await trx
.insert(roles)
.values({ name, orgId })
.returning();
await trx.insert(roleActions).values(
defaultRoleAllowedActions.map((action) => ({
roleId: created.roleId,
actionId: action,
orgId
}))
);
existingRoles.push(created);
logger.info(`Auto-created role "${name}" in org ${orgId} from blueprint`);
}
const roleIds = existingRoles.map((role) => role.roleId);
await trx
.insert(roleSiteResources)

View File

@@ -8,6 +8,7 @@ import {
resourcePincode,
resourceRules,
resourceWhitelist,
roleActions,
roleResources,
roles,
Target,
@@ -36,6 +37,7 @@ import { isValidRegionId } from "@server/db/regions";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { fireHealthCheckUnknownAlert } from "@server/lib/alerts";
import { tierMatrix } from "../billing/tierMatrix";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -925,14 +927,26 @@ async function syncRoleResources(
.where(eq(roleResources.resourceId, resourceId));
for (const roleName of ssoRoles) {
const [role] = await trx
let [role] = await trx
.select()
.from(roles)
.where(and(eq(roles.name, roleName), eq(roles.orgId, orgId)))
.limit(1);
if (!role) {
throw new Error(`Role not found: ${roleName} in org ${orgId}`);
const [created] = await trx
.insert(roles)
.values({ name: roleName, orgId })
.returning();
await trx.insert(roleActions).values(
defaultRoleAllowedActions.map((action) => ({
roleId: created.roleId,
actionId: action,
orgId
}))
);
role = created;
logger.info(`Auto-created role "${roleName}" in org ${orgId} from blueprint`);
}
if (role.isAdmin) {

View File

@@ -873,7 +873,13 @@ export const portRangeStringSchema = z
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.'
}
);
)
.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

View File

@@ -0,0 +1,11 @@
import { z } from "zod";
export function createApiResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
return z.object({
data: dataSchema.nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
});
}

View File

@@ -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(

View File

@@ -38,7 +38,22 @@ registry.registerPath({
request: {
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(

View File

@@ -49,7 +49,22 @@ registry.registerPath({
request: {
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(

View File

@@ -95,7 +95,22 @@ registry.registerPath({
query: querySchema,
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(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import {
alertRules,
@@ -148,6 +149,10 @@ const bodySchema = z
export type UpdateAlertRuleResponse = {
alertRuleId: number;
};
const UpdateAlertRuleResponseDataSchema = z.object({
alertRuleId: z.number()
});
registry.registerPath({
method: "post",
@@ -164,7 +169,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateAlertRuleResponseDataSchema)
}
}
}
}
});
export async function updateAlertRule(

View File

@@ -24,7 +24,7 @@ import type { NextFunction, Request, Response } from "express";
const paramsSchema = z.strictObject({
orgId: z.string(),
approvalId: z.string().transform(Number).pipe(z.int().positive())
approvalId: z.coerce.number().int().positive()
});
const bodySchema = z.strictObject({

View File

@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import logger from "@server/logger";
import {
queryAccessAuditLogsParams,
@@ -37,7 +38,22 @@ registry.registerPath({
query: queryAccessAuditLogsQuery,
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(

View File

@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import logger from "@server/logger";
import {
queryActionAuditLogsParams,
@@ -37,7 +38,22 @@ registry.registerPath({
query: queryActionAuditLogsQuery,
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(

View File

@@ -18,6 +18,7 @@ import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import logger from "@server/logger";
import {
queryConnectionAuditLogsParams,
@@ -37,7 +38,22 @@ registry.registerPath({
query: queryConnectionAuditLogsQuery,
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(

View File

@@ -93,6 +93,20 @@ export const queryAccessAuditLogsCombined = queryAccessAuditLogsQuery.merge(
);
type Q = z.infer<typeof queryAccessAuditLogsCombined>;
function sortNamedFilterOptions<T extends { id: number; name: string | null }>(
items: T[]
): T[] {
return [...items].sort((a, b) => {
const nameA = a.name ?? "";
const nameB = b.name ?? "";
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return a.id - b.id;
});
}
function getWhere(data: Q) {
return and(
gt(accessAuditLog.timestamp, data.timeStart),
@@ -308,7 +322,7 @@ async function queryUniqueFilterAttributes(
actors: uniqueActors
.map((row) => row.actor)
.filter((actor): actor is string => actor !== null),
resources: resourcesWithNames,
resources: sortNamedFilterOptions(resourcesWithNames),
locations: uniqueLocations
.map((row) => row.locations)
.filter((location): location is string => location !== null)
@@ -324,7 +338,22 @@ registry.registerPath({
query: queryAccessAuditLogsQuery,
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(

View File

@@ -165,7 +165,22 @@ registry.registerPath({
query: queryActionAuditLogsQuery,
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(

View File

@@ -107,6 +107,20 @@ export const queryConnectionAuditLogsCombined =
queryConnectionAuditLogsQuery.merge(queryConnectionAuditLogsParams);
type Q = z.infer<typeof queryConnectionAuditLogsCombined>;
function sortNamedFilterOptions<T extends { id: number; name: string | null }>(
items: T[]
): T[] {
return [...items].sort((a, b) => {
const nameA = a.name ?? "";
const nameB = b.name ?? "";
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return a.id - b.id;
});
}
function getWhere(data: Q) {
return and(
gt(connectionAuditLog.startedAt, data.timeStart),
@@ -425,7 +439,7 @@ async function queryUniqueFilterAttributes(
.map((row) => row.destAddr)
.filter((addr): addr is string => addr !== null),
clients: clientsWithNames,
resources: resourcesWithNames,
resources: sortNamedFilterOptions(resourcesWithNames),
users: usersWithEmails
};
}
@@ -439,7 +453,22 @@ registry.registerPath({
query: queryConnectionAuditLogsQuery,
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(

View File

@@ -39,7 +39,22 @@ const getOrgSchema = z.strictObject({
// request: {
// 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(

View File

@@ -115,7 +115,22 @@ registry.registerPath({
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(

View File

@@ -25,7 +25,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const restartCertificateParamsSchema = z.strictObject({
certId: z.string().transform(stoi).pipe(z.int().positive()),
certId: z.coerce.number().int().positive(),
orgId: z.string()
});
@@ -36,11 +36,26 @@ registry.registerPath({
tags: ["Certificate"],
request: {
params: z.object({
certId: z.string().transform(stoi).pipe(z.int().positive()),
certId: z.coerce.number().int().positive(),
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(

View File

@@ -42,7 +42,22 @@ registry.registerPath({
params: paramsSchema,
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(

View File

@@ -25,6 +25,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { isSubscribed } from "#private/lib/isSubscribed";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const paramsSchema = z.strictObject({});
@@ -65,6 +66,20 @@ export type ListDomainNamespacesResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListDomainNamespacesResponseDataSchema = z.object({
domainNamespaces: z.array(
z.object({
domainNamespaceId: z.string(),
domainId: z.string()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/domains/namepaces",
@@ -73,7 +88,18 @@ registry.registerPath({
request: {
query: querySchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(
ListDomainNamespacesResponseDataSchema
)
}
}
}
}
});
export async function listDomainNamespaces(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import { eventStreamingDestinations } from "@server/db";
import { logStreamingManager } from "#private/lib/logStreaming";
@@ -42,6 +43,10 @@ const bodySchema = z.strictObject({
export type CreateEventStreamingDestinationResponse = {
destinationId: number;
};
const CreateEventStreamingDestinationResponseDataSchema = z.object({
destinationId: z.number()
});
registry.registerPath({
method: "put",
@@ -58,7 +63,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(CreateEventStreamingDestinationResponseDataSchema)
}
}
}
}
});
export async function createEventStreamingDestination(

View File

@@ -38,7 +38,22 @@ registry.registerPath({
request: {
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(

View File

@@ -24,6 +24,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { eq, sql } from "drizzle-orm";
import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty()
@@ -67,6 +68,31 @@ export type ListEventStreamingDestinationsResponse = {
};
};
const ListEventStreamingDestinationsResponseDataSchema = z.object({
destinations: z.array(
z.object({
destinationId: z.number(),
orgId: z.string(),
type: z.string(),
config: z.string(),
enabled: z.boolean(),
lastError: z.string().nullable(),
lastErrorAt: z.number().nullable(),
createdAt: z.number(),
updatedAt: z.number(),
sendConnectionLogs: z.boolean(),
sendRequestLogs: z.boolean(),
sendActionLogs: z.boolean(),
sendAccessLogs: z.boolean()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
async function query(orgId: string, limit: number, offset: number) {
const res = await db
.select()
@@ -88,7 +114,18 @@ registry.registerPath({
query: querySchema,
params: paramsSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(
ListEventStreamingDestinationsResponseDataSchema
)
}
}
}
}
});
export async function listEventStreamingDestinations(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import { eventStreamingDestinations } from "@server/db";
import response from "@server/lib/response";
@@ -45,6 +46,10 @@ const bodySchema = z.strictObject({
export type UpdateEventStreamingDestinationResponse = {
destinationId: number;
};
const UpdateEventStreamingDestinationResponseDataSchema = z.object({
destinationId: z.number()
});
registry.registerPath({
method: "post",
@@ -61,7 +66,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateEventStreamingDestinationResponseDataSchema)
}
}
}
}
});
export async function updateEventStreamingDestination(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db, targetHealthCheck, newts, sites } from "@server/db";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
@@ -52,6 +53,10 @@ const bodySchema = z.strictObject({
export type CreateHealthCheckResponse = {
targetHealthCheckId: number;
};
const CreateHealthCheckResponseDataSchema = z.object({
targetHealthCheckId: z.number()
});
registry.registerPath({
method: "put",
@@ -68,7 +73,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(CreateHealthCheckResponseDataSchema)
}
}
}
}
});
export async function createHealthCheck(

View File

@@ -41,7 +41,22 @@ registry.registerPath({
request: {
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(

View File

@@ -68,7 +68,22 @@ registry.registerPath({
params: paramsSchema,
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(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db, targetHealthCheck, newts, sites } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -81,6 +82,29 @@ export type UpdateHealthCheckResponse = {
hcHealthyThreshold: number | null;
hcUnhealthyThreshold: number | null;
};
const UpdateHealthCheckResponseDataSchema = z.object({
targetHealthCheckId: z.number(),
name: z.string().nullable(),
siteId: z.number().nullable(),
hcEnabled: z.boolean(),
hcHealth: z.string().nullable(),
hcMode: z.string().nullable(),
hcHostname: z.string().nullable(),
hcPort: z.number().nullable(),
hcPath: z.string().nullable(),
hcScheme: z.string().nullable(),
hcMethod: z.string().nullable(),
hcInterval: z.number().nullable(),
hcUnhealthyInterval: z.number().nullable(),
hcTimeout: z.number().nullable(),
hcHeaders: z.string().nullable(),
hcFollowRedirects: z.boolean().nullable(),
hcStatus: z.number().nullable(),
hcTlsServerName: z.string().nullable(),
hcHealthyThreshold: z.number().nullable(),
hcUnhealthyThreshold: z.number().nullable()
});
registry.registerPath({
method: "post",
@@ -97,7 +121,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateHealthCheckResponseDataSchema)
}
}
}
}
});
export async function updateHealthCheck(

View File

@@ -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(

View File

@@ -38,7 +38,22 @@ registry.registerPath({
request: {
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(

View File

@@ -56,7 +56,22 @@ registry.registerPath({
request: {
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(

View File

@@ -72,7 +72,22 @@ registry.registerPath({
query: querySchema,
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(

View File

@@ -13,6 +13,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db, idpOrg } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -54,6 +55,10 @@ const bodySchema = z.strictObject({
export type UpdateOrgIdpResponse = {
idpId: number;
};
const UpdateOrgIdpResponseDataSchema = z.object({
idpId: z.number()
});
registry.registerPath({
method: "post",
@@ -70,7 +75,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateOrgIdpResponseDataSchema)
}
}
}
}
});
export async function updateOrgOidcIdp(

View File

@@ -28,7 +28,7 @@ import { OlmErrorCodes, sendOlmError } from "@server/routers/olm/error";
import { sendTerminateClient } from "@server/routers/client/terminate";
const reGenerateSecretParamsSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
const reGenerateSecretBodySchema = z.strictObject({

View File

@@ -27,7 +27,7 @@ import { getAllowedIps } from "@server/routers/target/helpers";
import { disconnectClient, sendToClient } from "#private/routers/ws";
const updateSiteParamsSchema = z.strictObject({
siteId: z.string().transform(Number).pipe(z.int().positive())
siteId: z.coerce.number().int().positive()
});
const updateSiteBodySchema = z.strictObject({

View File

@@ -93,7 +93,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(

View File

@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
const addUserRoleParamsSchema = z.strictObject({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number())
roleId: z.coerce.number()
});
registry.registerPath({
@@ -38,7 +38,22 @@ registry.registerPath({
request: {
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(

View File

@@ -27,7 +27,7 @@ import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAs
const removeUserRoleParamsSchema = z.strictObject({
userId: z.string(),
roleId: z.string().transform(stoi).pipe(z.number())
roleId: z.coerce.number()
});
registry.registerPath({
@@ -39,7 +39,22 @@ registry.registerPath({
request: {
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(

View File

@@ -22,7 +22,22 @@ registry.registerPath({
request: {
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(

View File

@@ -31,7 +31,7 @@ export const generateAccessTokenBodySchema = 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<
@@ -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(

View File

@@ -129,7 +129,22 @@ registry.registerPath({
}),
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({
@@ -143,7 +158,22 @@ registry.registerPath({
}),
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(

View File

@@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from "express";
import { db } from "@server/db";
import HttpCode from "@server/types/HttpCode";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { apiKeyOrg, apiKeys } from "@server/db";
import { fromError } from "zod-validation-error";
import createHttpError from "http-errors";
@@ -32,6 +33,14 @@ export type CreateOrgApiKeyResponse = {
lastChars: string;
createdAt: string;
};
const CreateOrgApiKeyResponseDataSchema = z.object({
apiKeyId: z.string(),
name: z.string(),
apiKey: z.string(),
lastChars: z.string(),
createdAt: z.string()
});
registry.registerPath({
method: "put",
@@ -48,7 +57,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(CreateOrgApiKeyResponseDataSchema)
}
}
}
}
});
export async function createOrgApiKey(

View File

@@ -22,7 +22,22 @@ registry.registerPath({
request: {
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(

View File

@@ -9,6 +9,7 @@ import { z } from "zod";
import { fromError } from "zod-validation-error";
import { eq } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const paramsSchema = z.object({
apiKeyId: z.string().nonempty()
@@ -44,6 +45,19 @@ export type ListApiKeyActionsResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListApiKeyActionsResponseDataSchema = z.object({
actions: z.array(
z.object({
actionId: z.string()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/org/{orgId}/api-key/{apiKeyId}/actions",
@@ -53,7 +67,18 @@ registry.registerPath({
params: paramsSchema,
query: querySchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(
ListApiKeyActionsResponseDataSchema
)
}
}
}
}
});
export async function listApiKeyActions(

View File

@@ -9,6 +9,7 @@ import { z } from "zod";
import { fromError } from "zod-validation-error";
import { eq, and } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const querySchema = z.object({
limit: z
@@ -48,6 +49,23 @@ export type ListOrgApiKeysResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListOrgApiKeysResponseDataSchema = z.object({
apiKeys: z.array(
z.object({
apiKeyId: z.string(),
orgId: z.string(),
lastChars: z.string(),
createdAt: z.string(),
name: z.string()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/org/{orgId}/api-keys",
@@ -57,7 +75,18 @@ registry.registerPath({
params: paramsSchema,
query: querySchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(
ListOrgApiKeysResponseDataSchema
)
}
}
}
}
});
export async function listOrgApiKeys(

View File

@@ -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(

View File

@@ -5,6 +5,7 @@ import { OpenAPITags } from "@server/openApi";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { fromError } from "zod-validation-error";
import { z } from "zod";
import logger from "@server/logger";
import {
queryAccessAuditLogsQuery,
@@ -28,7 +29,22 @@ registry.registerPath({
}),
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(

View File

@@ -156,7 +156,22 @@ registry.registerPath({
query: queryAccessAuditLogsQuery,
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>>;

View File

@@ -86,6 +86,20 @@ export const queryRequestAuditLogsCombined = queryAccessAuditLogsQuery.merge(
);
type Q = z.infer<typeof queryRequestAuditLogsCombined>;
function sortNamedFilterOptions<T extends { id: number; name: string | null }>(
items: T[]
): T[] {
return [...items].sort((a, b) => {
const nameA = a.name ?? "";
const nameB = b.name ?? "";
if (nameA < nameB) return -1;
if (nameA > nameB) return 1;
return a.id - b.id;
});
}
function getWhere(data: Q) {
return and(
gt(requestAuditLog.timestamp, data.timeStart),
@@ -227,7 +241,22 @@ registry.registerPath({
query: queryAccessAuditLogsQuery,
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(
@@ -353,7 +382,7 @@ async function queryUniqueFilterAttributes(
actors: uniqueActors
.map((row) => row.actor)
.filter((actor): actor is string => actor !== null),
resources: resourcesWithNames,
resources: sortNamedFilterOptions(resourcesWithNames),
locations: uniqueLocations
.map((row) => row.locations)
.filter((location): location is string => location !== null),

View File

@@ -9,7 +9,7 @@ import logger from "@server/logger";
export const params = z.strictObject({
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>;

View File

@@ -51,7 +51,22 @@ export type LookupUserResponse = {
// request: {
// body: lookupBodySchema
// },
// 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 lookupUser(

View File

@@ -31,7 +31,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 applyJSONBlueprint(

View File

@@ -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 applyYAMLBlueprint(

View File

@@ -7,13 +7,12 @@ import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import stoi from "@server/lib/stoi";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { BlueprintData } from "./types";
const getBlueprintSchema = z.strictObject({
blueprintId: z.string().transform(stoi).pipe(z.int().positive()),
blueprintId: z.coerce.number().int().positive(),
orgId: z.string()
});
@@ -57,7 +56,22 @@ registry.registerPath({
request: {
params: getBlueprintSchema
},
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 getBlueprint(

View File

@@ -74,7 +74,22 @@ registry.registerPath({
}),
query: listBluePrintsSchema
},
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 listBlueprints(

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const archiveClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -22,7 +22,22 @@ registry.registerPath({
request: {
params: archiveClientSchema
},
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 archiveClient(

View File

@@ -13,7 +13,7 @@ import { sendTerminateClient } from "./terminate";
import { OlmErrorCodes } from "../olm/error";
const blockClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -24,7 +24,22 @@ registry.registerPath({
request: {
params: blockClientSchema
},
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 blockClient(

View File

@@ -59,7 +59,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 createClient(

View File

@@ -60,7 +60,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 createUserClient(

View File

@@ -14,7 +14,7 @@ import { sendTerminateClient } from "./terminate";
import { OlmErrorCodes } from "../olm/error";
const deleteClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -25,7 +25,22 @@ registry.registerPath({
request: {
params: deleteClientSchema
},
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 deleteClient(

View File

@@ -253,7 +253,22 @@ registry.registerPath({
niceId: 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()
})
}
}
}
}
});
registry.registerPath({
@@ -266,7 +281,22 @@ registry.registerPath({
clientId: z.number()
})
},
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 getClient(

View File

@@ -186,7 +186,22 @@ registry.registerPath({
query: listClientsSchema,
params: listClientsParamsSchema
},
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 listClients(

View File

@@ -213,7 +213,22 @@ registry.registerPath({
query: listUserDevicesSchema,
params: listUserDevicesParamsSchema
},
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 listUserDevices(

View File

@@ -6,6 +6,7 @@ import logger from "@server/logger";
import { generateId } from "@server/auth/sessions/app";
import { getNextAvailableClientSubnet } from "@server/lib/ip";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
@@ -14,6 +15,12 @@ export type PickClientDefaultsResponse = {
olmSecret: string;
subnet: string;
};
const PickClientDefaultsResponseDataSchema = z.object({
olmId: z.string(),
olmSecret: z.string(),
subnet: z.string()
});
const pickClientDefaultsSchema = z.strictObject({
orgId: z.string()
@@ -27,7 +34,16 @@ registry.registerPath({
request: {
params: pickClientDefaultsSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(PickClientDefaultsResponseDataSchema)
}
}
}
}
});
export async function pickClientDefaults(

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const unarchiveClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -22,7 +22,22 @@ registry.registerPath({
request: {
params: unarchiveClientSchema
},
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 unarchiveClient(

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const unblockClientSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
registry.registerPath({
@@ -22,7 +22,22 @@ registry.registerPath({
request: {
params: unblockClientSchema
},
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 unblockClient(

View File

@@ -11,7 +11,7 @@ import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const updateClientParamsSchema = z.strictObject({
clientId: z.string().transform(Number).pipe(z.int().positive())
clientId: z.coerce.number().int().positive()
});
const updateClientSchema = z.strictObject({
@@ -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 updateClient(

View File

@@ -37,7 +37,22 @@ registry.registerPath({
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 getDNSRecords(

View File

@@ -38,7 +38,22 @@ registry.registerPath({
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 getDomain(

View File

@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const listDomainsParamsSchema = z.strictObject({
orgId: z.string()
@@ -56,6 +57,28 @@ export type ListDomainsResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListDomainsResponseDataSchema = z.object({
domains: z.array(
z.object({
domainId: z.string(),
baseDomain: z.string(),
verified: z.boolean(),
type: z.string().nullable(),
failed: z.boolean(),
tries: z.number(),
configManaged: z.boolean(),
certResolver: z.string().nullable(),
preferWildcardCert: z.boolean().nullable(),
errorMessage: z.string().nullable()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/org/{orgId}/domains",
@@ -67,7 +90,16 @@ registry.registerPath({
}),
query: listDomainsSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(ListDomainsResponseDataSchema)
}
}
}
}
});
export async function listDomains(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db, domains, orgDomains } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -24,6 +25,12 @@ export type UpdateDomainResponse = {
certResolver: string | null;
preferWildcardCert: boolean | null;
};
const UpdateDomainResponseDataSchema = z.object({
domainId: z.string(),
certResolver: z.string().nullable(),
preferWildcardCert: z.boolean().nullable()
});
registry.registerPath({
method: "patch",
@@ -36,7 +43,16 @@ registry.registerPath({
orgId: z.string()
})
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateDomainResponseDataSchema)
}
}
}
}
});
export async function updateOrgDomain(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -22,6 +23,8 @@ const bodySchema = z.strictObject({
});
export type CreateIdpOrgPolicyResponse = {};
const CreateIdpOrgPolicyResponseDataSchema = z.object({});
registry.registerPath({
method: "put",
@@ -38,7 +41,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(CreateIdpOrgPolicyResponseDataSchema)
}
}
}
}
});
export async function createIdpOrgPolicy(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -33,6 +34,11 @@ export type CreateIdpResponse = {
idpId: number;
redirectUrl: string;
};
const CreateIdpResponseDataSchema = z.object({
idpId: z.number(),
redirectUrl: z.string()
});
registry.registerPath({
method: "put",
@@ -48,7 +54,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(CreateIdpResponseDataSchema)
}
}
}
}
});
export async function createOidcIdp(

View File

@@ -25,7 +25,22 @@ registry.registerPath({
request: {
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 deleteIdp(

View File

@@ -23,7 +23,22 @@ registry.registerPath({
request: {
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 deleteIdpOrgPolicy(

View File

@@ -38,7 +38,22 @@ registry.registerPath({
request: {
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 getIdp(

View File

@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const paramsSchema = z.object({
idpId: z.coerce.number<number>()
@@ -44,6 +45,21 @@ export type ListIdpOrgPoliciesResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListIdpOrgPoliciesResponseDataSchema = z.object({
policies: z.array(
z.object({
idpId: z.number(),
orgId: z.string(),
assignDefaultOrgRoleId: z.number().nullable()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/idp/{idpId}/org",
@@ -53,7 +69,18 @@ registry.registerPath({
params: paramsSchema,
query: querySchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(
ListIdpOrgPoliciesResponseDataSchema
)
}
}
}
}
});
export async function listIdpOrgPolicies(

View File

@@ -9,6 +9,7 @@ import { eq, sql } from "drizzle-orm";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const querySchema = z.strictObject({
limit: z
@@ -54,6 +55,25 @@ export type ListIdpsResponse = {
};
};
const ListIdpsResponseDataSchema = z.object({
idps: z.array(
z.object({
idpId: z.number(),
name: z.string(),
type: z.string(),
variant: z.string().nullable(),
orgCount: z.number(),
autoProvision: z.boolean().nullable(),
tags: z.string().nullable()
})
),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
registry.registerPath({
method: "get",
path: "/idp",
@@ -62,7 +82,16 @@ registry.registerPath({
request: {
query: querySchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(ListIdpsResponseDataSchema)
}
}
}
}
});
export async function listIdps(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -21,6 +22,8 @@ const bodySchema = z.strictObject({
});
export type UpdateIdpOrgPolicyResponse = {};
const UpdateIdpOrgPolicyResponseDataSchema = z.object({});
registry.registerPath({
method: "post",
@@ -37,7 +40,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateIdpOrgPolicyResponseDataSchema)
}
}
}
}
});
export async function updateIdpOrgPolicy(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -38,6 +39,10 @@ const bodySchema = z.strictObject({
export type UpdateIdpResponse = {
idpId: number;
};
const UpdateIdpResponseDataSchema = z.object({
idpId: z.number()
});
registry.registerPath({
method: "post",
@@ -54,7 +59,16 @@ registry.registerPath({
}
}
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(UpdateIdpResponseDataSchema)
}
}
}
}
});
export async function updateOidcIdp(

View File

@@ -43,7 +43,22 @@ export type CreateOlmResponse = {
// },
// 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 createUserOlm(

View File

@@ -28,7 +28,22 @@ const paramsSchema = z
// request: {
// 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 deleteUserOlm(

View File

@@ -30,7 +30,22 @@ const querySchema = z.object({
// request: {
// 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 getUserOlm(

View File

@@ -41,7 +41,22 @@ const paramsSchema = z
// query: querySchema,
// 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 type ListUserOlmsResponse = {

View File

@@ -83,7 +83,22 @@ registry.registerPath({
request: {
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 checkOrgUserAccess(

View File

@@ -74,7 +74,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 createOrg(

View File

@@ -24,7 +24,22 @@ registry.registerPath({
request: {
params: deleteOrgSchema
},
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 deleteOrg(

View File

@@ -1,5 +1,6 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
import { db } from "@server/db";
import { Org, orgs } from "@server/db";
import { eq } from "drizzle-orm";
@@ -17,6 +18,10 @@ const getOrgSchema = z.strictObject({
export type GetOrgResponse = {
org: Org;
};
const GetOrgResponseDataSchema = z.object({
org: z.object({}).passthrough()
});
registry.registerPath({
method: "get",
@@ -26,7 +31,16 @@ registry.registerPath({
request: {
params: getOrgSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(GetOrgResponseDataSchema)
}
}
}
}
});
export async function getOrg(

View File

@@ -9,6 +9,7 @@ import { sql, inArray, eq } from "drizzle-orm";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { createApiResponseSchema } from "@server/lib/openapi/createApiResponseSchema";
const listOrgsSchema = z.object({
limit: z
@@ -33,7 +34,16 @@ registry.registerPath({
request: {
query: listOrgsSchema
},
responses: {}
responses: {
200: {
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(ListOrgsResponseDataSchema)
}
}
}
}
});
export type ListOrgsResponse = {
@@ -41,6 +51,15 @@ export type ListOrgsResponse = {
pagination: { total: number; limit: number; offset: number };
};
const ListOrgsResponseDataSchema = z.object({
orgs: z.array(z.object({}).passthrough()),
pagination: z.object({
total: z.number(),
limit: z.number(),
offset: z.number()
})
});
export async function listOrgs(
req: Request,
res: Response,

View File

@@ -37,7 +37,22 @@ const listOrgsSchema = z.object({
// request: {
// query: listOrgsSchema
// },
// 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()
// })
// }
// }
// }
// }
// });
type ResponseOrg = Org & {

View File

@@ -21,7 +21,22 @@ registry.registerPath({
request: {
params: resetOrgBandwidthParamsSchema
},
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 resetOrgBandwidth(

View File

@@ -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 updateOrg(

Some files were not shown because too many files have changed in this diff Show More