diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de7c56f7..ee90379e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -113,17 +113,6 @@ jobs: go-version-file: cmd/go.mod cache-dependency-path: cmd/go.sum - - name: Package restish-zpan plugin - working-directory: cmd - run: bash scripts/package-restish-zpan.sh "${{ github.ref_name }}" ../dist/restish-zpan - - - name: Verify restish-zpan plugin startup - working-directory: cmd - run: | - go build -trimpath -o /tmp/restish-zpan ./restish-zpan - /tmp/restish-zpan --rsh-plugin-manifest >/tmp/restish-zpan.manifest.cbor - /tmp/restish-zpan --rsh-plugin-commands >/tmp/restish-zpan.commands.cbor - - name: Generate changelog id: changelog uses: requarks/changelog-action@v1 @@ -138,15 +127,9 @@ jobs: body: | ${{ steps.changelog.outputs.changes }} - ### Restish plugin - ```bash - restish plugin install saltbo/zpan zpan - ``` - ### Docker ```bash docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }} docker pull ghcr.io/${{ github.repository }}:${{ github.ref_name }}-cli ``` generate_release_notes: false - files: dist/restish-zpan/* diff --git a/README.md b/README.md index 49ffde1c..4011c9e7 100644 --- a/README.md +++ b/README.md @@ -164,8 +164,6 @@ After startup: ## Documentation - [v2 Launch Offers](docs/v2-launch-offers.md) — earn ZPan Pro for free -- [ZPan Agent Skill](docs/agent-skill.md) — agent workflows for Restish setup, least-privilege profiles, uploads, CI, and MCP -- [Restish ZPan upload plugin](docs/restish-zpan.md) — install `restish-zpan` and upload local files through Restish profiles - [Roadmap](V2_ROADMAP.md) - [Contributing](CONTRIBUTING.md) diff --git a/cmd/go.mod b/cmd/go.mod index 4a413e66..99bb62ba 100644 --- a/cmd/go.mod +++ b/cmd/go.mod @@ -9,7 +9,6 @@ require ( github.com/docker/go-units v0.5.0 github.com/oapi-codegen/runtime v1.4.1 github.com/oschwald/geoip2-golang v1.13.0 - github.com/rest-sh/restish/v2 v2.3.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 golang.org/x/sys v0.45.0 @@ -21,7 +20,6 @@ require ( github.com/avast/retry-go v3.0.0+incompatible // indirect github.com/cenkalti/hub v1.0.1-0.20160527103212-11382a9960d3 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -37,7 +35,6 @@ require ( github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect golang.org/x/net v0.55.0 // indirect diff --git a/cmd/go.sum b/cmd/go.sum index 76cabb2f..77ee024d 100644 --- a/cmd/go.sum +++ b/cmd/go.sum @@ -26,8 +26,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= -github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -61,8 +59,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rest-sh/restish/v2 v2.3.0 h1:kbeHH4uvEYsgi1ShS8M5M/qkWHQVlQ6L8b3NGcJA6NE= -github.com/rest-sh/restish/v2 v2.3.0/go.mod h1:5i2g3d6x84yp4NvJ8c7gUPYoMSeaeXIUizC3CQLpHRU= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -88,8 +84,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0= diff --git a/cmd/internal/openapi/client.gen.go b/cmd/internal/openapi/client.gen.go index c1320276..56c197c4 100644 --- a/cmd/internal/openapi/client.gen.go +++ b/cmd/internal/openapi/client.gen.go @@ -2122,6 +2122,108 @@ func (e ListAgentOAuthGrants200JSONResponseBodyItemsStatus) Valid() bool { } } +// Defines values for ListUsersParamsSearchField. +const ( + ListUsersParamsSearchFieldEmail ListUsersParamsSearchField = "email" + ListUsersParamsSearchFieldName ListUsersParamsSearchField = "name" +) + +// Valid indicates whether the value is a known member of the ListUsersParamsSearchField enum. +func (e ListUsersParamsSearchField) Valid() bool { + switch e { + case ListUsersParamsSearchFieldEmail: + return true + case ListUsersParamsSearchFieldName: + return true + default: + return false + } +} + +// Defines values for ListUsersParamsSearchOperator. +const ( + ListUsersParamsSearchOperatorContains ListUsersParamsSearchOperator = "contains" + ListUsersParamsSearchOperatorEndsWith ListUsersParamsSearchOperator = "ends_with" + ListUsersParamsSearchOperatorStartsWith ListUsersParamsSearchOperator = "starts_with" +) + +// Valid indicates whether the value is a known member of the ListUsersParamsSearchOperator enum. +func (e ListUsersParamsSearchOperator) Valid() bool { + switch e { + case ListUsersParamsSearchOperatorContains: + return true + case ListUsersParamsSearchOperatorEndsWith: + return true + case ListUsersParamsSearchOperatorStartsWith: + return true + default: + return false + } +} + +// Defines values for ListUsersParamsSortDirection. +const ( + ListUsersParamsSortDirectionAsc ListUsersParamsSortDirection = "asc" + ListUsersParamsSortDirectionDesc ListUsersParamsSortDirection = "desc" +) + +// Valid indicates whether the value is a known member of the ListUsersParamsSortDirection enum. +func (e ListUsersParamsSortDirection) Valid() bool { + switch e { + case ListUsersParamsSortDirectionAsc: + return true + case ListUsersParamsSortDirectionDesc: + return true + default: + return false + } +} + +// Defines values for ListUsersParamsFilterOperator. +const ( + ListUsersParamsFilterOperatorContains ListUsersParamsFilterOperator = "contains" + ListUsersParamsFilterOperatorEndsWith ListUsersParamsFilterOperator = "ends_with" + ListUsersParamsFilterOperatorEq ListUsersParamsFilterOperator = "eq" + ListUsersParamsFilterOperatorGt ListUsersParamsFilterOperator = "gt" + ListUsersParamsFilterOperatorGte ListUsersParamsFilterOperator = "gte" + ListUsersParamsFilterOperatorIn ListUsersParamsFilterOperator = "in" + ListUsersParamsFilterOperatorLt ListUsersParamsFilterOperator = "lt" + ListUsersParamsFilterOperatorLte ListUsersParamsFilterOperator = "lte" + ListUsersParamsFilterOperatorNe ListUsersParamsFilterOperator = "ne" + ListUsersParamsFilterOperatorNotIn ListUsersParamsFilterOperator = "not_in" + ListUsersParamsFilterOperatorStartsWith ListUsersParamsFilterOperator = "starts_with" +) + +// Valid indicates whether the value is a known member of the ListUsersParamsFilterOperator enum. +func (e ListUsersParamsFilterOperator) Valid() bool { + switch e { + case ListUsersParamsFilterOperatorContains: + return true + case ListUsersParamsFilterOperatorEndsWith: + return true + case ListUsersParamsFilterOperatorEq: + return true + case ListUsersParamsFilterOperatorGt: + return true + case ListUsersParamsFilterOperatorGte: + return true + case ListUsersParamsFilterOperatorIn: + return true + case ListUsersParamsFilterOperatorLt: + return true + case ListUsersParamsFilterOperatorLte: + return true + case ListUsersParamsFilterOperatorNe: + return true + case ListUsersParamsFilterOperatorNotIn: + return true + case ListUsersParamsFilterOperatorStartsWith: + return true + default: + return false + } +} + // Defines values for ChangeEmail200JSONResponseBodyMessage. const ( ChangeEmail200JSONResponseBodyMessageEmailUpdated ChangeEmail200JSONResponseBodyMessage = "Email updated" @@ -2242,228 +2344,294 @@ func (e PostApiAuthDeviceToken400JSONResponseBodyError) Valid() bool { } } -// Defines values for PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes. +// Defines values for GetJSONWebKeySet200JSONResponseBodyKeysUse. const ( - PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesAuthorizationCode PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes = "authorization_code" - PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesClientCredentials PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes = "client_credentials" - PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesRefreshToken PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes = "refresh_token" + Sig GetJSONWebKeySet200JSONResponseBodyKeysUse = "sig" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes enum. -func (e PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes) Valid() bool { +// Valid indicates whether the value is a known member of the GetJSONWebKeySet200JSONResponseBodyKeysUse enum. +func (e GetJSONWebKeySet200JSONResponseBodyKeysUse) Valid() bool { switch e { - case PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesAuthorizationCode: - return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesClientCredentials: - return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypesRefreshToken: + case Sig: return true default: return false } } -// Defines values for PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes. +// Defines values for LinkSocialAccountJSONBodyProvider0. const ( - PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypesCode PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes = "code" + LinkSocialAccountJSONBodyProvider0Apple LinkSocialAccountJSONBodyProvider0 = "apple" + LinkSocialAccountJSONBodyProvider0Atlassian LinkSocialAccountJSONBodyProvider0 = "atlassian" + LinkSocialAccountJSONBodyProvider0Cognito LinkSocialAccountJSONBodyProvider0 = "cognito" + LinkSocialAccountJSONBodyProvider0Discord LinkSocialAccountJSONBodyProvider0 = "discord" + LinkSocialAccountJSONBodyProvider0Dropbox LinkSocialAccountJSONBodyProvider0 = "dropbox" + LinkSocialAccountJSONBodyProvider0Facebook LinkSocialAccountJSONBodyProvider0 = "facebook" + LinkSocialAccountJSONBodyProvider0Figma LinkSocialAccountJSONBodyProvider0 = "figma" + LinkSocialAccountJSONBodyProvider0Github LinkSocialAccountJSONBodyProvider0 = "github" + LinkSocialAccountJSONBodyProvider0Gitlab LinkSocialAccountJSONBodyProvider0 = "gitlab" + LinkSocialAccountJSONBodyProvider0Google LinkSocialAccountJSONBodyProvider0 = "google" + LinkSocialAccountJSONBodyProvider0Huggingface LinkSocialAccountJSONBodyProvider0 = "huggingface" + LinkSocialAccountJSONBodyProvider0Kakao LinkSocialAccountJSONBodyProvider0 = "kakao" + LinkSocialAccountJSONBodyProvider0Kick LinkSocialAccountJSONBodyProvider0 = "kick" + LinkSocialAccountJSONBodyProvider0Line LinkSocialAccountJSONBodyProvider0 = "line" + LinkSocialAccountJSONBodyProvider0Linear LinkSocialAccountJSONBodyProvider0 = "linear" + LinkSocialAccountJSONBodyProvider0Linkedin LinkSocialAccountJSONBodyProvider0 = "linkedin" + LinkSocialAccountJSONBodyProvider0Microsoft LinkSocialAccountJSONBodyProvider0 = "microsoft" + LinkSocialAccountJSONBodyProvider0Naver LinkSocialAccountJSONBodyProvider0 = "naver" + LinkSocialAccountJSONBodyProvider0Notion LinkSocialAccountJSONBodyProvider0 = "notion" + LinkSocialAccountJSONBodyProvider0Paybin LinkSocialAccountJSONBodyProvider0 = "paybin" + LinkSocialAccountJSONBodyProvider0Paypal LinkSocialAccountJSONBodyProvider0 = "paypal" + LinkSocialAccountJSONBodyProvider0Polar LinkSocialAccountJSONBodyProvider0 = "polar" + LinkSocialAccountJSONBodyProvider0Railway LinkSocialAccountJSONBodyProvider0 = "railway" + LinkSocialAccountJSONBodyProvider0Reddit LinkSocialAccountJSONBodyProvider0 = "reddit" + LinkSocialAccountJSONBodyProvider0Roblox LinkSocialAccountJSONBodyProvider0 = "roblox" + LinkSocialAccountJSONBodyProvider0Salesforce LinkSocialAccountJSONBodyProvider0 = "salesforce" + LinkSocialAccountJSONBodyProvider0Slack LinkSocialAccountJSONBodyProvider0 = "slack" + LinkSocialAccountJSONBodyProvider0Spotify LinkSocialAccountJSONBodyProvider0 = "spotify" + LinkSocialAccountJSONBodyProvider0Tiktok LinkSocialAccountJSONBodyProvider0 = "tiktok" + LinkSocialAccountJSONBodyProvider0Twitch LinkSocialAccountJSONBodyProvider0 = "twitch" + LinkSocialAccountJSONBodyProvider0Twitter LinkSocialAccountJSONBodyProvider0 = "twitter" + LinkSocialAccountJSONBodyProvider0Vercel LinkSocialAccountJSONBodyProvider0 = "vercel" + LinkSocialAccountJSONBodyProvider0Vk LinkSocialAccountJSONBodyProvider0 = "vk" + LinkSocialAccountJSONBodyProvider0Wechat LinkSocialAccountJSONBodyProvider0 = "wechat" + LinkSocialAccountJSONBodyProvider0Zoom LinkSocialAccountJSONBodyProvider0 = "zoom" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes enum. -func (e PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes) Valid() bool { +// Valid indicates whether the value is a known member of the LinkSocialAccountJSONBodyProvider0 enum. +func (e LinkSocialAccountJSONBodyProvider0) Valid() bool { switch e { - case PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypesCode: + case LinkSocialAccountJSONBodyProvider0Apple: + return true + case LinkSocialAccountJSONBodyProvider0Atlassian: + return true + case LinkSocialAccountJSONBodyProvider0Cognito: + return true + case LinkSocialAccountJSONBodyProvider0Discord: + return true + case LinkSocialAccountJSONBodyProvider0Dropbox: + return true + case LinkSocialAccountJSONBodyProvider0Facebook: + return true + case LinkSocialAccountJSONBodyProvider0Figma: + return true + case LinkSocialAccountJSONBodyProvider0Github: + return true + case LinkSocialAccountJSONBodyProvider0Gitlab: + return true + case LinkSocialAccountJSONBodyProvider0Google: + return true + case LinkSocialAccountJSONBodyProvider0Huggingface: + return true + case LinkSocialAccountJSONBodyProvider0Kakao: + return true + case LinkSocialAccountJSONBodyProvider0Kick: + return true + case LinkSocialAccountJSONBodyProvider0Line: + return true + case LinkSocialAccountJSONBodyProvider0Linear: + return true + case LinkSocialAccountJSONBodyProvider0Linkedin: + return true + case LinkSocialAccountJSONBodyProvider0Microsoft: + return true + case LinkSocialAccountJSONBodyProvider0Naver: + return true + case LinkSocialAccountJSONBodyProvider0Notion: + return true + case LinkSocialAccountJSONBodyProvider0Paybin: + return true + case LinkSocialAccountJSONBodyProvider0Paypal: + return true + case LinkSocialAccountJSONBodyProvider0Polar: + return true + case LinkSocialAccountJSONBodyProvider0Railway: + return true + case LinkSocialAccountJSONBodyProvider0Reddit: + return true + case LinkSocialAccountJSONBodyProvider0Roblox: + return true + case LinkSocialAccountJSONBodyProvider0Salesforce: + return true + case LinkSocialAccountJSONBodyProvider0Slack: + return true + case LinkSocialAccountJSONBodyProvider0Spotify: + return true + case LinkSocialAccountJSONBodyProvider0Tiktok: + return true + case LinkSocialAccountJSONBodyProvider0Twitch: + return true + case LinkSocialAccountJSONBodyProvider0Twitter: + return true + case LinkSocialAccountJSONBodyProvider0Vercel: + return true + case LinkSocialAccountJSONBodyProvider0Vk: + return true + case LinkSocialAccountJSONBodyProvider0Wechat: + return true + case LinkSocialAccountJSONBodyProvider0Zoom: return true default: return false } } -// Defines values for PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod. +// Defines values for PostApiAuthOauth2CreateClientJSONBodyResponseTypes. const ( - PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodClientSecretBasic PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod = "client_secret_basic" - PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodClientSecretPost PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod = "client_secret_post" - PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodNone PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod = "none" + PostApiAuthOauth2CreateClientJSONBodyResponseTypesCode PostApiAuthOauth2CreateClientJSONBodyResponseTypes = "code" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod enum. -func (e PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClientJSONBodyResponseTypes enum. +func (e PostApiAuthOauth2CreateClientJSONBodyResponseTypes) Valid() bool { switch e { - case PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodClientSecretBasic: - return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodClientSecretPost: - return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethodNone: + case PostApiAuthOauth2CreateClientJSONBodyResponseTypesCode: return true default: return false } } -// Defines values for PostApiAuthOauth2CreateClient200JSONResponseBodyType. +// Defines values for PostApiAuthOauth2CreateClientJSONBodyType. const ( - PostApiAuthOauth2CreateClient200JSONResponseBodyTypeNative PostApiAuthOauth2CreateClient200JSONResponseBodyType = "native" - PostApiAuthOauth2CreateClient200JSONResponseBodyTypeUserAgentBased PostApiAuthOauth2CreateClient200JSONResponseBodyType = "user-agent-based" - PostApiAuthOauth2CreateClient200JSONResponseBodyTypeWeb PostApiAuthOauth2CreateClient200JSONResponseBodyType = "web" + PostApiAuthOauth2CreateClientJSONBodyTypeNative PostApiAuthOauth2CreateClientJSONBodyType = "native" + PostApiAuthOauth2CreateClientJSONBodyTypeUserAgentBased PostApiAuthOauth2CreateClientJSONBodyType = "user-agent-based" + PostApiAuthOauth2CreateClientJSONBodyTypeWeb PostApiAuthOauth2CreateClientJSONBodyType = "web" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient200JSONResponseBodyType enum. -func (e PostApiAuthOauth2CreateClient200JSONResponseBodyType) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClientJSONBodyType enum. +func (e PostApiAuthOauth2CreateClientJSONBodyType) Valid() bool { switch e { - case PostApiAuthOauth2CreateClient200JSONResponseBodyTypeNative: + case PostApiAuthOauth2CreateClientJSONBodyTypeNative: return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyTypeUserAgentBased: + case PostApiAuthOauth2CreateClientJSONBodyTypeUserAgentBased: return true - case PostApiAuthOauth2CreateClient200JSONResponseBodyTypeWeb: + case PostApiAuthOauth2CreateClientJSONBodyTypeWeb: return true default: return false } } -// Defines values for PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint. +// Defines values for PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes. const ( - PostApiAuthOauth2IntrospectJSONBodyTokenTypeHintAccessToken PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint = "access_token" - PostApiAuthOauth2IntrospectJSONBodyTokenTypeHintRefreshToken PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint = "refresh_token" + PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypesCode PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes = "code" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint enum. -func (e PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes enum. +func (e PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes) Valid() bool { switch e { - case PostApiAuthOauth2IntrospectJSONBodyTokenTypeHintAccessToken: - return true - case PostApiAuthOauth2IntrospectJSONBodyTokenTypeHintRefreshToken: + case PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypesCode: return true default: return false } } -// Defines values for PostApiAuthOauth2Link200JSONResponseBodyRedirect. +// Defines values for PostApiAuthOauth2CreateClient201JSONResponseBodyType. const ( - PostApiAuthOauth2Link200JSONResponseBodyRedirectTrue PostApiAuthOauth2Link200JSONResponseBodyRedirect = true + PostApiAuthOauth2CreateClient201JSONResponseBodyTypeNative PostApiAuthOauth2CreateClient201JSONResponseBodyType = "native" + PostApiAuthOauth2CreateClient201JSONResponseBodyTypeUserAgentBased PostApiAuthOauth2CreateClient201JSONResponseBodyType = "user-agent-based" + PostApiAuthOauth2CreateClient201JSONResponseBodyTypeWeb PostApiAuthOauth2CreateClient201JSONResponseBodyType = "web" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2Link200JSONResponseBodyRedirect enum. -func (e PostApiAuthOauth2Link200JSONResponseBodyRedirect) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2CreateClient201JSONResponseBodyType enum. +func (e PostApiAuthOauth2CreateClient201JSONResponseBodyType) Valid() bool { switch e { - case PostApiAuthOauth2Link200JSONResponseBodyRedirectTrue: + case PostApiAuthOauth2CreateClient201JSONResponseBodyTypeNative: + return true + case PostApiAuthOauth2CreateClient201JSONResponseBodyTypeUserAgentBased: + return true + case PostApiAuthOauth2CreateClient201JSONResponseBodyTypeWeb: return true default: return false } } -// Defines values for PostApiAuthOauth2Register200JSONResponseBodyGrantTypes. +// Defines values for PostApiAuthOauth2RegisterJSONBodyResponseTypes. const ( - PostApiAuthOauth2Register200JSONResponseBodyGrantTypesAuthorizationCode PostApiAuthOauth2Register200JSONResponseBodyGrantTypes = "authorization_code" - PostApiAuthOauth2Register200JSONResponseBodyGrantTypesClientCredentials PostApiAuthOauth2Register200JSONResponseBodyGrantTypes = "client_credentials" - PostApiAuthOauth2Register200JSONResponseBodyGrantTypesRefreshToken PostApiAuthOauth2Register200JSONResponseBodyGrantTypes = "refresh_token" + PostApiAuthOauth2RegisterJSONBodyResponseTypesCode PostApiAuthOauth2RegisterJSONBodyResponseTypes = "code" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register200JSONResponseBodyGrantTypes enum. -func (e PostApiAuthOauth2Register200JSONResponseBodyGrantTypes) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2RegisterJSONBodyResponseTypes enum. +func (e PostApiAuthOauth2RegisterJSONBodyResponseTypes) Valid() bool { switch e { - case PostApiAuthOauth2Register200JSONResponseBodyGrantTypesAuthorizationCode: - return true - case PostApiAuthOauth2Register200JSONResponseBodyGrantTypesClientCredentials: - return true - case PostApiAuthOauth2Register200JSONResponseBodyGrantTypesRefreshToken: + case PostApiAuthOauth2RegisterJSONBodyResponseTypesCode: return true default: return false } } -// Defines values for PostApiAuthOauth2Register200JSONResponseBodyResponseTypes. +// Defines values for PostApiAuthOauth2RegisterJSONBodySubjectType. const ( - PostApiAuthOauth2Register200JSONResponseBodyResponseTypesCode PostApiAuthOauth2Register200JSONResponseBodyResponseTypes = "code" + Pairwise PostApiAuthOauth2RegisterJSONBodySubjectType = "pairwise" + Public PostApiAuthOauth2RegisterJSONBodySubjectType = "public" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register200JSONResponseBodyResponseTypes enum. -func (e PostApiAuthOauth2Register200JSONResponseBodyResponseTypes) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2RegisterJSONBodySubjectType enum. +func (e PostApiAuthOauth2RegisterJSONBodySubjectType) Valid() bool { switch e { - case PostApiAuthOauth2Register200JSONResponseBodyResponseTypesCode: + case Pairwise: + return true + case Public: return true default: return false } } -// Defines values for PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod. +// Defines values for PostApiAuthOauth2RegisterJSONBodyType. const ( - PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodClientSecretBasic PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod = "client_secret_basic" - PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodClientSecretPost PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod = "client_secret_post" - PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodNone PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod = "none" + PostApiAuthOauth2RegisterJSONBodyTypeNative PostApiAuthOauth2RegisterJSONBodyType = "native" + PostApiAuthOauth2RegisterJSONBodyTypeUserAgentBased PostApiAuthOauth2RegisterJSONBodyType = "user-agent-based" + PostApiAuthOauth2RegisterJSONBodyTypeWeb PostApiAuthOauth2RegisterJSONBodyType = "web" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod enum. -func (e PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2RegisterJSONBodyType enum. +func (e PostApiAuthOauth2RegisterJSONBodyType) Valid() bool { switch e { - case PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodClientSecretBasic: + case PostApiAuthOauth2RegisterJSONBodyTypeNative: return true - case PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodClientSecretPost: + case PostApiAuthOauth2RegisterJSONBodyTypeUserAgentBased: return true - case PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethodNone: + case PostApiAuthOauth2RegisterJSONBodyTypeWeb: return true default: return false } } -// Defines values for PostApiAuthOauth2Register200JSONResponseBodyType. +// Defines values for PostApiAuthOauth2Register201JSONResponseBodyResponseTypes. const ( - PostApiAuthOauth2Register200JSONResponseBodyTypeNative PostApiAuthOauth2Register200JSONResponseBodyType = "native" - PostApiAuthOauth2Register200JSONResponseBodyTypeUserAgentBased PostApiAuthOauth2Register200JSONResponseBodyType = "user-agent-based" - PostApiAuthOauth2Register200JSONResponseBodyTypeWeb PostApiAuthOauth2Register200JSONResponseBodyType = "web" + PostApiAuthOauth2Register201JSONResponseBodyResponseTypesCode PostApiAuthOauth2Register201JSONResponseBodyResponseTypes = "code" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register200JSONResponseBodyType enum. -func (e PostApiAuthOauth2Register200JSONResponseBodyType) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register201JSONResponseBodyResponseTypes enum. +func (e PostApiAuthOauth2Register201JSONResponseBodyResponseTypes) Valid() bool { switch e { - case PostApiAuthOauth2Register200JSONResponseBodyTypeNative: - return true - case PostApiAuthOauth2Register200JSONResponseBodyTypeUserAgentBased: - return true - case PostApiAuthOauth2Register200JSONResponseBodyTypeWeb: + case PostApiAuthOauth2Register201JSONResponseBodyResponseTypesCode: return true default: return false } } -// Defines values for PostApiAuthOauth2RevokeJSONBodyTokenTypeHint. +// Defines values for PostApiAuthOauth2Register201JSONResponseBodyType. const ( - PostApiAuthOauth2RevokeJSONBodyTokenTypeHintAccessToken PostApiAuthOauth2RevokeJSONBodyTokenTypeHint = "access_token" - PostApiAuthOauth2RevokeJSONBodyTokenTypeHintRefreshToken PostApiAuthOauth2RevokeJSONBodyTokenTypeHint = "refresh_token" + PostApiAuthOauth2Register201JSONResponseBodyTypeNative PostApiAuthOauth2Register201JSONResponseBodyType = "native" + PostApiAuthOauth2Register201JSONResponseBodyTypeUserAgentBased PostApiAuthOauth2Register201JSONResponseBodyType = "user-agent-based" + PostApiAuthOauth2Register201JSONResponseBodyTypeWeb PostApiAuthOauth2Register201JSONResponseBodyType = "web" ) -// Valid indicates whether the value is a known member of the PostApiAuthOauth2RevokeJSONBodyTokenTypeHint enum. -func (e PostApiAuthOauth2RevokeJSONBodyTokenTypeHint) Valid() bool { +// Valid indicates whether the value is a known member of the PostApiAuthOauth2Register201JSONResponseBodyType enum. +func (e PostApiAuthOauth2Register201JSONResponseBodyType) Valid() bool { switch e { - case PostApiAuthOauth2RevokeJSONBodyTokenTypeHintAccessToken: + case PostApiAuthOauth2Register201JSONResponseBodyTypeNative: return true - case PostApiAuthOauth2RevokeJSONBodyTokenTypeHintRefreshToken: + case PostApiAuthOauth2Register201JSONResponseBodyTypeUserAgentBased: return true - default: - return false - } -} - -// Defines values for PostApiAuthOauth2TokenJSONBodyGrantType. -const ( - AuthorizationCode PostApiAuthOauth2TokenJSONBodyGrantType = "authorization_code" - ClientCredentials PostApiAuthOauth2TokenJSONBodyGrantType = "client_credentials" - RefreshToken PostApiAuthOauth2TokenJSONBodyGrantType = "refresh_token" -) - -// Valid indicates whether the value is a known member of the PostApiAuthOauth2TokenJSONBodyGrantType enum. -func (e PostApiAuthOauth2TokenJSONBodyGrantType) Valid() bool { - switch e { - case AuthorizationCode: - return true - case ClientCredentials: - return true - case RefreshToken: + case PostApiAuthOauth2Register201JSONResponseBodyTypeWeb: return true default: return false @@ -2473,6 +2641,7 @@ func (e PostApiAuthOauth2TokenJSONBodyGrantType) Valid() bool { // Defines values for PostApiAuthOauth2Token200JSONResponseBodyTokenType. const ( Bearer PostApiAuthOauth2Token200JSONResponseBodyTokenType = "Bearer" + DPoP PostApiAuthOauth2Token200JSONResponseBodyTokenType = "DPoP" ) // Valid indicates whether the value is a known member of the PostApiAuthOauth2Token200JSONResponseBodyTokenType enum. @@ -2480,6 +2649,44 @@ func (e PostApiAuthOauth2Token200JSONResponseBodyTokenType) Valid() bool { switch e { case Bearer: return true + case DPoP: + return true + default: + return false + } +} + +// Defines values for PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes. +const ( + Code PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes = "code" +) + +// Valid indicates whether the value is a known member of the PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes enum. +func (e PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes) Valid() bool { + switch e { + case Code: + return true + default: + return false + } +} + +// Defines values for PostApiAuthOauth2UpdateClientJSONBodyUpdateType. +const ( + Native PostApiAuthOauth2UpdateClientJSONBodyUpdateType = "native" + UserAgentBased PostApiAuthOauth2UpdateClientJSONBodyUpdateType = "user-agent-based" + Web PostApiAuthOauth2UpdateClientJSONBodyUpdateType = "web" +) + +// Valid indicates whether the value is a known member of the PostApiAuthOauth2UpdateClientJSONBodyUpdateType enum. +func (e PostApiAuthOauth2UpdateClientJSONBodyUpdateType) Valid() bool { + switch e { + case Native: + return true + case UserAgentBased: + return true + case Web: + return true default: return false } @@ -2500,6 +2707,123 @@ func (e SignInEmail200JSONResponseBodyRedirect) Valid() bool { } } +// Defines values for SocialSignInJSONBodyProvider0. +const ( + SocialSignInJSONBodyProvider0Apple SocialSignInJSONBodyProvider0 = "apple" + SocialSignInJSONBodyProvider0Atlassian SocialSignInJSONBodyProvider0 = "atlassian" + SocialSignInJSONBodyProvider0Cognito SocialSignInJSONBodyProvider0 = "cognito" + SocialSignInJSONBodyProvider0Discord SocialSignInJSONBodyProvider0 = "discord" + SocialSignInJSONBodyProvider0Dropbox SocialSignInJSONBodyProvider0 = "dropbox" + SocialSignInJSONBodyProvider0Facebook SocialSignInJSONBodyProvider0 = "facebook" + SocialSignInJSONBodyProvider0Figma SocialSignInJSONBodyProvider0 = "figma" + SocialSignInJSONBodyProvider0Github SocialSignInJSONBodyProvider0 = "github" + SocialSignInJSONBodyProvider0Gitlab SocialSignInJSONBodyProvider0 = "gitlab" + SocialSignInJSONBodyProvider0Google SocialSignInJSONBodyProvider0 = "google" + SocialSignInJSONBodyProvider0Huggingface SocialSignInJSONBodyProvider0 = "huggingface" + SocialSignInJSONBodyProvider0Kakao SocialSignInJSONBodyProvider0 = "kakao" + SocialSignInJSONBodyProvider0Kick SocialSignInJSONBodyProvider0 = "kick" + SocialSignInJSONBodyProvider0Line SocialSignInJSONBodyProvider0 = "line" + SocialSignInJSONBodyProvider0Linear SocialSignInJSONBodyProvider0 = "linear" + SocialSignInJSONBodyProvider0Linkedin SocialSignInJSONBodyProvider0 = "linkedin" + SocialSignInJSONBodyProvider0Microsoft SocialSignInJSONBodyProvider0 = "microsoft" + SocialSignInJSONBodyProvider0Naver SocialSignInJSONBodyProvider0 = "naver" + SocialSignInJSONBodyProvider0Notion SocialSignInJSONBodyProvider0 = "notion" + SocialSignInJSONBodyProvider0Paybin SocialSignInJSONBodyProvider0 = "paybin" + SocialSignInJSONBodyProvider0Paypal SocialSignInJSONBodyProvider0 = "paypal" + SocialSignInJSONBodyProvider0Polar SocialSignInJSONBodyProvider0 = "polar" + SocialSignInJSONBodyProvider0Railway SocialSignInJSONBodyProvider0 = "railway" + SocialSignInJSONBodyProvider0Reddit SocialSignInJSONBodyProvider0 = "reddit" + SocialSignInJSONBodyProvider0Roblox SocialSignInJSONBodyProvider0 = "roblox" + SocialSignInJSONBodyProvider0Salesforce SocialSignInJSONBodyProvider0 = "salesforce" + SocialSignInJSONBodyProvider0Slack SocialSignInJSONBodyProvider0 = "slack" + SocialSignInJSONBodyProvider0Spotify SocialSignInJSONBodyProvider0 = "spotify" + SocialSignInJSONBodyProvider0Tiktok SocialSignInJSONBodyProvider0 = "tiktok" + SocialSignInJSONBodyProvider0Twitch SocialSignInJSONBodyProvider0 = "twitch" + SocialSignInJSONBodyProvider0Twitter SocialSignInJSONBodyProvider0 = "twitter" + SocialSignInJSONBodyProvider0Vercel SocialSignInJSONBodyProvider0 = "vercel" + SocialSignInJSONBodyProvider0Vk SocialSignInJSONBodyProvider0 = "vk" + SocialSignInJSONBodyProvider0Wechat SocialSignInJSONBodyProvider0 = "wechat" + SocialSignInJSONBodyProvider0Zoom SocialSignInJSONBodyProvider0 = "zoom" +) + +// Valid indicates whether the value is a known member of the SocialSignInJSONBodyProvider0 enum. +func (e SocialSignInJSONBodyProvider0) Valid() bool { + switch e { + case SocialSignInJSONBodyProvider0Apple: + return true + case SocialSignInJSONBodyProvider0Atlassian: + return true + case SocialSignInJSONBodyProvider0Cognito: + return true + case SocialSignInJSONBodyProvider0Discord: + return true + case SocialSignInJSONBodyProvider0Dropbox: + return true + case SocialSignInJSONBodyProvider0Facebook: + return true + case SocialSignInJSONBodyProvider0Figma: + return true + case SocialSignInJSONBodyProvider0Github: + return true + case SocialSignInJSONBodyProvider0Gitlab: + return true + case SocialSignInJSONBodyProvider0Google: + return true + case SocialSignInJSONBodyProvider0Huggingface: + return true + case SocialSignInJSONBodyProvider0Kakao: + return true + case SocialSignInJSONBodyProvider0Kick: + return true + case SocialSignInJSONBodyProvider0Line: + return true + case SocialSignInJSONBodyProvider0Linear: + return true + case SocialSignInJSONBodyProvider0Linkedin: + return true + case SocialSignInJSONBodyProvider0Microsoft: + return true + case SocialSignInJSONBodyProvider0Naver: + return true + case SocialSignInJSONBodyProvider0Notion: + return true + case SocialSignInJSONBodyProvider0Paybin: + return true + case SocialSignInJSONBodyProvider0Paypal: + return true + case SocialSignInJSONBodyProvider0Polar: + return true + case SocialSignInJSONBodyProvider0Railway: + return true + case SocialSignInJSONBodyProvider0Reddit: + return true + case SocialSignInJSONBodyProvider0Roblox: + return true + case SocialSignInJSONBodyProvider0Salesforce: + return true + case SocialSignInJSONBodyProvider0Slack: + return true + case SocialSignInJSONBodyProvider0Spotify: + return true + case SocialSignInJSONBodyProvider0Tiktok: + return true + case SocialSignInJSONBodyProvider0Twitch: + return true + case SocialSignInJSONBodyProvider0Twitter: + return true + case SocialSignInJSONBodyProvider0Vercel: + return true + case SocialSignInJSONBodyProvider0Vk: + return true + case SocialSignInJSONBodyProvider0Wechat: + return true + case SocialSignInJSONBodyProvider0Zoom: + return true + default: + return false + } +} + // Defines values for ListBackgroundJobsParamsStatus. const ( ListBackgroundJobsParamsStatusCanceled ListBackgroundJobsParamsStatus = "canceled" @@ -2848,6 +3172,231 @@ func (e CreateObject201JSONResponseBodyUploadMode) Valid() bool { } } +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowAbortMethod. +const ( + DELETE CreateObject201JSONResponseBodyUploadWorkflowAbortMethod = "DELETE" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowAbortMethod enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowAbortMethod) Valid() bool { + switch e { + case DELETE: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId. +const ( + AbortObjectUpload CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId = "abortObjectUpload" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId) Valid() bool { + switch e { + case AbortObjectUpload: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod. +const ( + CreateObject201JSONResponseBodyUploadWorkflowCompleteMethodPOST CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod = "POST" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod) Valid() bool { + switch e { + case CreateObject201JSONResponseBodyUploadWorkflowCompleteMethodPOST: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId. +const ( + CompleteObjectUpload CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId = "completeObjectUpload" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId) Valid() bool { + switch e { + case CompleteObjectUpload: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField. +const ( + Parts CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField = "parts" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField) Valid() bool { + switch e { + case Parts: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod. +const ( + CreateObject201JSONResponseBodyUploadWorkflowRePresignMethodPOST CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod = "POST" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod) Valid() bool { + switch e { + case CreateObject201JSONResponseBodyUploadWorkflowRePresignMethodPOST: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId. +const ( + PresignObjectUploadParts CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId = "presignObjectUploadParts" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId) Valid() bool { + switch e { + case PresignObjectUploadParts: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField. +const ( + PartNumbers CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField = "partNumbers" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField) Valid() bool { + switch e { + case PartNumbers: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField. +const ( + PartsLength CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField = "parts[].length" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField) Valid() bool { + switch e { + case PartsLength: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader. +const ( + ETag CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader = "ETag" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader) Valid() bool { + switch e { + case ETag: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField. +const ( + PartsOffset CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField = "parts[].offset" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField) Valid() bool { + switch e { + case PartsOffset: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField. +const ( + PartsHeaders CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField = "parts[].headers" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField) Valid() bool { + switch e { + case PartsHeaders: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadMethod. +const ( + PUT CreateObject201JSONResponseBodyUploadWorkflowUploadMethod = "PUT" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadMethod enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadMethod) Valid() bool { + switch e { + case PUT: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField. +const ( + PartsUrl CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField = "parts[].url" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField) Valid() bool { + switch e { + case PartsUrl: + return true + default: + return false + } +} + +// Defines values for CreateObject201JSONResponseBodyUploadWorkflowVersion. +const ( + N1 CreateObject201JSONResponseBodyUploadWorkflowVersion = "1" +) + +// Valid indicates whether the value is a known member of the CreateObject201JSONResponseBodyUploadWorkflowVersion enum. +func (e CreateObject201JSONResponseBodyUploadWorkflowVersion) Valid() bool { + switch e { + case N1: + return true + default: + return false + } +} + // Defines values for UpdateObjectJSONBodyOnConflict. const ( UpdateObjectJSONBodyOnConflictFail UpdateObjectJSONBodyOnConflict = "fail" @@ -3015,13 +3564,13 @@ func (e VerifySharePassword200JSONResponseBodyOk) Valid() bool { // Defines values for RevokeShareJSONBodyStatus. const ( - RevokeShareJSONBodyStatusRevoked RevokeShareJSONBodyStatus = "revoked" + Revoked RevokeShareJSONBodyStatus = "revoked" ) // Valid indicates whether the value is a known member of the RevokeShareJSONBodyStatus enum. func (e RevokeShareJSONBodyStatus) Valid() bool { switch e { - case RevokeShareJSONBodyStatusRevoked: + case Revoked: return true default: return false @@ -3120,16 +3669,16 @@ func (e GetAdminAnalyticsTrafficParamsTimeZone) Valid() bool { // Defines values for ListAnnouncementsParamsScope. const ( - ListAnnouncementsParamsScopeActive ListAnnouncementsParamsScope = "active" - ListAnnouncementsParamsScopeAll ListAnnouncementsParamsScope = "all" + Active ListAnnouncementsParamsScope = "active" + All ListAnnouncementsParamsScope = "all" ) // Valid indicates whether the value is a known member of the ListAnnouncementsParamsScope enum. func (e ListAnnouncementsParamsScope) Valid() bool { switch e { - case ListAnnouncementsParamsScopeActive: + case Active: return true - case ListAnnouncementsParamsScopeAll: + case All: return true default: return false @@ -3339,19 +3888,19 @@ func (e ListStorageUsageItemsParamsCategory) Valid() bool { // Defines values for ListStorageUsageItemsParamsSortBy. const ( - Name ListStorageUsageItemsParamsSortBy = "name" - Size ListStorageUsageItemsParamsSortBy = "size" - UpdatedAt ListStorageUsageItemsParamsSortBy = "updatedAt" + ListStorageUsageItemsParamsSortByName ListStorageUsageItemsParamsSortBy = "name" + ListStorageUsageItemsParamsSortBySize ListStorageUsageItemsParamsSortBy = "size" + ListStorageUsageItemsParamsSortByUpdatedAt ListStorageUsageItemsParamsSortBy = "updatedAt" ) // Valid indicates whether the value is a known member of the ListStorageUsageItemsParamsSortBy enum. func (e ListStorageUsageItemsParamsSortBy) Valid() bool { switch e { - case Name: + case ListStorageUsageItemsParamsSortByName: return true - case Size: + case ListStorageUsageItemsParamsSortBySize: return true - case UpdatedAt: + case ListStorageUsageItemsParamsSortByUpdatedAt: return true default: return false @@ -3360,16 +3909,16 @@ func (e ListStorageUsageItemsParamsSortBy) Valid() bool { // Defines values for ListStorageUsageItemsParamsSortDir. const ( - Asc ListStorageUsageItemsParamsSortDir = "asc" - Desc ListStorageUsageItemsParamsSortDir = "desc" + ListStorageUsageItemsParamsSortDirAsc ListStorageUsageItemsParamsSortDir = "asc" + ListStorageUsageItemsParamsSortDirDesc ListStorageUsageItemsParamsSortDir = "desc" ) // Valid indicates whether the value is a known member of the ListStorageUsageItemsParamsSortDir enum. func (e ListStorageUsageItemsParamsSortDir) Valid() bool { switch e { - case Asc: + case ListStorageUsageItemsParamsSortDirAsc: return true - case Desc: + case ListStorageUsageItemsParamsSortDirDesc: return true default: return false @@ -3447,13 +3996,13 @@ func (e CreateTeamInviteLinkJSONBodyRole) Valid() bool { // Defines values for JoinTeam200JSONResponseBodyOk. const ( - True JoinTeam200JSONResponseBodyOk = true + JoinTeam200JSONResponseBodyOkTrue JoinTeam200JSONResponseBodyOk = true ) // Valid indicates whether the value is a known member of the JoinTeam200JSONResponseBodyOk enum. func (e JoinTeam200JSONResponseBodyOk) Valid() bool { switch e { - case True: + case JoinTeam200JSONResponseBodyOkTrue: return true default: return false @@ -3496,273 +4045,6 @@ func (e GrantUserEntitlementJSONBodyResourceType) Valid() bool { } } -// Defines values for ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes. -const ( - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsCreate ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "objects:create" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsDelete ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "objects:delete" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsRead ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "objects:read" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsUpdate ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "objects:update" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesQuotaRead ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "quota:read" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesCreate ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "shares:create" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesDelete ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "shares:delete" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesRead ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "shares:read" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesStorageUsageRead ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes = "storage-usage:read" -) - -// Valid indicates whether the value is a known member of the ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes enum. -func (e ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes) Valid() bool { - switch e { - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsCreate: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsDelete: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsRead: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesObjectsUpdate: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesQuotaRead: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesCreate: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesDelete: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesSharesRead: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopesStorageUsageRead: - return true - default: - return false - } -} - -// Defines values for ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus. -const ( - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusActive ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus = "active" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusExpired ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus = "expired" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusInaccessible ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus = "inaccessible" - ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusRevoked ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus = "revoked" -) - -// Valid indicates whether the value is a known member of the ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus enum. -func (e ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus) Valid() bool { - switch e { - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusActive: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusExpired: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusInaccessible: - return true - case ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatusRevoked: - return true - default: - return false - } -} - -// Defines values for CreateWorkspaceAgentApiKeyJSONBodyScopes. -const ( - CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsCreate CreateWorkspaceAgentApiKeyJSONBodyScopes = "objects:create" - CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsDelete CreateWorkspaceAgentApiKeyJSONBodyScopes = "objects:delete" - CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsRead CreateWorkspaceAgentApiKeyJSONBodyScopes = "objects:read" - CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsUpdate CreateWorkspaceAgentApiKeyJSONBodyScopes = "objects:update" - CreateWorkspaceAgentApiKeyJSONBodyScopesQuotaRead CreateWorkspaceAgentApiKeyJSONBodyScopes = "quota:read" - CreateWorkspaceAgentApiKeyJSONBodyScopesSharesCreate CreateWorkspaceAgentApiKeyJSONBodyScopes = "shares:create" - CreateWorkspaceAgentApiKeyJSONBodyScopesSharesDelete CreateWorkspaceAgentApiKeyJSONBodyScopes = "shares:delete" - CreateWorkspaceAgentApiKeyJSONBodyScopesSharesRead CreateWorkspaceAgentApiKeyJSONBodyScopes = "shares:read" - CreateWorkspaceAgentApiKeyJSONBodyScopesStorageUsageRead CreateWorkspaceAgentApiKeyJSONBodyScopes = "storage-usage:read" -) - -// Valid indicates whether the value is a known member of the CreateWorkspaceAgentApiKeyJSONBodyScopes enum. -func (e CreateWorkspaceAgentApiKeyJSONBodyScopes) Valid() bool { - switch e { - case CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsCreate: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsDelete: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsRead: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesObjectsUpdate: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesQuotaRead: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesSharesCreate: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesSharesDelete: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesSharesRead: - return true - case CreateWorkspaceAgentApiKeyJSONBodyScopesStorageUsageRead: - return true - default: - return false - } -} - -// Defines values for CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes. -const ( - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsCreate CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:create" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsDelete CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:delete" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsRead CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:read" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsUpdate CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:update" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesQuotaRead CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "quota:read" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesCreate CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:create" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesDelete CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:delete" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesRead CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:read" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesStorageUsageRead CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "storage-usage:read" -) - -// Valid indicates whether the value is a known member of the CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes enum. -func (e CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes) Valid() bool { - switch e { - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsCreate: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsDelete: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsRead: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesObjectsUpdate: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesQuotaRead: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesCreate: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesDelete: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesSharesRead: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopesStorageUsageRead: - return true - default: - return false - } -} - -// Defines values for CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus. -const ( - CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusActive CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "active" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusExpired CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "expired" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusInaccessible CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "inaccessible" - CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusRevoked CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "revoked" -) - -// Valid indicates whether the value is a known member of the CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus enum. -func (e CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus) Valid() bool { - switch e { - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusActive: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusExpired: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusInaccessible: - return true - case CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatusRevoked: - return true - default: - return false - } -} - -// Defines values for RotateWorkspaceAgentApiKeyJSONBodyScopes. -const ( - RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsCreate RotateWorkspaceAgentApiKeyJSONBodyScopes = "objects:create" - RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsDelete RotateWorkspaceAgentApiKeyJSONBodyScopes = "objects:delete" - RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsRead RotateWorkspaceAgentApiKeyJSONBodyScopes = "objects:read" - RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsUpdate RotateWorkspaceAgentApiKeyJSONBodyScopes = "objects:update" - RotateWorkspaceAgentApiKeyJSONBodyScopesQuotaRead RotateWorkspaceAgentApiKeyJSONBodyScopes = "quota:read" - RotateWorkspaceAgentApiKeyJSONBodyScopesSharesCreate RotateWorkspaceAgentApiKeyJSONBodyScopes = "shares:create" - RotateWorkspaceAgentApiKeyJSONBodyScopesSharesDelete RotateWorkspaceAgentApiKeyJSONBodyScopes = "shares:delete" - RotateWorkspaceAgentApiKeyJSONBodyScopesSharesRead RotateWorkspaceAgentApiKeyJSONBodyScopes = "shares:read" - RotateWorkspaceAgentApiKeyJSONBodyScopesStorageUsageRead RotateWorkspaceAgentApiKeyJSONBodyScopes = "storage-usage:read" -) - -// Valid indicates whether the value is a known member of the RotateWorkspaceAgentApiKeyJSONBodyScopes enum. -func (e RotateWorkspaceAgentApiKeyJSONBodyScopes) Valid() bool { - switch e { - case RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsCreate: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsDelete: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsRead: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesObjectsUpdate: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesQuotaRead: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesSharesCreate: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesSharesDelete: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesSharesRead: - return true - case RotateWorkspaceAgentApiKeyJSONBodyScopesStorageUsageRead: - return true - default: - return false - } -} - -// Defines values for RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes. -const ( - ObjectsCreate RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:create" - ObjectsDelete RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:delete" - ObjectsRead RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:read" - ObjectsUpdate RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "objects:update" - QuotaRead RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "quota:read" - SharesCreate RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:create" - SharesDelete RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:delete" - SharesRead RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "shares:read" - StorageUsageRead RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes = "storage-usage:read" -) - -// Valid indicates whether the value is a known member of the RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes enum. -func (e RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes) Valid() bool { - switch e { - case ObjectsCreate: - return true - case ObjectsDelete: - return true - case ObjectsRead: - return true - case ObjectsUpdate: - return true - case QuotaRead: - return true - case SharesCreate: - return true - case SharesDelete: - return true - case SharesRead: - return true - case StorageUsageRead: - return true - default: - return false - } -} - -// Defines values for RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus. -const ( - Active RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "active" - Expired RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "expired" - Inaccessible RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "inaccessible" - Revoked RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus = "revoked" -) - -// Valid indicates whether the value is a known member of the RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus enum. -func (e RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus) Valid() bool { - switch e { - case Active: - return true - case Expired: - return true - case Inaccessible: - return true - case Revoked: - return true - default: - return false - } -} - // ActivityPage defines model for ActivityPage. type ActivityPage struct { Items []AuditEvent `json:"items"` @@ -4640,11 +4922,21 @@ type AuthProvider struct { // AuthProviderList defines model for AuthProviderList. type AuthProviderList struct { - CallbackBaseUri string `json:"callbackBaseUri"` - Items []AuthProvider `json:"items"` - Page int `json:"page"` - PageSize int `json:"pageSize"` - Total int `json:"total"` + CallbackBaseUri string `json:"callbackBaseUri"` + Items []AuthProvider `json:"items"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + RegisteredApplications []struct { + ClientId string `json:"clientId"` + CreatedAt string `json:"createdAt"` + Disabled bool `json:"disabled"` + GrantTypes []string `json:"grantTypes"` + Name string `json:"name"` + RedirectUris []string `json:"redirectUris"` + Scopes []string `json:"scopes"` + Uri *string `json:"uri"` + } `json:"registeredApplications"` + Total int `json:"total"` } // BackgroundJob defines model for BackgroundJob. @@ -6065,15 +6357,26 @@ type BanUserJSONBody struct { // CreateUserJSONBody defines parameters for CreateUser. type CreateUserJSONBody struct { - Data *string `json:"data,omitempty"` + Data *map[string]interface{} `json:"data,omitempty"` // Email The email of the user Email string `json:"email"` // Name The name of the user - Name string `json:"name"` - Password *string `json:"password,omitempty"` - Role *string `json:"role,omitempty"` + Name string `json:"name"` + Password *string `json:"password,omitempty"` + Role *CreateUserJSONBody_Role `json:"role,omitempty"` +} + +// CreateUserJSONBodyRole0 defines parameters for CreateUser. +type CreateUserJSONBodyRole0 = string + +// CreateUserJSONBodyRole1 defines parameters for CreateUser. +type CreateUserJSONBodyRole1 = []string + +// CreateUserJSONBody_Role defines parameters for CreateUser. +type CreateUserJSONBody_Role struct { + union json.RawMessage } // GetUserParams defines parameters for GetUser. @@ -6101,18 +6404,78 @@ type AdminListUserSessionsJSONBody struct { // ListUsersParams defines parameters for ListUsers. type ListUsersParams struct { - SearchValue *string `form:"searchValue,omitempty" json:"searchValue,omitempty"` - SearchField *string `form:"searchField,omitempty" json:"searchField,omitempty"` - SearchOperator *string `form:"searchOperator,omitempty" json:"searchOperator,omitempty"` - Limit *string `form:"limit,omitempty" json:"limit,omitempty"` - Offset *string `form:"offset,omitempty" json:"offset,omitempty"` - SortBy *string `form:"sortBy,omitempty" json:"sortBy,omitempty"` - SortDirection *string `form:"sortDirection,omitempty" json:"sortDirection,omitempty"` - FilterField *string `form:"filterField,omitempty" json:"filterField,omitempty"` - FilterValue *string `form:"filterValue,omitempty" json:"filterValue,omitempty"` - FilterOperator *string `form:"filterOperator,omitempty" json:"filterOperator,omitempty"` + SearchValue *string `form:"searchValue,omitempty" json:"searchValue,omitempty"` + SearchField *ListUsersParamsSearchField `form:"searchField,omitempty" json:"searchField,omitempty"` + SearchOperator *ListUsersParamsSearchOperator `form:"searchOperator,omitempty" json:"searchOperator,omitempty"` + Limit *struct { + union json.RawMessage + } `form:"limit,omitempty" json:"limit,omitempty"` + Offset *struct { + union json.RawMessage + } `form:"offset,omitempty" json:"offset,omitempty"` + SortBy *string `form:"sortBy,omitempty" json:"sortBy,omitempty"` + SortDirection *ListUsersParamsSortDirection `form:"sortDirection,omitempty" json:"sortDirection,omitempty"` + FilterField *string `form:"filterField,omitempty" json:"filterField,omitempty"` + FilterValue *struct { + union json.RawMessage + } `form:"filterValue,omitempty" json:"filterValue,omitempty"` + FilterOperator *ListUsersParamsFilterOperator `form:"filterOperator,omitempty" json:"filterOperator,omitempty"` } +// ListUsersParamsSearchField defines parameters for ListUsers. +type ListUsersParamsSearchField string + +// ListUsersParamsSearchOperator defines parameters for ListUsers. +type ListUsersParamsSearchOperator string + +// ListUsersParamsLimit0 defines parameters for ListUsers. +type ListUsersParamsLimit0 = string + +// ListUsersParamsLimit1 defines parameters for ListUsers. +type ListUsersParamsLimit1 = float32 + +// ListUsersParamsOffset0 defines parameters for ListUsers. +type ListUsersParamsOffset0 = string + +// ListUsersParamsOffset1 defines parameters for ListUsers. +type ListUsersParamsOffset1 = float32 + +// ListUsersParamsSortDirection defines parameters for ListUsers. +type ListUsersParamsSortDirection string + +// ListUsersParamsFilterValue0 defines parameters for ListUsers. +type ListUsersParamsFilterValue0 struct { + union json.RawMessage +} + +// ListUsersParamsFilterValue00 defines parameters for ListUsers. +type ListUsersParamsFilterValue00 struct { + union json.RawMessage +} + +// ListUsersParamsFilterValue000 defines parameters for ListUsers. +type ListUsersParamsFilterValue000 struct { + union json.RawMessage +} + +// ListUsersParamsFilterValue0000 defines parameters for ListUsers. +type ListUsersParamsFilterValue0000 = string + +// ListUsersParamsFilterValue0001 defines parameters for ListUsers. +type ListUsersParamsFilterValue0001 = float32 + +// ListUsersParamsFilterValue001 defines parameters for ListUsers. +type ListUsersParamsFilterValue001 = bool + +// ListUsersParamsFilterValue01 defines parameters for ListUsers. +type ListUsersParamsFilterValue01 = []string + +// ListUsersParamsFilterValue1 defines parameters for ListUsers. +type ListUsersParamsFilterValue1 = []float32 + +// ListUsersParamsFilterOperator defines parameters for ListUsers. +type ListUsersParamsFilterOperator string + // RemoveUserJSONBody defines parameters for RemoveUser. type RemoveUserJSONBody struct { // UserId The user id @@ -6134,12 +6497,23 @@ type RevokeUserSessionsJSONBody struct { // SetUserRoleJSONBody defines parameters for SetUserRole. type SetUserRoleJSONBody struct { // Role The role to set, this can be a string or an array of strings. Eg: `admin` or `[admin, user]` - Role string `json:"role"` + Role SetUserRoleJSONBody_Role `json:"role"` // UserId The user id UserId string `json:"userId"` } +// SetUserRoleJSONBodyRole0 defines parameters for SetUserRole. +type SetUserRoleJSONBodyRole0 = string + +// SetUserRoleJSONBodyRole1 defines parameters for SetUserRole. +type SetUserRoleJSONBodyRole1 = []string + +// SetUserRoleJSONBody_Role defines parameters for SetUserRole. +type SetUserRoleJSONBody_Role struct { + union json.RawMessage +} + // SetUserPasswordJSONBody defines parameters for SetUserPassword. type SetUserPasswordJSONBody struct { // NewPassword The new password @@ -6158,7 +6532,7 @@ type UnbanUserJSONBody struct { // AdminUpdateUserJSONBody defines parameters for AdminUpdateUser. type AdminUpdateUserJSONBody struct { // Data The user data to update - Data string `json:"data"` + Data map[string]interface{} `json:"data"` // UserId The user id UserId string `json:"userId"` @@ -6167,9 +6541,11 @@ type AdminUpdateUserJSONBody struct { // PostApiAuthApiKeyCreateJSONBody defines parameters for PostApiAuthApiKeyCreate. type PostApiAuthApiKeyCreateJSONBody struct { // ConfigId The configuration ID to use for the API key. If not provided, the default configuration will be used. - ConfigId *string `json:"configId,omitempty"` - ExpiresIn string `json:"expiresIn"` - Metadata *string `json:"metadata,omitempty"` + ConfigId *string `json:"configId,omitempty"` + + // ExpiresIn Expiration time of the Api Key in seconds + ExpiresIn *float32 `json:"expiresIn,omitempty"` + Metadata interface{} `json:"metadata,omitempty"` // Name Name of the Api Key Name *string `json:"name,omitempty"` @@ -6178,7 +6554,7 @@ type PostApiAuthApiKeyCreateJSONBody struct { OrganizationId *string `json:"organizationId,omitempty"` // Permissions Permissions of the Api Key. - Permissions *string `json:"permissions,omitempty"` + Permissions *map[string][]string `json:"permissions,omitempty"` // Prefix Prefix of the Api Key Prefix *string `json:"prefix,omitempty"` @@ -6197,7 +6573,9 @@ type PostApiAuthApiKeyCreateJSONBody struct { // RefillInterval Interval to refill the Api Key in milliseconds. server-only. Eg: 1000 RefillInterval *float32 `json:"refillInterval,omitempty"` - Remaining string `json:"remaining"` + + // Remaining Remaining number of requests. Server side only + Remaining *float32 `json:"remaining,omitempty"` // UserId User Id of the user that the Api Key belongs to. server-only. Eg: "user-id" UserId *string `json:"userId,omitempty"` @@ -6221,16 +6599,20 @@ type PostApiAuthApiKeyUpdateJSONBody struct { ConfigId *string `json:"configId,omitempty"` // Enabled Whether the Api Key is enabled or not - Enabled *bool `json:"enabled,omitempty"` - ExpiresIn string `json:"expiresIn"` + Enabled *bool `json:"enabled,omitempty"` + + // ExpiresIn Expiration time of the Api Key in seconds + ExpiresIn *float32 `json:"expiresIn,omitempty"` // KeyId The id of the Api Key - KeyId string `json:"keyId"` - Metadata *string `json:"metadata,omitempty"` + KeyId string `json:"keyId"` + Metadata interface{} `json:"metadata,omitempty"` // Name The name of the key - Name *string `json:"name,omitempty"` - Permissions string `json:"permissions"` + Name *string `json:"name,omitempty"` + + // Permissions Update the permissions on the API Key. server-only. + Permissions *map[string][]string `json:"permissions,omitempty"` // RateLimitEnabled Whether the key has rate limiting enabled. RateLimitEnabled *bool `json:"rateLimitEnabled,omitempty"` @@ -6255,7 +6637,15 @@ type PostApiAuthApiKeyUpdateJSONBody struct { } // PostApiAuthCallbackIdJSONBody defines parameters for PostApiAuthCallbackId. -type PostApiAuthCallbackIdJSONBody = map[string]interface{} +type PostApiAuthCallbackIdJSONBody struct { + Code *string `json:"code,omitempty"` + DeviceId *string `json:"device_id,omitempty"` + Error *string `json:"error,omitempty"` + ErrorDescription *string `json:"error_description,omitempty"` + Iss *string `json:"iss,omitempty"` + State *string `json:"state,omitempty"` + User *string `json:"user,omitempty"` +} // ChangeEmailJSONBody defines parameters for ChangeEmail. type ChangeEmailJSONBody struct { @@ -6326,6 +6716,9 @@ type PostApiAuthDeviceCodeJSONBody struct { // Scope Space-separated list of scopes Scope *string `json:"scope,omitempty"` + + // UserId The user ID to which the device code should be pre-bound. + UserId *string `json:"user_id,omitempty"` } // PostApiAuthDeviceCode400JSONResponseBodyError defines parameters for PostApiAuthDeviceCode. @@ -6346,7 +6739,7 @@ type PostApiAuthDeviceTokenJSONBody struct { DeviceCode string `json:"device_code"` // GrantType The grant type for device flow - GrantType string `json:"grant_type"` + GrantType interface{} `json:"grant_type"` } // PostApiAuthDeviceToken400JSONResponseBodyError defines parameters for PostApiAuthDeviceToken. @@ -6354,11 +6747,22 @@ type PostApiAuthDeviceToken400JSONResponseBodyError string // PostApiAuthGetAccessTokenJSONBody defines parameters for PostApiAuthGetAccessToken. type PostApiAuthGetAccessTokenJSONBody struct { - // AccountId The account ID associated with the refresh token - AccountId *string `json:"accountId,omitempty"` + union json.RawMessage +} - // ProviderId The provider ID for the OAuth provider - ProviderId string `json:"providerId"` +// PostApiAuthGetAccessTokenJSONBody0 defines parameters for PostApiAuthGetAccessToken. +type PostApiAuthGetAccessTokenJSONBody0 struct { + // AccountId The Better Auth account ID + AccountId string `json:"accountId"` + + // UserId The user ID associated with the account + UserId *string `json:"userId,omitempty"` +} + +// PostApiAuthGetAccessTokenJSONBody1 defines parameters for PostApiAuthGetAccessToken. +type PostApiAuthGetAccessTokenJSONBody1 struct { + // UseAccountCookie Select the current OAuth account from its signed cookie + UseAccountCookie interface{} `json:"useAccountCookie"` // UserId The user ID associated with the account UserId *string `json:"userId,omitempty"` @@ -6373,9 +6777,15 @@ type PostApiAuthIsUsernameAvailableJSONBody struct { Username string `json:"username"` } +// GetJSONWebKeySet200JSONResponseBodyKeysUse defines parameters for GetJSONWebKeySet. +type GetJSONWebKeySet200JSONResponseBodyKeysUse string + // LinkSocialAccountJSONBody defines parameters for LinkSocialAccount. type LinkSocialAccountJSONBody struct { - AdditionalData *string `json:"additionalData,omitempty"` + AdditionalData *map[string]interface{} `json:"additionalData,omitempty"` + + // AdditionalParams Extra query parameters to append to the provider authorization URL (e.g. Cognito identity_provider, Google hd). + AdditionalParams *map[string]string `json:"additionalParams,omitempty"` // CallbackURL The URL to redirect to after the user has signed in CallbackURL *string `json:"callbackURL,omitempty"` @@ -6386,34 +6796,47 @@ type LinkSocialAccountJSONBody struct { // ErrorCallbackURL The URL to redirect to if there is an error during the link process ErrorCallbackURL *string `json:"errorCallbackURL,omitempty"` IdToken *struct { - AccessToken *string `json:"accessToken,omitempty"` - Nonce *string `json:"nonce,omitempty"` - RefreshToken *string `json:"refreshToken,omitempty"` - Scopes *[]interface{} `json:"scopes,omitempty"` - Token string `json:"token"` + AccessToken *string `json:"accessToken,omitempty"` + Nonce *string `json:"nonce,omitempty"` + RefreshToken *string `json:"refreshToken,omitempty"` + Token string `json:"token"` } `json:"idToken,omitempty"` - Provider string `json:"provider"` - RequestSignUp *bool `json:"requestSignUp,omitempty"` + + // LoginHint The login hint to use for the authorization code request + LoginHint *string `json:"loginHint,omitempty"` + Provider LinkSocialAccountJSONBody_Provider `json:"provider"` + RequestSignUp *bool `json:"requestSignUp,omitempty"` // Scopes Additional scopes to request from the provider - Scopes *[]interface{} `json:"scopes,omitempty"` + Scopes *[]string `json:"scopes,omitempty"` +} + +// LinkSocialAccountJSONBodyProvider0 defines parameters for LinkSocialAccount. +type LinkSocialAccountJSONBodyProvider0 string + +// LinkSocialAccountJSONBodyProvider1 defines parameters for LinkSocialAccount. +type LinkSocialAccountJSONBodyProvider1 = string + +// LinkSocialAccountJSONBody_Provider defines parameters for LinkSocialAccount. +type LinkSocialAccountJSONBody_Provider struct { + union json.RawMessage } // GetApiAuthOauth2AuthorizeParams defines parameters for GetApiAuthOauth2Authorize. type GetApiAuthOauth2AuthorizeParams struct { - // ResponseType OAuth2 response type (e.g., 'code') + // ResponseType OAuth 2.1 response type (e.g., 'code') ResponseType *string `form:"response_type,omitempty" json:"response_type,omitempty"` - // ClientId OAuth2 client ID + // ClientId OAuth 2.1 client ID ClientId string `form:"client_id" json:"client_id"` - // RedirectUri OAuth2 redirect URI + // RedirectUri OAuth 2.1 redirect URI RedirectUri *string `form:"redirect_uri,omitempty" json:"redirect_uri,omitempty"` - // Scope OAuth2 scopes (space-separated) + // Scope OAuth 2.1 scopes (space-separated) Scope *string `form:"scope,omitempty" json:"scope,omitempty"` - // State OAuth2 state parameter + // State OAuth 2.1 state parameter State *string `form:"state,omitempty" json:"state,omitempty"` // RequestUri Pushed Authorization Request URI referencing stored parameters @@ -6428,17 +6851,53 @@ type GetApiAuthOauth2AuthorizeParams struct { // Nonce OpenID Connect nonce Nonce *string `form:"nonce,omitempty" json:"nonce,omitempty"` + // MaxAge Maximum authentication age in seconds; forces re-authentication when exceeded + MaxAge *int `form:"max_age,omitempty" json:"max_age,omitempty"` + + // Resource Requested protected resource(s) for the access token. May be supplied multiple times as repeated 'resource' query parameters (RFC 8707) or as an array of strings. + Resource *[]string `form:"resource,omitempty" json:"resource,omitempty"` + // Prompt OAuth2 prompt parameter Prompt *string `form:"prompt,omitempty" json:"prompt,omitempty"` } -// GetApiAuthOauth2CallbackProviderIdParams defines parameters for GetApiAuthOauth2CallbackProviderId. -type GetApiAuthOauth2CallbackProviderIdParams struct { - Code *string `form:"code,omitempty" json:"code,omitempty"` - Error *string `form:"error,omitempty" json:"error,omitempty"` - ErrorDescription *string `form:"error_description,omitempty" json:"error_description,omitempty"` - State *string `form:"state,omitempty" json:"state,omitempty"` - Iss *string `form:"iss,omitempty" json:"iss,omitempty"` +// PostApiAuthOauth2AuthorizeParams defines parameters for PostApiAuthOauth2Authorize. +type PostApiAuthOauth2AuthorizeParams struct { + // ResponseType OAuth 2.1 response type (e.g., 'code') + ResponseType *string `form:"response_type,omitempty" json:"response_type,omitempty"` + + // ClientId OAuth 2.1 client ID + ClientId string `form:"client_id" json:"client_id"` + + // RedirectUri OAuth 2.1 redirect URI + RedirectUri *string `form:"redirect_uri,omitempty" json:"redirect_uri,omitempty"` + + // Scope OAuth 2.1 scopes (space-separated) + Scope *string `form:"scope,omitempty" json:"scope,omitempty"` + + // State OAuth 2.1 state parameter + State *string `form:"state,omitempty" json:"state,omitempty"` + + // RequestUri Pushed Authorization Request URI referencing stored parameters + RequestUri *string `form:"request_uri,omitempty" json:"request_uri,omitempty"` + + // CodeChallenge PKCE code challenge + CodeChallenge *string `form:"code_challenge,omitempty" json:"code_challenge,omitempty"` + + // CodeChallengeMethod PKCE code challenge method + CodeChallengeMethod *string `form:"code_challenge_method,omitempty" json:"code_challenge_method,omitempty"` + + // Nonce OpenID Connect nonce + Nonce *string `form:"nonce,omitempty" json:"nonce,omitempty"` + + // MaxAge Maximum authentication age in seconds; forces re-authentication when exceeded + MaxAge *int `form:"max_age,omitempty" json:"max_age,omitempty"` + + // Resource Requested protected resource(s) for the access token. May be supplied multiple times as repeated 'resource' query parameters (RFC 8707) or as an array of strings. + Resource *[]string `form:"resource,omitempty" json:"resource,omitempty"` + + // Prompt OAuth2 prompt parameter + Prompt *string `form:"prompt,omitempty" json:"prompt,omitempty"` } // PostApiAuthOauth2ClientRotateSecretJSONBody defines parameters for PostApiAuthOauth2ClientRotateSecret. @@ -6449,9 +6908,21 @@ type PostApiAuthOauth2ClientRotateSecretJSONBody struct { // PostApiAuthOauth2ConsentJSONBody defines parameters for PostApiAuthOauth2Consent. type PostApiAuthOauth2ConsentJSONBody struct { // Accept Accept or deny user consent for a set of scopes - Accept bool `json:"accept"` - OauthQuery *string `json:"oauth_query,omitempty"` - Scope *string `json:"scope,omitempty"` + Accept bool `json:"accept"` + Claims *PostApiAuthOauth2ConsentJSONBody_Claims `json:"claims,omitempty"` + OauthQuery *string `json:"oauth_query,omitempty"` + Scope *string `json:"scope,omitempty"` +} + +// PostApiAuthOauth2ConsentJSONBodyClaims0 defines parameters for PostApiAuthOauth2Consent. +type PostApiAuthOauth2ConsentJSONBodyClaims0 = string + +// PostApiAuthOauth2ConsentJSONBodyClaims1 defines parameters for PostApiAuthOauth2Consent. +type PostApiAuthOauth2ConsentJSONBodyClaims1 map[string]interface{} + +// PostApiAuthOauth2ConsentJSONBody_Claims defines parameters for PostApiAuthOauth2Consent. +type PostApiAuthOauth2ConsentJSONBody_Claims struct { + union json.RawMessage } // PostApiAuthOauth2ContinueJSONBody defines parameters for PostApiAuthOauth2Continue. @@ -6464,35 +6935,53 @@ type PostApiAuthOauth2ContinueJSONBody struct { // PostApiAuthOauth2CreateClientJSONBody defines parameters for PostApiAuthOauth2CreateClient. type PostApiAuthOauth2CreateClientJSONBody struct { - ClientName *string `json:"client_name,omitempty"` - ClientUri *string `json:"client_uri,omitempty"` - Contacts *[]interface{} `json:"contacts,omitempty"` - GrantTypes *[]interface{} `json:"grant_types,omitempty"` - LogoUri *string `json:"logo_uri,omitempty"` - PolicyUri *string `json:"policy_uri,omitempty"` - PostLogoutRedirectUris *[]interface{} `json:"post_logout_redirect_uris,omitempty"` - RedirectUris []interface{} `json:"redirect_uris"` - ResponseTypes *[]interface{} `json:"response_types,omitempty"` - Scope *string `json:"scope,omitempty"` - SoftwareId *string `json:"software_id,omitempty"` - SoftwareStatement *string `json:"software_statement,omitempty"` - SoftwareVersion *string `json:"software_version,omitempty"` - TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` - TosUri *string `json:"tos_uri,omitempty"` - Type *string `json:"type,omitempty"` + BackchannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"` + BackchannelLogoutUri *string `json:"backchannel_logout_uri,omitempty"` + ClientName *string `json:"client_name,omitempty"` + ClientUri *string `json:"client_uri,omitempty"` + Contacts *[]string `json:"contacts,omitempty"` + DpopBoundAccessTokens *bool `json:"dpop_bound_access_tokens,omitempty"` + GrantTypes *[]string `json:"grant_types,omitempty"` + Jwks *PostApiAuthOauth2CreateClientJSONBody_Jwks `json:"jwks,omitempty"` + JwksUri *string `json:"jwks_uri,omitempty"` + LogoUri *string `json:"logo_uri,omitempty"` + PolicyUri *string `json:"policy_uri,omitempty"` + PostLogoutRedirectUris *[]string `json:"post_logout_redirect_uris,omitempty"` + RedirectUris *[]string `json:"redirect_uris,omitempty"` + ResponseTypes *[]PostApiAuthOauth2CreateClientJSONBodyResponseTypes `json:"response_types,omitempty"` + Scope *string `json:"scope,omitempty"` + SoftwareId *string `json:"software_id,omitempty"` + SoftwareStatement *string `json:"software_statement,omitempty"` + SoftwareVersion *string `json:"software_version,omitempty"` + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` + TosUri *string `json:"tos_uri,omitempty"` + Type *PostApiAuthOauth2CreateClientJSONBodyType `json:"type,omitempty"` } -// PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes defines parameters for PostApiAuthOauth2CreateClient. -type PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes string +// PostApiAuthOauth2CreateClientJSONBodyJwks0 defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClientJSONBodyJwks0 = []map[string]interface{} -// PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes defines parameters for PostApiAuthOauth2CreateClient. -type PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes string +// PostApiAuthOauth2CreateClientJSONBodyJwks1 defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClientJSONBodyJwks1 struct { + Keys []map[string]interface{} `json:"keys"` +} -// PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod defines parameters for PostApiAuthOauth2CreateClient. -type PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod string +// PostApiAuthOauth2CreateClientJSONBody_Jwks defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClientJSONBody_Jwks struct { + union json.RawMessage +} -// PostApiAuthOauth2CreateClient200JSONResponseBodyType defines parameters for PostApiAuthOauth2CreateClient. -type PostApiAuthOauth2CreateClient200JSONResponseBodyType string +// PostApiAuthOauth2CreateClientJSONBodyResponseTypes defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClientJSONBodyResponseTypes string + +// PostApiAuthOauth2CreateClientJSONBodyType defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClientJSONBodyType string + +// PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes string + +// PostApiAuthOauth2CreateClient201JSONResponseBodyType defines parameters for PostApiAuthOauth2CreateClient. +type PostApiAuthOauth2CreateClient201JSONResponseBodyType string // PostApiAuthOauth2DeleteClientJSONBody defines parameters for PostApiAuthOauth2DeleteClient. type PostApiAuthOauth2DeleteClientJSONBody struct { @@ -6530,34 +7019,13 @@ type PostApiAuthOauth2IntrospectJSONBody struct { // ClientSecret OAuth2 client secret ClientSecret *string `json:"client_secret,omitempty"` - // Resource Introspects a token for a specific resource. - Resource *string `json:"resource,omitempty"` - // Token The token to introspect (access or refresh token) Token string `json:"token"` - // TokenTypeHint Hint about the type of the token submitted for introspection - TokenTypeHint *PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint `json:"token_type_hint,omitempty"` + // TokenTypeHint Hint about the token type. Recognized values: `access_token`, `refresh_token`. + TokenTypeHint *string `json:"token_type_hint,omitempty"` } -// PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint defines parameters for PostApiAuthOauth2Introspect. -type PostApiAuthOauth2IntrospectJSONBodyTokenTypeHint string - -// PostApiAuthOauth2LinkJSONBody defines parameters for PostApiAuthOauth2Link. -type PostApiAuthOauth2LinkJSONBody struct { - CallbackURL string `json:"callbackURL"` - - // ErrorCallbackURL The URL to redirect to if there is an error during the link process - ErrorCallbackURL *string `json:"errorCallbackURL,omitempty"` - ProviderId string `json:"providerId"` - - // Scopes Additional scopes to request when linking the account - Scopes *[]interface{} `json:"scopes,omitempty"` -} - -// PostApiAuthOauth2Link200JSONResponseBodyRedirect defines parameters for PostApiAuthOauth2Link. -type PostApiAuthOauth2Link200JSONResponseBodyRedirect bool - // GetApiAuthOauth2PublicClientParams defines parameters for GetApiAuthOauth2PublicClient. type GetApiAuthOauth2PublicClientParams struct { ClientId *string `form:"client_id,omitempty" json:"client_id,omitempty"` @@ -6571,37 +7039,59 @@ type PostApiAuthOauth2PublicClientPreloginJSONBody struct { // PostApiAuthOauth2RegisterJSONBody defines parameters for PostApiAuthOauth2Register. type PostApiAuthOauth2RegisterJSONBody struct { - ClientName *string `json:"client_name,omitempty"` - ClientUri *string `json:"client_uri,omitempty"` - Contacts *[]interface{} `json:"contacts,omitempty"` - GrantTypes *[]interface{} `json:"grant_types,omitempty"` - LogoUri *string `json:"logo_uri,omitempty"` - PolicyUri *string `json:"policy_uri,omitempty"` - PostLogoutRedirectUris *[]interface{} `json:"post_logout_redirect_uris,omitempty"` - RedirectUris []interface{} `json:"redirect_uris"` - ResponseTypes *[]interface{} `json:"response_types,omitempty"` - Scope *string `json:"scope,omitempty"` - SkipConsent *string `json:"skip_consent,omitempty"` - SoftwareId *string `json:"software_id,omitempty"` - SoftwareStatement *string `json:"software_statement,omitempty"` - SoftwareVersion *string `json:"software_version,omitempty"` - SubjectType *string `json:"subject_type,omitempty"` - TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` - TosUri *string `json:"tos_uri,omitempty"` - Type *string `json:"type,omitempty"` + BackchannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"` + BackchannelLogoutUri *string `json:"backchannel_logout_uri,omitempty"` + ClientName *string `json:"client_name,omitempty"` + ClientUri *string `json:"client_uri,omitempty"` + Contacts *[]string `json:"contacts,omitempty"` + DpopBoundAccessTokens *bool `json:"dpop_bound_access_tokens,omitempty"` + GrantTypes *[]string `json:"grant_types,omitempty"` + Jwks *PostApiAuthOauth2RegisterJSONBody_Jwks `json:"jwks,omitempty"` + JwksUri *string `json:"jwks_uri,omitempty"` + LogoUri *string `json:"logo_uri,omitempty"` + PolicyUri *string `json:"policy_uri,omitempty"` + PostLogoutRedirectUris *[]string `json:"post_logout_redirect_uris,omitempty"` + RedirectUris *[]string `json:"redirect_uris,omitempty"` + Resources *[]string `json:"resources,omitempty"` + ResponseTypes *[]PostApiAuthOauth2RegisterJSONBodyResponseTypes `json:"response_types,omitempty"` + Scope *string `json:"scope,omitempty"` + SkipConsent *string `json:"skip_consent,omitempty"` + SoftwareId *string `json:"software_id,omitempty"` + SoftwareStatement *string `json:"software_statement,omitempty"` + SoftwareVersion *string `json:"software_version,omitempty"` + SubjectType *PostApiAuthOauth2RegisterJSONBodySubjectType `json:"subject_type,omitempty"` + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` + TosUri *string `json:"tos_uri,omitempty"` + Type *PostApiAuthOauth2RegisterJSONBodyType `json:"type,omitempty"` } -// PostApiAuthOauth2Register200JSONResponseBodyGrantTypes defines parameters for PostApiAuthOauth2Register. -type PostApiAuthOauth2Register200JSONResponseBodyGrantTypes string +// PostApiAuthOauth2RegisterJSONBodyJwks0 defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBodyJwks0 = []map[string]interface{} -// PostApiAuthOauth2Register200JSONResponseBodyResponseTypes defines parameters for PostApiAuthOauth2Register. -type PostApiAuthOauth2Register200JSONResponseBodyResponseTypes string +// PostApiAuthOauth2RegisterJSONBodyJwks1 defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBodyJwks1 struct { + Keys []map[string]interface{} `json:"keys"` +} -// PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod defines parameters for PostApiAuthOauth2Register. -type PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod string +// PostApiAuthOauth2RegisterJSONBody_Jwks defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBody_Jwks struct { + union json.RawMessage +} -// PostApiAuthOauth2Register200JSONResponseBodyType defines parameters for PostApiAuthOauth2Register. -type PostApiAuthOauth2Register200JSONResponseBodyType string +// PostApiAuthOauth2RegisterJSONBodyResponseTypes defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBodyResponseTypes string + +// PostApiAuthOauth2RegisterJSONBodySubjectType defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBodySubjectType string + +// PostApiAuthOauth2RegisterJSONBodyType defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2RegisterJSONBodyType string + +// PostApiAuthOauth2Register201JSONResponseBodyResponseTypes defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2Register201JSONResponseBodyResponseTypes string + +// PostApiAuthOauth2Register201JSONResponseBodyType defines parameters for PostApiAuthOauth2Register. +type PostApiAuthOauth2Register201JSONResponseBodyType string // PostApiAuthOauth2RevokeJSONBody defines parameters for PostApiAuthOauth2Revoke. type PostApiAuthOauth2RevokeJSONBody struct { @@ -6614,13 +7104,10 @@ type PostApiAuthOauth2RevokeJSONBody struct { // Token The token to revoke (access or refresh token) Token string `json:"token"` - // TokenTypeHint Hint about the type of the token submitted for revocation - TokenTypeHint *PostApiAuthOauth2RevokeJSONBodyTokenTypeHint `json:"token_type_hint,omitempty"` + // TokenTypeHint Hint about the token type. Recognized values: `access_token`, `refresh_token`. + TokenTypeHint *string `json:"token_type_hint,omitempty"` } -// PostApiAuthOauth2RevokeJSONBodyTokenTypeHint defines parameters for PostApiAuthOauth2Revoke. -type PostApiAuthOauth2RevokeJSONBodyTokenTypeHint string - // PostApiAuthOauth2TokenJSONBody defines parameters for PostApiAuthOauth2Token. type PostApiAuthOauth2TokenJSONBody struct { // ClientId OAuth2 client ID @@ -6636,7 +7123,7 @@ type PostApiAuthOauth2TokenJSONBody struct { CodeVerifier *string `json:"code_verifier,omitempty"` // GrantType OAuth2 grant type - GrantType PostApiAuthOauth2TokenJSONBodyGrantType `json:"grant_type"` + GrantType string `json:"grant_type"` // RedirectUri Redirect URI (for authorization_code grant) RedirectUri *string `json:"redirect_uri,omitempty"` @@ -6644,15 +7131,29 @@ type PostApiAuthOauth2TokenJSONBody struct { // RefreshToken Refresh token (for refresh_token grant) RefreshToken *string `json:"refresh_token,omitempty"` - // Resource Requested token resource (ie audience) to obtain a JWT formatted access token - Resource *string `json:"resource,omitempty"` + // Resource Requested protected resource(s) for the access token + Resource *PostApiAuthOauth2TokenJSONBody_Resource `json:"resource,omitempty"` // Scope Requested scopes (for client_credentials grant) Scope *string `json:"scope,omitempty"` } -// PostApiAuthOauth2TokenJSONBodyGrantType defines parameters for PostApiAuthOauth2Token. -type PostApiAuthOauth2TokenJSONBodyGrantType string +// PostApiAuthOauth2TokenParams defines parameters for PostApiAuthOauth2Token. +type PostApiAuthOauth2TokenParams struct { + // DPoP RFC 9449 DPoP proof JWT for issuing DPoP-bound tokens + DPoP *string `json:"DPoP,omitempty"` +} + +// PostApiAuthOauth2TokenJSONBodyResource0 defines parameters for PostApiAuthOauth2Token. +type PostApiAuthOauth2TokenJSONBodyResource0 = string + +// PostApiAuthOauth2TokenJSONBodyResource1 defines parameters for PostApiAuthOauth2Token. +type PostApiAuthOauth2TokenJSONBodyResource1 = []string + +// PostApiAuthOauth2TokenJSONBody_Resource defines parameters for PostApiAuthOauth2Token. +type PostApiAuthOauth2TokenJSONBody_Resource struct { + union json.RawMessage +} // PostApiAuthOauth2Token200JSONResponseBodyTokenType defines parameters for PostApiAuthOauth2Token. type PostApiAuthOauth2Token200JSONResponseBodyTokenType string @@ -6661,36 +7162,61 @@ type PostApiAuthOauth2Token200JSONResponseBodyTokenType string type PostApiAuthOauth2UpdateClientJSONBody struct { ClientId string `json:"client_id"` Update struct { - ClientName *string `json:"client_name,omitempty"` - ClientUri *string `json:"client_uri,omitempty"` - Contacts *[]interface{} `json:"contacts,omitempty"` - GrantTypes *[]interface{} `json:"grant_types,omitempty"` - LogoUri *string `json:"logo_uri,omitempty"` - PolicyUri *string `json:"policy_uri,omitempty"` - PostLogoutRedirectUris *[]interface{} `json:"post_logout_redirect_uris,omitempty"` - RedirectUris *[]interface{} `json:"redirect_uris,omitempty"` - ResponseTypes *[]interface{} `json:"response_types,omitempty"` - Scope *string `json:"scope,omitempty"` - SoftwareId *string `json:"software_id,omitempty"` - SoftwareStatement *string `json:"software_statement,omitempty"` - SoftwareVersion *string `json:"software_version,omitempty"` - TosUri *string `json:"tos_uri,omitempty"` - Type *string `json:"type,omitempty"` + BackchannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"` + BackchannelLogoutUri *string `json:"backchannel_logout_uri,omitempty"` + ClientName *string `json:"client_name,omitempty"` + ClientUri *string `json:"client_uri,omitempty"` + Contacts *[]string `json:"contacts,omitempty"` + GrantTypes *[]string `json:"grant_types,omitempty"` + LogoUri *string `json:"logo_uri,omitempty"` + PolicyUri *string `json:"policy_uri,omitempty"` + PostLogoutRedirectUris *[]string `json:"post_logout_redirect_uris,omitempty"` + RedirectUris *[]string `json:"redirect_uris,omitempty"` + ResponseTypes *[]PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes `json:"response_types,omitempty"` + Scope *string `json:"scope,omitempty"` + SoftwareId *string `json:"software_id,omitempty"` + SoftwareStatement *string `json:"software_statement,omitempty"` + SoftwareVersion *string `json:"software_version,omitempty"` + TosUri *string `json:"tos_uri,omitempty"` + Type *PostApiAuthOauth2UpdateClientJSONBodyUpdateType `json:"type,omitempty"` } `json:"update"` } +// PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes defines parameters for PostApiAuthOauth2UpdateClient. +type PostApiAuthOauth2UpdateClientJSONBodyUpdateResponseTypes string + +// PostApiAuthOauth2UpdateClientJSONBodyUpdateType defines parameters for PostApiAuthOauth2UpdateClient. +type PostApiAuthOauth2UpdateClientJSONBodyUpdateType string + // PostApiAuthOauth2UpdateConsentJSONBody defines parameters for PostApiAuthOauth2UpdateConsent. type PostApiAuthOauth2UpdateConsentJSONBody struct { Id string `json:"id"` Update struct { - Scopes []interface{} `json:"scopes"` + Scopes []string `json:"scopes"` } `json:"update"` } // GetApiAuthOauth2UserinfoParams defines parameters for GetApiAuthOauth2Userinfo. type GetApiAuthOauth2UserinfoParams struct { - // Authorization Bearer access token + // Authorization Bearer or DPoP access token Authorization *string `json:"Authorization,omitempty"` + + // DPoP RFC 9449 DPoP proof JWT when using a DPoP-bound access token + DPoP *string `json:"DPoP,omitempty"` +} + +// PostApiAuthOauth2UserinfoJSONBody defines parameters for PostApiAuthOauth2Userinfo. +type PostApiAuthOauth2UserinfoJSONBody struct { + AccessToken *string `json:"access_token,omitempty"` +} + +// PostApiAuthOauth2UserinfoParams defines parameters for PostApiAuthOauth2Userinfo. +type PostApiAuthOauth2UserinfoParams struct { + // Authorization Bearer or DPoP access token + Authorization *string `json:"Authorization,omitempty"` + + // DPoP RFC 9449 DPoP proof JWT when using a DPoP-bound access token + DPoP *string `json:"DPoP,omitempty"` } // PostApiAuthOrganizationAcceptInvitationJSONBody defines parameters for PostApiAuthOrganizationAcceptInvitation. @@ -6714,11 +7240,13 @@ type PostApiAuthOrganizationCheckSlugJSONBody struct { // PostApiAuthOrganizationCreateJSONBody defines parameters for PostApiAuthOrganizationCreate. type PostApiAuthOrganizationCreateJSONBody struct { // KeepCurrentActiveOrganization Whether to keep the current active organization active after creating a new one. Eg: true - KeepCurrentActiveOrganization *bool `json:"keepCurrentActiveOrganization,omitempty"` - Logo *string `json:"logo,omitempty"` + KeepCurrentActiveOrganization *bool `json:"keepCurrentActiveOrganization,omitempty"` + + // Logo The logo of the organization + Logo *string `json:"logo,omitempty"` // Metadata The metadata of the organization - Metadata *string `json:"metadata,omitempty"` + Metadata *map[string]interface{} `json:"metadata,omitempty"` // Name The name of the organization Name string `json:"name"` @@ -6763,8 +7291,30 @@ type CreateOrganizationInvitationJSONBody struct { Resend *bool `json:"resend,omitempty"` // Role The role(s) to assign to the user. It can be `admin`, `member`, owner. Eg: "member" - Role string `json:"role"` - TeamId string `json:"teamId"` + Role CreateOrganizationInvitationJSONBody_Role `json:"role"` + TeamId *CreateOrganizationInvitationJSONBody_TeamId `json:"teamId,omitempty"` +} + +// CreateOrganizationInvitationJSONBodyRole0 defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBodyRole0 = string + +// CreateOrganizationInvitationJSONBodyRole1 defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBodyRole1 = []string + +// CreateOrganizationInvitationJSONBody_Role defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBody_Role struct { + union json.RawMessage +} + +// CreateOrganizationInvitationJSONBodyTeamId0 defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBodyTeamId0 = string + +// CreateOrganizationInvitationJSONBodyTeamId1 defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBodyTeamId1 = []string + +// CreateOrganizationInvitationJSONBody_TeamId defines parameters for CreateOrganizationInvitation. +type CreateOrganizationInvitationJSONBody_TeamId struct { + union json.RawMessage } // PostApiAuthOrganizationLeaveJSONBody defines parameters for PostApiAuthOrganizationLeave. @@ -6790,6 +7340,7 @@ type PostApiAuthOrganizationRemoveMemberJSONBody struct { // SetActiveOrganizationJSONBody defines parameters for SetActiveOrganization. type SetActiveOrganizationJSONBody struct { + // OrganizationId The organization id to set as active. It can be null to unset the active organization. Eg: "org-id" OrganizationId *string `json:"organizationId,omitempty"` // OrganizationSlug The organization slug to set as active. It can be null to unset the active organization if organizationId is not provided. Eg: "org-slug" @@ -6799,10 +7350,11 @@ type SetActiveOrganizationJSONBody struct { // PostApiAuthOrganizationUpdateJSONBody defines parameters for PostApiAuthOrganizationUpdate. type PostApiAuthOrganizationUpdateJSONBody struct { Data struct { + // Logo The logo of the organization Logo *string `json:"logo,omitempty"` // Metadata The metadata of the organization - Metadata *string `json:"metadata,omitempty"` + Metadata *map[string]interface{} `json:"metadata,omitempty"` // Name The name of the organization Name *string `json:"name,omitempty"` @@ -6824,16 +7376,38 @@ type UpdateOrganizationMemberRoleJSONBody struct { OrganizationId *string `json:"organizationId,omitempty"` // Role The new role to be applied. This can be a string or array of strings representing the roles. Eg: ["admin", "sale"] - Role string `json:"role"` + Role UpdateOrganizationMemberRoleJSONBody_Role `json:"role"` +} + +// UpdateOrganizationMemberRoleJSONBodyRole0 defines parameters for UpdateOrganizationMemberRole. +type UpdateOrganizationMemberRoleJSONBodyRole0 = string + +// UpdateOrganizationMemberRoleJSONBodyRole1 defines parameters for UpdateOrganizationMemberRole. +type UpdateOrganizationMemberRoleJSONBodyRole1 = []string + +// UpdateOrganizationMemberRoleJSONBody_Role defines parameters for UpdateOrganizationMemberRole. +type UpdateOrganizationMemberRoleJSONBody_Role struct { + union json.RawMessage } // PostApiAuthRefreshTokenJSONBody defines parameters for PostApiAuthRefreshToken. type PostApiAuthRefreshTokenJSONBody struct { - // AccountId The account ID associated with the refresh token - AccountId *string `json:"accountId,omitempty"` + union json.RawMessage +} - // ProviderId The provider ID for the OAuth provider - ProviderId string `json:"providerId"` +// PostApiAuthRefreshTokenJSONBody0 defines parameters for PostApiAuthRefreshToken. +type PostApiAuthRefreshTokenJSONBody0 struct { + // AccountId The Better Auth account ID + AccountId string `json:"accountId"` + + // UserId The user ID associated with the account + UserId *string `json:"userId,omitempty"` +} + +// PostApiAuthRefreshTokenJSONBody1 defines parameters for PostApiAuthRefreshToken. +type PostApiAuthRefreshTokenJSONBody1 struct { + // UseAccountCookie Select the current OAuth account from its signed cookie + UseAccountCookie interface{} `json:"useAccountCookie"` // UserId The user ID associated with the account UserId *string `json:"userId,omitempty"` @@ -6902,35 +7476,12 @@ type SignInEmailJSONBody struct { // SignInEmail200JSONResponseBodyRedirect defines parameters for SignInEmail. type SignInEmail200JSONResponseBodyRedirect bool -// PostApiAuthSignInOauth2JSONBody defines parameters for PostApiAuthSignInOauth2. -type PostApiAuthSignInOauth2JSONBody struct { - AdditionalData *string `json:"additionalData,omitempty"` - - // CallbackURL The URL to redirect to after sign in - CallbackURL *string `json:"callbackURL,omitempty"` - - // DisableRedirect Disable redirect - DisableRedirect *bool `json:"disableRedirect,omitempty"` - - // ErrorCallbackURL The URL to redirect to if an error occurs - ErrorCallbackURL *string `json:"errorCallbackURL,omitempty"` - - // NewUserCallbackURL The URL to redirect to after login if the user is new. Eg: "/welcome" - NewUserCallbackURL *string `json:"newUserCallbackURL,omitempty"` - - // ProviderId The provider ID for the OAuth provider - ProviderId string `json:"providerId"` - - // RequestSignUp Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider. Eg: false - RequestSignUp *bool `json:"requestSignUp,omitempty"` - - // Scopes Scopes to be passed to the provider authorization request. - Scopes *[]interface{} `json:"scopes,omitempty"` -} - // SocialSignInJSONBody defines parameters for SocialSignIn. type SocialSignInJSONBody struct { - AdditionalData *string `json:"additionalData,omitempty"` + AdditionalData *map[string]interface{} `json:"additionalData,omitempty"` + + // AdditionalParams Extra query parameters to append to the provider authorization URL (e.g. Cognito identity_provider, Google hd). + AdditionalParams *map[string]string `json:"additionalParams,omitempty"` // CallbackURL Callback URL to redirect to after the user has signed in CallbackURL *string `json:"callbackURL,omitempty"` @@ -6967,15 +7518,26 @@ type SocialSignInJSONBody struct { } `json:"idToken,omitempty"` // LoginHint The login hint to use for the authorization code request - LoginHint *string `json:"loginHint,omitempty"` - NewUserCallbackURL *string `json:"newUserCallbackURL,omitempty"` - Provider string `json:"provider"` + LoginHint *string `json:"loginHint,omitempty"` + NewUserCallbackURL *string `json:"newUserCallbackURL,omitempty"` + Provider SocialSignInJSONBody_Provider `json:"provider"` // RequestSignUp Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider RequestSignUp *bool `json:"requestSignUp,omitempty"` // Scopes Array of scopes to request from the provider. This will override the default scopes passed. - Scopes *[]interface{} `json:"scopes,omitempty"` + Scopes *[]string `json:"scopes,omitempty"` +} + +// SocialSignInJSONBodyProvider0 defines parameters for SocialSignIn. +type SocialSignInJSONBodyProvider0 string + +// SocialSignInJSONBodyProvider1 defines parameters for SocialSignIn. +type SocialSignInJSONBodyProvider1 = string + +// SocialSignInJSONBody_Provider defines parameters for SocialSignIn. +type SocialSignInJSONBody_Provider struct { + union json.RawMessage } // PostApiAuthSignInUsernameJSONBody defines parameters for PostApiAuthSignInUsername. @@ -6999,7 +7561,8 @@ type SignOutJSONBody = map[string]interface{} // SignUpWithEmailAndPasswordJSONBody defines parameters for SignUpWithEmailAndPassword. type SignUpWithEmailAndPasswordJSONBody struct { // CallbackURL The URL to use for email verification callback - CallbackURL *string `json:"callbackURL,omitempty"` + CallbackURL *string `json:"callbackURL,omitempty"` + DisplayUsername *string `json:"displayUsername,omitempty"` // Email The email of the user Email string `json:"email"` @@ -7014,25 +7577,29 @@ type SignUpWithEmailAndPasswordJSONBody struct { Password string `json:"password"` // RememberMe If this is false, the session will not be remembered. Default is `true`. - RememberMe *bool `json:"rememberMe,omitempty"` + RememberMe *bool `json:"rememberMe,omitempty"` + Username *string `json:"username,omitempty"` } // PostApiAuthUnlinkAccountJSONBody defines parameters for PostApiAuthUnlinkAccount. type PostApiAuthUnlinkAccountJSONBody struct { - AccountId *string `json:"accountId,omitempty"` - ProviderId string `json:"providerId"` + // AccountId The Better Auth account ID to unlink + AccountId string `json:"accountId"` } // UpdateSessionJSONBody defines parameters for UpdateSession. -type UpdateSessionJSONBody = map[string]interface{} +type UpdateSessionJSONBody map[string]interface{} // UpdateUserJSONBody defines parameters for UpdateUser. type UpdateUserJSONBody struct { + DisplayUsername *string `json:"displayUsername,omitempty"` + // Image The image of the user Image *string `json:"image,omitempty"` // Name The name of the user - Name *string `json:"name,omitempty"` + Name *string `json:"name,omitempty"` + Username *string `json:"username,omitempty"` } // GetApiAuthVerifyEmailParams defines parameters for GetApiAuthVerifyEmail. @@ -7350,6 +7917,51 @@ type CreateObjectJSONBodyOnConflict string // CreateObject201JSONResponseBodyUploadMode defines parameters for CreateObject. type CreateObject201JSONResponseBodyUploadMode string +// CreateObject201JSONResponseBodyUploadWorkflowAbortMethod defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowAbortMethod string + +// CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId string + +// CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod string + +// CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId string + +// CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField string + +// CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod string + +// CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId string + +// CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadMethod defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadMethod string + +// CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField string + +// CreateObject201JSONResponseBodyUploadWorkflowVersion defines parameters for CreateObject. +type CreateObject201JSONResponseBodyUploadWorkflowVersion string + // UpdateObjectJSONBody defines parameters for UpdateObject. type UpdateObjectJSONBody struct { Name *string `json:"name,omitempty"` @@ -7813,50 +8425,6 @@ type UpdateUserEntitlementJSONBody struct { Note *string `json:"note,omitempty"` } -// ListWorkspaceAgentApiKeysParams defines parameters for ListWorkspaceAgentApiKeys. -type ListWorkspaceAgentApiKeysParams struct { - Page *int `form:"page,omitempty" json:"page,omitempty"` - PageSize *int `form:"pageSize,omitempty" json:"pageSize,omitempty"` -} - -// ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes defines parameters for ListWorkspaceAgentApiKeys. -type ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes string - -// ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus defines parameters for ListWorkspaceAgentApiKeys. -type ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus string - -// CreateWorkspaceAgentApiKeyJSONBody defines parameters for CreateWorkspaceAgentApiKey. -type CreateWorkspaceAgentApiKeyJSONBody struct { - ExpiresAt time.Time `json:"expiresAt"` - Name string `json:"name"` - Scopes []CreateWorkspaceAgentApiKeyJSONBodyScopes `json:"scopes"` -} - -// CreateWorkspaceAgentApiKeyJSONBodyScopes defines parameters for CreateWorkspaceAgentApiKey. -type CreateWorkspaceAgentApiKeyJSONBodyScopes string - -// CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes defines parameters for CreateWorkspaceAgentApiKey. -type CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes string - -// CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus defines parameters for CreateWorkspaceAgentApiKey. -type CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus string - -// RotateWorkspaceAgentApiKeyJSONBody defines parameters for RotateWorkspaceAgentApiKey. -type RotateWorkspaceAgentApiKeyJSONBody struct { - ExpiresAt *time.Time `json:"expiresAt,omitempty"` - Name *string `json:"name,omitempty"` - Scopes *[]RotateWorkspaceAgentApiKeyJSONBodyScopes `json:"scopes,omitempty"` -} - -// RotateWorkspaceAgentApiKeyJSONBodyScopes defines parameters for RotateWorkspaceAgentApiKey. -type RotateWorkspaceAgentApiKeyJSONBodyScopes string - -// RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes defines parameters for RotateWorkspaceAgentApiKey. -type RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes string - -// RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus defines parameters for RotateWorkspaceAgentApiKey. -type RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus string - // SubmitAgentOAuthConsentJSONRequestBody defines body for SubmitAgentOAuthConsent for application/json ContentType. type SubmitAgentOAuthConsentJSONRequestBody SubmitAgentOAuthConsentJSONBody @@ -7906,7 +8474,7 @@ type PostApiAuthApiKeyDeleteJSONRequestBody PostApiAuthApiKeyDeleteJSONBody type PostApiAuthApiKeyUpdateJSONRequestBody PostApiAuthApiKeyUpdateJSONBody // PostApiAuthCallbackIdJSONRequestBody defines body for PostApiAuthCallbackId for application/json ContentType. -type PostApiAuthCallbackIdJSONRequestBody = PostApiAuthCallbackIdJSONBody +type PostApiAuthCallbackIdJSONRequestBody PostApiAuthCallbackIdJSONBody // ChangeEmailJSONRequestBody defines body for ChangeEmail for application/json ContentType. type ChangeEmailJSONRequestBody ChangeEmailJSONBody @@ -7962,9 +8530,6 @@ type PostApiAuthOauth2DeleteConsentJSONRequestBody PostApiAuthOauth2DeleteConsen // PostApiAuthOauth2IntrospectJSONRequestBody defines body for PostApiAuthOauth2Introspect for application/json ContentType. type PostApiAuthOauth2IntrospectJSONRequestBody PostApiAuthOauth2IntrospectJSONBody -// PostApiAuthOauth2LinkJSONRequestBody defines body for PostApiAuthOauth2Link for application/json ContentType. -type PostApiAuthOauth2LinkJSONRequestBody PostApiAuthOauth2LinkJSONBody - // PostApiAuthOauth2PublicClientPreloginJSONRequestBody defines body for PostApiAuthOauth2PublicClientPrelogin for application/json ContentType. type PostApiAuthOauth2PublicClientPreloginJSONRequestBody PostApiAuthOauth2PublicClientPreloginJSONBody @@ -7983,6 +8548,9 @@ type PostApiAuthOauth2UpdateClientJSONRequestBody PostApiAuthOauth2UpdateClientJ // PostApiAuthOauth2UpdateConsentJSONRequestBody defines body for PostApiAuthOauth2UpdateConsent for application/json ContentType. type PostApiAuthOauth2UpdateConsentJSONRequestBody PostApiAuthOauth2UpdateConsentJSONBody +// PostApiAuthOauth2UserinfoJSONRequestBody defines body for PostApiAuthOauth2Userinfo for application/json ContentType. +type PostApiAuthOauth2UserinfoJSONRequestBody PostApiAuthOauth2UserinfoJSONBody + // PostApiAuthOrganizationAcceptInvitationJSONRequestBody defines body for PostApiAuthOrganizationAcceptInvitation for application/json ContentType. type PostApiAuthOrganizationAcceptInvitationJSONRequestBody PostApiAuthOrganizationAcceptInvitationJSONBody @@ -8046,9 +8614,6 @@ type SendVerificationEmailJSONRequestBody SendVerificationEmailJSONBody // SignInEmailJSONRequestBody defines body for SignInEmail for application/json ContentType. type SignInEmailJSONRequestBody SignInEmailJSONBody -// PostApiAuthSignInOauth2JSONRequestBody defines body for PostApiAuthSignInOauth2 for application/json ContentType. -type PostApiAuthSignInOauth2JSONRequestBody PostApiAuthSignInOauth2JSONBody - // SocialSignInJSONRequestBody defines body for SocialSignIn for application/json ContentType. type SocialSignInJSONRequestBody SocialSignInJSONBody @@ -8065,7 +8630,7 @@ type SignUpWithEmailAndPasswordJSONRequestBody SignUpWithEmailAndPasswordJSONBod type PostApiAuthUnlinkAccountJSONRequestBody PostApiAuthUnlinkAccountJSONBody // UpdateSessionJSONRequestBody defines body for UpdateSession for application/json ContentType. -type UpdateSessionJSONRequestBody = UpdateSessionJSONBody +type UpdateSessionJSONRequestBody UpdateSessionJSONBody // UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType. type UpdateUserJSONRequestBody UpdateUserJSONBody @@ -8229,12 +8794,6 @@ type GrantUserEntitlementJSONRequestBody GrantUserEntitlementJSONBody // UpdateUserEntitlementJSONRequestBody defines body for UpdateUserEntitlement for application/json ContentType. type UpdateUserEntitlementJSONRequestBody UpdateUserEntitlementJSONBody -// CreateWorkspaceAgentApiKeyJSONRequestBody defines body for CreateWorkspaceAgentApiKey for application/json ContentType. -type CreateWorkspaceAgentApiKeyJSONRequestBody CreateWorkspaceAgentApiKeyJSONBody - -// RotateWorkspaceAgentApiKeyJSONRequestBody defines body for RotateWorkspaceAgentApiKey for application/json ContentType. -type RotateWorkspaceAgentApiKeyJSONRequestBody RotateWorkspaceAgentApiKeyJSONBody - // AsCloudflareSaasImageDomainSettingsCloudflare0 returns the union data inside the CloudflareSaasImageDomainSettings_Cloudflare as a CloudflareSaasImageDomainSettingsCloudflare0 func (t CloudflareSaasImageDomainSettings_Cloudflare) AsCloudflareSaasImageDomainSettingsCloudflare0() (CloudflareSaasImageDomainSettingsCloudflare0, error) { var body CloudflareSaasImageDomainSettingsCloudflare0 @@ -8769,6 +9328,998 @@ func (t *UpdateImageDomainSettings) UnmarshalJSON(b []byte) error { return err } +// AsCreateUserJSONBodyRole0 returns the union data inside the CreateUserJSONBody_Role as a CreateUserJSONBodyRole0 +func (t CreateUserJSONBody_Role) AsCreateUserJSONBodyRole0() (CreateUserJSONBodyRole0, error) { + var body CreateUserJSONBodyRole0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateUserJSONBodyRole0 overwrites any union data inside the CreateUserJSONBody_Role as the provided CreateUserJSONBodyRole0 +func (t *CreateUserJSONBody_Role) FromCreateUserJSONBodyRole0(v CreateUserJSONBodyRole0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateUserJSONBodyRole0 performs a merge with any union data inside the CreateUserJSONBody_Role, using the provided CreateUserJSONBodyRole0 +func (t *CreateUserJSONBody_Role) MergeCreateUserJSONBodyRole0(v CreateUserJSONBodyRole0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsCreateUserJSONBodyRole1 returns the union data inside the CreateUserJSONBody_Role as a CreateUserJSONBodyRole1 +func (t CreateUserJSONBody_Role) AsCreateUserJSONBodyRole1() (CreateUserJSONBodyRole1, error) { + var body CreateUserJSONBodyRole1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateUserJSONBodyRole1 overwrites any union data inside the CreateUserJSONBody_Role as the provided CreateUserJSONBodyRole1 +func (t *CreateUserJSONBody_Role) FromCreateUserJSONBodyRole1(v CreateUserJSONBodyRole1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateUserJSONBodyRole1 performs a merge with any union data inside the CreateUserJSONBody_Role, using the provided CreateUserJSONBodyRole1 +func (t *CreateUserJSONBody_Role) MergeCreateUserJSONBodyRole1(v CreateUserJSONBodyRole1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t CreateUserJSONBody_Role) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *CreateUserJSONBody_Role) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsListUsersParamsFilterValue00 returns the union data inside the ListUsersParamsFilterValue0 as a ListUsersParamsFilterValue00 +func (t ListUsersParamsFilterValue0) AsListUsersParamsFilterValue00() (ListUsersParamsFilterValue00, error) { + var body ListUsersParamsFilterValue00 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue00 overwrites any union data inside the ListUsersParamsFilterValue0 as the provided ListUsersParamsFilterValue00 +func (t *ListUsersParamsFilterValue0) FromListUsersParamsFilterValue00(v ListUsersParamsFilterValue00) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue00 performs a merge with any union data inside the ListUsersParamsFilterValue0, using the provided ListUsersParamsFilterValue00 +func (t *ListUsersParamsFilterValue0) MergeListUsersParamsFilterValue00(v ListUsersParamsFilterValue00) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsListUsersParamsFilterValue01 returns the union data inside the ListUsersParamsFilterValue0 as a ListUsersParamsFilterValue01 +func (t ListUsersParamsFilterValue0) AsListUsersParamsFilterValue01() (ListUsersParamsFilterValue01, error) { + var body ListUsersParamsFilterValue01 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue01 overwrites any union data inside the ListUsersParamsFilterValue0 as the provided ListUsersParamsFilterValue01 +func (t *ListUsersParamsFilterValue0) FromListUsersParamsFilterValue01(v ListUsersParamsFilterValue01) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue01 performs a merge with any union data inside the ListUsersParamsFilterValue0, using the provided ListUsersParamsFilterValue01 +func (t *ListUsersParamsFilterValue0) MergeListUsersParamsFilterValue01(v ListUsersParamsFilterValue01) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ListUsersParamsFilterValue0) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ListUsersParamsFilterValue0) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsListUsersParamsFilterValue000 returns the union data inside the ListUsersParamsFilterValue00 as a ListUsersParamsFilterValue000 +func (t ListUsersParamsFilterValue00) AsListUsersParamsFilterValue000() (ListUsersParamsFilterValue000, error) { + var body ListUsersParamsFilterValue000 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue000 overwrites any union data inside the ListUsersParamsFilterValue00 as the provided ListUsersParamsFilterValue000 +func (t *ListUsersParamsFilterValue00) FromListUsersParamsFilterValue000(v ListUsersParamsFilterValue000) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue000 performs a merge with any union data inside the ListUsersParamsFilterValue00, using the provided ListUsersParamsFilterValue000 +func (t *ListUsersParamsFilterValue00) MergeListUsersParamsFilterValue000(v ListUsersParamsFilterValue000) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsListUsersParamsFilterValue001 returns the union data inside the ListUsersParamsFilterValue00 as a ListUsersParamsFilterValue001 +func (t ListUsersParamsFilterValue00) AsListUsersParamsFilterValue001() (ListUsersParamsFilterValue001, error) { + var body ListUsersParamsFilterValue001 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue001 overwrites any union data inside the ListUsersParamsFilterValue00 as the provided ListUsersParamsFilterValue001 +func (t *ListUsersParamsFilterValue00) FromListUsersParamsFilterValue001(v ListUsersParamsFilterValue001) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue001 performs a merge with any union data inside the ListUsersParamsFilterValue00, using the provided ListUsersParamsFilterValue001 +func (t *ListUsersParamsFilterValue00) MergeListUsersParamsFilterValue001(v ListUsersParamsFilterValue001) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ListUsersParamsFilterValue00) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ListUsersParamsFilterValue00) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsListUsersParamsFilterValue0000 returns the union data inside the ListUsersParamsFilterValue000 as a ListUsersParamsFilterValue0000 +func (t ListUsersParamsFilterValue000) AsListUsersParamsFilterValue0000() (ListUsersParamsFilterValue0000, error) { + var body ListUsersParamsFilterValue0000 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue0000 overwrites any union data inside the ListUsersParamsFilterValue000 as the provided ListUsersParamsFilterValue0000 +func (t *ListUsersParamsFilterValue000) FromListUsersParamsFilterValue0000(v ListUsersParamsFilterValue0000) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue0000 performs a merge with any union data inside the ListUsersParamsFilterValue000, using the provided ListUsersParamsFilterValue0000 +func (t *ListUsersParamsFilterValue000) MergeListUsersParamsFilterValue0000(v ListUsersParamsFilterValue0000) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsListUsersParamsFilterValue0001 returns the union data inside the ListUsersParamsFilterValue000 as a ListUsersParamsFilterValue0001 +func (t ListUsersParamsFilterValue000) AsListUsersParamsFilterValue0001() (ListUsersParamsFilterValue0001, error) { + var body ListUsersParamsFilterValue0001 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromListUsersParamsFilterValue0001 overwrites any union data inside the ListUsersParamsFilterValue000 as the provided ListUsersParamsFilterValue0001 +func (t *ListUsersParamsFilterValue000) FromListUsersParamsFilterValue0001(v ListUsersParamsFilterValue0001) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeListUsersParamsFilterValue0001 performs a merge with any union data inside the ListUsersParamsFilterValue000, using the provided ListUsersParamsFilterValue0001 +func (t *ListUsersParamsFilterValue000) MergeListUsersParamsFilterValue0001(v ListUsersParamsFilterValue0001) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t ListUsersParamsFilterValue000) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *ListUsersParamsFilterValue000) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsSetUserRoleJSONBodyRole0 returns the union data inside the SetUserRoleJSONBody_Role as a SetUserRoleJSONBodyRole0 +func (t SetUserRoleJSONBody_Role) AsSetUserRoleJSONBodyRole0() (SetUserRoleJSONBodyRole0, error) { + var body SetUserRoleJSONBodyRole0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromSetUserRoleJSONBodyRole0 overwrites any union data inside the SetUserRoleJSONBody_Role as the provided SetUserRoleJSONBodyRole0 +func (t *SetUserRoleJSONBody_Role) FromSetUserRoleJSONBodyRole0(v SetUserRoleJSONBodyRole0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeSetUserRoleJSONBodyRole0 performs a merge with any union data inside the SetUserRoleJSONBody_Role, using the provided SetUserRoleJSONBodyRole0 +func (t *SetUserRoleJSONBody_Role) MergeSetUserRoleJSONBodyRole0(v SetUserRoleJSONBodyRole0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsSetUserRoleJSONBodyRole1 returns the union data inside the SetUserRoleJSONBody_Role as a SetUserRoleJSONBodyRole1 +func (t SetUserRoleJSONBody_Role) AsSetUserRoleJSONBodyRole1() (SetUserRoleJSONBodyRole1, error) { + var body SetUserRoleJSONBodyRole1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromSetUserRoleJSONBodyRole1 overwrites any union data inside the SetUserRoleJSONBody_Role as the provided SetUserRoleJSONBodyRole1 +func (t *SetUserRoleJSONBody_Role) FromSetUserRoleJSONBodyRole1(v SetUserRoleJSONBodyRole1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeSetUserRoleJSONBodyRole1 performs a merge with any union data inside the SetUserRoleJSONBody_Role, using the provided SetUserRoleJSONBodyRole1 +func (t *SetUserRoleJSONBody_Role) MergeSetUserRoleJSONBodyRole1(v SetUserRoleJSONBodyRole1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t SetUserRoleJSONBody_Role) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *SetUserRoleJSONBody_Role) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthGetAccessTokenJSONBody0 returns the union data inside the PostApiAuthGetAccessTokenJSONBody as a PostApiAuthGetAccessTokenJSONBody0 +func (t PostApiAuthGetAccessTokenJSONBody) AsPostApiAuthGetAccessTokenJSONBody0() (PostApiAuthGetAccessTokenJSONBody0, error) { + var body PostApiAuthGetAccessTokenJSONBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthGetAccessTokenJSONBody0 overwrites any union data inside the PostApiAuthGetAccessTokenJSONBody as the provided PostApiAuthGetAccessTokenJSONBody0 +func (t *PostApiAuthGetAccessTokenJSONBody) FromPostApiAuthGetAccessTokenJSONBody0(v PostApiAuthGetAccessTokenJSONBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthGetAccessTokenJSONBody0 performs a merge with any union data inside the PostApiAuthGetAccessTokenJSONBody, using the provided PostApiAuthGetAccessTokenJSONBody0 +func (t *PostApiAuthGetAccessTokenJSONBody) MergePostApiAuthGetAccessTokenJSONBody0(v PostApiAuthGetAccessTokenJSONBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthGetAccessTokenJSONBody1 returns the union data inside the PostApiAuthGetAccessTokenJSONBody as a PostApiAuthGetAccessTokenJSONBody1 +func (t PostApiAuthGetAccessTokenJSONBody) AsPostApiAuthGetAccessTokenJSONBody1() (PostApiAuthGetAccessTokenJSONBody1, error) { + var body PostApiAuthGetAccessTokenJSONBody1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthGetAccessTokenJSONBody1 overwrites any union data inside the PostApiAuthGetAccessTokenJSONBody as the provided PostApiAuthGetAccessTokenJSONBody1 +func (t *PostApiAuthGetAccessTokenJSONBody) FromPostApiAuthGetAccessTokenJSONBody1(v PostApiAuthGetAccessTokenJSONBody1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthGetAccessTokenJSONBody1 performs a merge with any union data inside the PostApiAuthGetAccessTokenJSONBody, using the provided PostApiAuthGetAccessTokenJSONBody1 +func (t *PostApiAuthGetAccessTokenJSONBody) MergePostApiAuthGetAccessTokenJSONBody1(v PostApiAuthGetAccessTokenJSONBody1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthGetAccessTokenJSONBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthGetAccessTokenJSONBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsLinkSocialAccountJSONBodyProvider0 returns the union data inside the LinkSocialAccountJSONBody_Provider as a LinkSocialAccountJSONBodyProvider0 +func (t LinkSocialAccountJSONBody_Provider) AsLinkSocialAccountJSONBodyProvider0() (LinkSocialAccountJSONBodyProvider0, error) { + var body LinkSocialAccountJSONBodyProvider0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromLinkSocialAccountJSONBodyProvider0 overwrites any union data inside the LinkSocialAccountJSONBody_Provider as the provided LinkSocialAccountJSONBodyProvider0 +func (t *LinkSocialAccountJSONBody_Provider) FromLinkSocialAccountJSONBodyProvider0(v LinkSocialAccountJSONBodyProvider0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeLinkSocialAccountJSONBodyProvider0 performs a merge with any union data inside the LinkSocialAccountJSONBody_Provider, using the provided LinkSocialAccountJSONBodyProvider0 +func (t *LinkSocialAccountJSONBody_Provider) MergeLinkSocialAccountJSONBodyProvider0(v LinkSocialAccountJSONBodyProvider0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsLinkSocialAccountJSONBodyProvider1 returns the union data inside the LinkSocialAccountJSONBody_Provider as a LinkSocialAccountJSONBodyProvider1 +func (t LinkSocialAccountJSONBody_Provider) AsLinkSocialAccountJSONBodyProvider1() (LinkSocialAccountJSONBodyProvider1, error) { + var body LinkSocialAccountJSONBodyProvider1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromLinkSocialAccountJSONBodyProvider1 overwrites any union data inside the LinkSocialAccountJSONBody_Provider as the provided LinkSocialAccountJSONBodyProvider1 +func (t *LinkSocialAccountJSONBody_Provider) FromLinkSocialAccountJSONBodyProvider1(v LinkSocialAccountJSONBodyProvider1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeLinkSocialAccountJSONBodyProvider1 performs a merge with any union data inside the LinkSocialAccountJSONBody_Provider, using the provided LinkSocialAccountJSONBodyProvider1 +func (t *LinkSocialAccountJSONBody_Provider) MergeLinkSocialAccountJSONBodyProvider1(v LinkSocialAccountJSONBodyProvider1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t LinkSocialAccountJSONBody_Provider) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *LinkSocialAccountJSONBody_Provider) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthOauth2ConsentJSONBodyClaims0 returns the union data inside the PostApiAuthOauth2ConsentJSONBody_Claims as a PostApiAuthOauth2ConsentJSONBodyClaims0 +func (t PostApiAuthOauth2ConsentJSONBody_Claims) AsPostApiAuthOauth2ConsentJSONBodyClaims0() (PostApiAuthOauth2ConsentJSONBodyClaims0, error) { + var body PostApiAuthOauth2ConsentJSONBodyClaims0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2ConsentJSONBodyClaims0 overwrites any union data inside the PostApiAuthOauth2ConsentJSONBody_Claims as the provided PostApiAuthOauth2ConsentJSONBodyClaims0 +func (t *PostApiAuthOauth2ConsentJSONBody_Claims) FromPostApiAuthOauth2ConsentJSONBodyClaims0(v PostApiAuthOauth2ConsentJSONBodyClaims0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2ConsentJSONBodyClaims0 performs a merge with any union data inside the PostApiAuthOauth2ConsentJSONBody_Claims, using the provided PostApiAuthOauth2ConsentJSONBodyClaims0 +func (t *PostApiAuthOauth2ConsentJSONBody_Claims) MergePostApiAuthOauth2ConsentJSONBodyClaims0(v PostApiAuthOauth2ConsentJSONBodyClaims0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthOauth2ConsentJSONBodyClaims1 returns the union data inside the PostApiAuthOauth2ConsentJSONBody_Claims as a PostApiAuthOauth2ConsentJSONBodyClaims1 +func (t PostApiAuthOauth2ConsentJSONBody_Claims) AsPostApiAuthOauth2ConsentJSONBodyClaims1() (PostApiAuthOauth2ConsentJSONBodyClaims1, error) { + var body PostApiAuthOauth2ConsentJSONBodyClaims1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2ConsentJSONBodyClaims1 overwrites any union data inside the PostApiAuthOauth2ConsentJSONBody_Claims as the provided PostApiAuthOauth2ConsentJSONBodyClaims1 +func (t *PostApiAuthOauth2ConsentJSONBody_Claims) FromPostApiAuthOauth2ConsentJSONBodyClaims1(v PostApiAuthOauth2ConsentJSONBodyClaims1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2ConsentJSONBodyClaims1 performs a merge with any union data inside the PostApiAuthOauth2ConsentJSONBody_Claims, using the provided PostApiAuthOauth2ConsentJSONBodyClaims1 +func (t *PostApiAuthOauth2ConsentJSONBody_Claims) MergePostApiAuthOauth2ConsentJSONBodyClaims1(v PostApiAuthOauth2ConsentJSONBodyClaims1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthOauth2ConsentJSONBody_Claims) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthOauth2ConsentJSONBody_Claims) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthOauth2CreateClientJSONBodyJwks0 returns the union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks as a PostApiAuthOauth2CreateClientJSONBodyJwks0 +func (t PostApiAuthOauth2CreateClientJSONBody_Jwks) AsPostApiAuthOauth2CreateClientJSONBodyJwks0() (PostApiAuthOauth2CreateClientJSONBodyJwks0, error) { + var body PostApiAuthOauth2CreateClientJSONBodyJwks0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2CreateClientJSONBodyJwks0 overwrites any union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks as the provided PostApiAuthOauth2CreateClientJSONBodyJwks0 +func (t *PostApiAuthOauth2CreateClientJSONBody_Jwks) FromPostApiAuthOauth2CreateClientJSONBodyJwks0(v PostApiAuthOauth2CreateClientJSONBodyJwks0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2CreateClientJSONBodyJwks0 performs a merge with any union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks, using the provided PostApiAuthOauth2CreateClientJSONBodyJwks0 +func (t *PostApiAuthOauth2CreateClientJSONBody_Jwks) MergePostApiAuthOauth2CreateClientJSONBodyJwks0(v PostApiAuthOauth2CreateClientJSONBodyJwks0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthOauth2CreateClientJSONBodyJwks1 returns the union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks as a PostApiAuthOauth2CreateClientJSONBodyJwks1 +func (t PostApiAuthOauth2CreateClientJSONBody_Jwks) AsPostApiAuthOauth2CreateClientJSONBodyJwks1() (PostApiAuthOauth2CreateClientJSONBodyJwks1, error) { + var body PostApiAuthOauth2CreateClientJSONBodyJwks1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2CreateClientJSONBodyJwks1 overwrites any union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks as the provided PostApiAuthOauth2CreateClientJSONBodyJwks1 +func (t *PostApiAuthOauth2CreateClientJSONBody_Jwks) FromPostApiAuthOauth2CreateClientJSONBodyJwks1(v PostApiAuthOauth2CreateClientJSONBodyJwks1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2CreateClientJSONBodyJwks1 performs a merge with any union data inside the PostApiAuthOauth2CreateClientJSONBody_Jwks, using the provided PostApiAuthOauth2CreateClientJSONBodyJwks1 +func (t *PostApiAuthOauth2CreateClientJSONBody_Jwks) MergePostApiAuthOauth2CreateClientJSONBodyJwks1(v PostApiAuthOauth2CreateClientJSONBodyJwks1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthOauth2CreateClientJSONBody_Jwks) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthOauth2CreateClientJSONBody_Jwks) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthOauth2RegisterJSONBodyJwks0 returns the union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks as a PostApiAuthOauth2RegisterJSONBodyJwks0 +func (t PostApiAuthOauth2RegisterJSONBody_Jwks) AsPostApiAuthOauth2RegisterJSONBodyJwks0() (PostApiAuthOauth2RegisterJSONBodyJwks0, error) { + var body PostApiAuthOauth2RegisterJSONBodyJwks0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2RegisterJSONBodyJwks0 overwrites any union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks as the provided PostApiAuthOauth2RegisterJSONBodyJwks0 +func (t *PostApiAuthOauth2RegisterJSONBody_Jwks) FromPostApiAuthOauth2RegisterJSONBodyJwks0(v PostApiAuthOauth2RegisterJSONBodyJwks0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2RegisterJSONBodyJwks0 performs a merge with any union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks, using the provided PostApiAuthOauth2RegisterJSONBodyJwks0 +func (t *PostApiAuthOauth2RegisterJSONBody_Jwks) MergePostApiAuthOauth2RegisterJSONBodyJwks0(v PostApiAuthOauth2RegisterJSONBodyJwks0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthOauth2RegisterJSONBodyJwks1 returns the union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks as a PostApiAuthOauth2RegisterJSONBodyJwks1 +func (t PostApiAuthOauth2RegisterJSONBody_Jwks) AsPostApiAuthOauth2RegisterJSONBodyJwks1() (PostApiAuthOauth2RegisterJSONBodyJwks1, error) { + var body PostApiAuthOauth2RegisterJSONBodyJwks1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2RegisterJSONBodyJwks1 overwrites any union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks as the provided PostApiAuthOauth2RegisterJSONBodyJwks1 +func (t *PostApiAuthOauth2RegisterJSONBody_Jwks) FromPostApiAuthOauth2RegisterJSONBodyJwks1(v PostApiAuthOauth2RegisterJSONBodyJwks1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2RegisterJSONBodyJwks1 performs a merge with any union data inside the PostApiAuthOauth2RegisterJSONBody_Jwks, using the provided PostApiAuthOauth2RegisterJSONBodyJwks1 +func (t *PostApiAuthOauth2RegisterJSONBody_Jwks) MergePostApiAuthOauth2RegisterJSONBodyJwks1(v PostApiAuthOauth2RegisterJSONBodyJwks1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthOauth2RegisterJSONBody_Jwks) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthOauth2RegisterJSONBody_Jwks) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthOauth2TokenJSONBodyResource0 returns the union data inside the PostApiAuthOauth2TokenJSONBody_Resource as a PostApiAuthOauth2TokenJSONBodyResource0 +func (t PostApiAuthOauth2TokenJSONBody_Resource) AsPostApiAuthOauth2TokenJSONBodyResource0() (PostApiAuthOauth2TokenJSONBodyResource0, error) { + var body PostApiAuthOauth2TokenJSONBodyResource0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2TokenJSONBodyResource0 overwrites any union data inside the PostApiAuthOauth2TokenJSONBody_Resource as the provided PostApiAuthOauth2TokenJSONBodyResource0 +func (t *PostApiAuthOauth2TokenJSONBody_Resource) FromPostApiAuthOauth2TokenJSONBodyResource0(v PostApiAuthOauth2TokenJSONBodyResource0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2TokenJSONBodyResource0 performs a merge with any union data inside the PostApiAuthOauth2TokenJSONBody_Resource, using the provided PostApiAuthOauth2TokenJSONBodyResource0 +func (t *PostApiAuthOauth2TokenJSONBody_Resource) MergePostApiAuthOauth2TokenJSONBodyResource0(v PostApiAuthOauth2TokenJSONBodyResource0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthOauth2TokenJSONBodyResource1 returns the union data inside the PostApiAuthOauth2TokenJSONBody_Resource as a PostApiAuthOauth2TokenJSONBodyResource1 +func (t PostApiAuthOauth2TokenJSONBody_Resource) AsPostApiAuthOauth2TokenJSONBodyResource1() (PostApiAuthOauth2TokenJSONBodyResource1, error) { + var body PostApiAuthOauth2TokenJSONBodyResource1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthOauth2TokenJSONBodyResource1 overwrites any union data inside the PostApiAuthOauth2TokenJSONBody_Resource as the provided PostApiAuthOauth2TokenJSONBodyResource1 +func (t *PostApiAuthOauth2TokenJSONBody_Resource) FromPostApiAuthOauth2TokenJSONBodyResource1(v PostApiAuthOauth2TokenJSONBodyResource1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthOauth2TokenJSONBodyResource1 performs a merge with any union data inside the PostApiAuthOauth2TokenJSONBody_Resource, using the provided PostApiAuthOauth2TokenJSONBodyResource1 +func (t *PostApiAuthOauth2TokenJSONBody_Resource) MergePostApiAuthOauth2TokenJSONBodyResource1(v PostApiAuthOauth2TokenJSONBodyResource1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthOauth2TokenJSONBody_Resource) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthOauth2TokenJSONBody_Resource) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCreateOrganizationInvitationJSONBodyRole0 returns the union data inside the CreateOrganizationInvitationJSONBody_Role as a CreateOrganizationInvitationJSONBodyRole0 +func (t CreateOrganizationInvitationJSONBody_Role) AsCreateOrganizationInvitationJSONBodyRole0() (CreateOrganizationInvitationJSONBodyRole0, error) { + var body CreateOrganizationInvitationJSONBodyRole0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateOrganizationInvitationJSONBodyRole0 overwrites any union data inside the CreateOrganizationInvitationJSONBody_Role as the provided CreateOrganizationInvitationJSONBodyRole0 +func (t *CreateOrganizationInvitationJSONBody_Role) FromCreateOrganizationInvitationJSONBodyRole0(v CreateOrganizationInvitationJSONBodyRole0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateOrganizationInvitationJSONBodyRole0 performs a merge with any union data inside the CreateOrganizationInvitationJSONBody_Role, using the provided CreateOrganizationInvitationJSONBodyRole0 +func (t *CreateOrganizationInvitationJSONBody_Role) MergeCreateOrganizationInvitationJSONBodyRole0(v CreateOrganizationInvitationJSONBodyRole0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsCreateOrganizationInvitationJSONBodyRole1 returns the union data inside the CreateOrganizationInvitationJSONBody_Role as a CreateOrganizationInvitationJSONBodyRole1 +func (t CreateOrganizationInvitationJSONBody_Role) AsCreateOrganizationInvitationJSONBodyRole1() (CreateOrganizationInvitationJSONBodyRole1, error) { + var body CreateOrganizationInvitationJSONBodyRole1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateOrganizationInvitationJSONBodyRole1 overwrites any union data inside the CreateOrganizationInvitationJSONBody_Role as the provided CreateOrganizationInvitationJSONBodyRole1 +func (t *CreateOrganizationInvitationJSONBody_Role) FromCreateOrganizationInvitationJSONBodyRole1(v CreateOrganizationInvitationJSONBodyRole1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateOrganizationInvitationJSONBodyRole1 performs a merge with any union data inside the CreateOrganizationInvitationJSONBody_Role, using the provided CreateOrganizationInvitationJSONBodyRole1 +func (t *CreateOrganizationInvitationJSONBody_Role) MergeCreateOrganizationInvitationJSONBodyRole1(v CreateOrganizationInvitationJSONBodyRole1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t CreateOrganizationInvitationJSONBody_Role) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *CreateOrganizationInvitationJSONBody_Role) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsCreateOrganizationInvitationJSONBodyTeamId0 returns the union data inside the CreateOrganizationInvitationJSONBody_TeamId as a CreateOrganizationInvitationJSONBodyTeamId0 +func (t CreateOrganizationInvitationJSONBody_TeamId) AsCreateOrganizationInvitationJSONBodyTeamId0() (CreateOrganizationInvitationJSONBodyTeamId0, error) { + var body CreateOrganizationInvitationJSONBodyTeamId0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateOrganizationInvitationJSONBodyTeamId0 overwrites any union data inside the CreateOrganizationInvitationJSONBody_TeamId as the provided CreateOrganizationInvitationJSONBodyTeamId0 +func (t *CreateOrganizationInvitationJSONBody_TeamId) FromCreateOrganizationInvitationJSONBodyTeamId0(v CreateOrganizationInvitationJSONBodyTeamId0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateOrganizationInvitationJSONBodyTeamId0 performs a merge with any union data inside the CreateOrganizationInvitationJSONBody_TeamId, using the provided CreateOrganizationInvitationJSONBodyTeamId0 +func (t *CreateOrganizationInvitationJSONBody_TeamId) MergeCreateOrganizationInvitationJSONBodyTeamId0(v CreateOrganizationInvitationJSONBodyTeamId0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsCreateOrganizationInvitationJSONBodyTeamId1 returns the union data inside the CreateOrganizationInvitationJSONBody_TeamId as a CreateOrganizationInvitationJSONBodyTeamId1 +func (t CreateOrganizationInvitationJSONBody_TeamId) AsCreateOrganizationInvitationJSONBodyTeamId1() (CreateOrganizationInvitationJSONBodyTeamId1, error) { + var body CreateOrganizationInvitationJSONBodyTeamId1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromCreateOrganizationInvitationJSONBodyTeamId1 overwrites any union data inside the CreateOrganizationInvitationJSONBody_TeamId as the provided CreateOrganizationInvitationJSONBodyTeamId1 +func (t *CreateOrganizationInvitationJSONBody_TeamId) FromCreateOrganizationInvitationJSONBodyTeamId1(v CreateOrganizationInvitationJSONBodyTeamId1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeCreateOrganizationInvitationJSONBodyTeamId1 performs a merge with any union data inside the CreateOrganizationInvitationJSONBody_TeamId, using the provided CreateOrganizationInvitationJSONBodyTeamId1 +func (t *CreateOrganizationInvitationJSONBody_TeamId) MergeCreateOrganizationInvitationJSONBodyTeamId1(v CreateOrganizationInvitationJSONBodyTeamId1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t CreateOrganizationInvitationJSONBody_TeamId) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *CreateOrganizationInvitationJSONBody_TeamId) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsUpdateOrganizationMemberRoleJSONBodyRole0 returns the union data inside the UpdateOrganizationMemberRoleJSONBody_Role as a UpdateOrganizationMemberRoleJSONBodyRole0 +func (t UpdateOrganizationMemberRoleJSONBody_Role) AsUpdateOrganizationMemberRoleJSONBodyRole0() (UpdateOrganizationMemberRoleJSONBodyRole0, error) { + var body UpdateOrganizationMemberRoleJSONBodyRole0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUpdateOrganizationMemberRoleJSONBodyRole0 overwrites any union data inside the UpdateOrganizationMemberRoleJSONBody_Role as the provided UpdateOrganizationMemberRoleJSONBodyRole0 +func (t *UpdateOrganizationMemberRoleJSONBody_Role) FromUpdateOrganizationMemberRoleJSONBodyRole0(v UpdateOrganizationMemberRoleJSONBodyRole0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUpdateOrganizationMemberRoleJSONBodyRole0 performs a merge with any union data inside the UpdateOrganizationMemberRoleJSONBody_Role, using the provided UpdateOrganizationMemberRoleJSONBodyRole0 +func (t *UpdateOrganizationMemberRoleJSONBody_Role) MergeUpdateOrganizationMemberRoleJSONBodyRole0(v UpdateOrganizationMemberRoleJSONBodyRole0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsUpdateOrganizationMemberRoleJSONBodyRole1 returns the union data inside the UpdateOrganizationMemberRoleJSONBody_Role as a UpdateOrganizationMemberRoleJSONBodyRole1 +func (t UpdateOrganizationMemberRoleJSONBody_Role) AsUpdateOrganizationMemberRoleJSONBodyRole1() (UpdateOrganizationMemberRoleJSONBodyRole1, error) { + var body UpdateOrganizationMemberRoleJSONBodyRole1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromUpdateOrganizationMemberRoleJSONBodyRole1 overwrites any union data inside the UpdateOrganizationMemberRoleJSONBody_Role as the provided UpdateOrganizationMemberRoleJSONBodyRole1 +func (t *UpdateOrganizationMemberRoleJSONBody_Role) FromUpdateOrganizationMemberRoleJSONBodyRole1(v UpdateOrganizationMemberRoleJSONBodyRole1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeUpdateOrganizationMemberRoleJSONBodyRole1 performs a merge with any union data inside the UpdateOrganizationMemberRoleJSONBody_Role, using the provided UpdateOrganizationMemberRoleJSONBodyRole1 +func (t *UpdateOrganizationMemberRoleJSONBody_Role) MergeUpdateOrganizationMemberRoleJSONBodyRole1(v UpdateOrganizationMemberRoleJSONBodyRole1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t UpdateOrganizationMemberRoleJSONBody_Role) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *UpdateOrganizationMemberRoleJSONBody_Role) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsPostApiAuthRefreshTokenJSONBody0 returns the union data inside the PostApiAuthRefreshTokenJSONBody as a PostApiAuthRefreshTokenJSONBody0 +func (t PostApiAuthRefreshTokenJSONBody) AsPostApiAuthRefreshTokenJSONBody0() (PostApiAuthRefreshTokenJSONBody0, error) { + var body PostApiAuthRefreshTokenJSONBody0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthRefreshTokenJSONBody0 overwrites any union data inside the PostApiAuthRefreshTokenJSONBody as the provided PostApiAuthRefreshTokenJSONBody0 +func (t *PostApiAuthRefreshTokenJSONBody) FromPostApiAuthRefreshTokenJSONBody0(v PostApiAuthRefreshTokenJSONBody0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthRefreshTokenJSONBody0 performs a merge with any union data inside the PostApiAuthRefreshTokenJSONBody, using the provided PostApiAuthRefreshTokenJSONBody0 +func (t *PostApiAuthRefreshTokenJSONBody) MergePostApiAuthRefreshTokenJSONBody0(v PostApiAuthRefreshTokenJSONBody0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsPostApiAuthRefreshTokenJSONBody1 returns the union data inside the PostApiAuthRefreshTokenJSONBody as a PostApiAuthRefreshTokenJSONBody1 +func (t PostApiAuthRefreshTokenJSONBody) AsPostApiAuthRefreshTokenJSONBody1() (PostApiAuthRefreshTokenJSONBody1, error) { + var body PostApiAuthRefreshTokenJSONBody1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromPostApiAuthRefreshTokenJSONBody1 overwrites any union data inside the PostApiAuthRefreshTokenJSONBody as the provided PostApiAuthRefreshTokenJSONBody1 +func (t *PostApiAuthRefreshTokenJSONBody) FromPostApiAuthRefreshTokenJSONBody1(v PostApiAuthRefreshTokenJSONBody1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergePostApiAuthRefreshTokenJSONBody1 performs a merge with any union data inside the PostApiAuthRefreshTokenJSONBody, using the provided PostApiAuthRefreshTokenJSONBody1 +func (t *PostApiAuthRefreshTokenJSONBody) MergePostApiAuthRefreshTokenJSONBody1(v PostApiAuthRefreshTokenJSONBody1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t PostApiAuthRefreshTokenJSONBody) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *PostApiAuthRefreshTokenJSONBody) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + +// AsSocialSignInJSONBodyProvider0 returns the union data inside the SocialSignInJSONBody_Provider as a SocialSignInJSONBodyProvider0 +func (t SocialSignInJSONBody_Provider) AsSocialSignInJSONBodyProvider0() (SocialSignInJSONBodyProvider0, error) { + var body SocialSignInJSONBodyProvider0 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromSocialSignInJSONBodyProvider0 overwrites any union data inside the SocialSignInJSONBody_Provider as the provided SocialSignInJSONBodyProvider0 +func (t *SocialSignInJSONBody_Provider) FromSocialSignInJSONBodyProvider0(v SocialSignInJSONBodyProvider0) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeSocialSignInJSONBodyProvider0 performs a merge with any union data inside the SocialSignInJSONBody_Provider, using the provided SocialSignInJSONBodyProvider0 +func (t *SocialSignInJSONBody_Provider) MergeSocialSignInJSONBodyProvider0(v SocialSignInJSONBodyProvider0) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +// AsSocialSignInJSONBodyProvider1 returns the union data inside the SocialSignInJSONBody_Provider as a SocialSignInJSONBodyProvider1 +func (t SocialSignInJSONBody_Provider) AsSocialSignInJSONBodyProvider1() (SocialSignInJSONBodyProvider1, error) { + var body SocialSignInJSONBodyProvider1 + err := json.Unmarshal(t.union, &body) + return body, err +} + +// FromSocialSignInJSONBodyProvider1 overwrites any union data inside the SocialSignInJSONBody_Provider as the provided SocialSignInJSONBodyProvider1 +func (t *SocialSignInJSONBody_Provider) FromSocialSignInJSONBodyProvider1(v SocialSignInJSONBodyProvider1) error { + b, err := json.Marshal(v) + t.union = b + return err +} + +// MergeSocialSignInJSONBodyProvider1 performs a merge with any union data inside the SocialSignInJSONBody_Provider, using the provided SocialSignInJSONBodyProvider1 +func (t *SocialSignInJSONBody_Provider) MergeSocialSignInJSONBodyProvider1(v SocialSignInJSONBodyProvider1) error { + b, err := json.Marshal(v) + if err != nil { + return err + } + + merged, err := runtime.JSONMerge(t.union, b) + t.union = merged + return err +} + +func (t SocialSignInJSONBody_Provider) MarshalJSON() ([]byte, error) { + b, err := t.union.MarshalJSON() + return b, err +} + +func (t *SocialSignInJSONBody_Provider) UnmarshalJSON(b []byte) error { + err := t.union.UnmarshalJSON(b) + return err +} + // AsCreateBackgroundJobJSONBody0 returns the union data inside the CreateBackgroundJobJSONBody as a CreateBackgroundJobJSONBody0 func (t CreateBackgroundJobJSONBody) AsCreateBackgroundJobJSONBody0() (CreateBackgroundJobJSONBody0, error) { var body CreateBackgroundJobJSONBody0 @@ -9081,6 +10632,9 @@ type ClientInterface interface { PostApiAuthIsUsernameAvailable(ctx context.Context, body PostApiAuthIsUsernameAvailableJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetJSONWebKeySet request + GetJSONWebKeySet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // LinkSocialAccountWithBody request with any body LinkSocialAccountWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9095,8 +10649,8 @@ type ClientInterface interface { // GetApiAuthOauth2Authorize request GetApiAuthOauth2Authorize(ctx context.Context, params *GetApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetApiAuthOauth2CallbackProviderId request - GetApiAuthOauth2CallbackProviderId(ctx context.Context, providerId string, params *GetApiAuthOauth2CallbackProviderIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostApiAuthOauth2Authorize request + PostApiAuthOauth2Authorize(ctx context.Context, params *PostApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*http.Response, error) // PostApiAuthOauth2ClientRotateSecretWithBody request with any body PostApiAuthOauth2ClientRotateSecretWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9148,11 +10702,6 @@ type ClientInterface interface { PostApiAuthOauth2Introspect(ctx context.Context, body PostApiAuthOauth2IntrospectJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostApiAuthOauth2LinkWithBody request with any body - PostApiAuthOauth2LinkWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostApiAuthOauth2Link(ctx context.Context, body PostApiAuthOauth2LinkJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetApiAuthOauth2PublicClient request GetApiAuthOauth2PublicClient(ctx context.Context, params *GetApiAuthOauth2PublicClientParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9172,9 +10721,9 @@ type ClientInterface interface { PostApiAuthOauth2Revoke(ctx context.Context, body PostApiAuthOauth2RevokeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostApiAuthOauth2TokenWithBody request with any body - PostApiAuthOauth2TokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + PostApiAuthOauth2TokenWithBody(ctx context.Context, params *PostApiAuthOauth2TokenParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - PostApiAuthOauth2Token(ctx context.Context, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + PostApiAuthOauth2Token(ctx context.Context, params *PostApiAuthOauth2TokenParams, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) // PostApiAuthOauth2UpdateClientWithBody request with any body PostApiAuthOauth2UpdateClientWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9189,6 +10738,11 @@ type ClientInterface interface { // GetApiAuthOauth2Userinfo request GetApiAuthOauth2Userinfo(ctx context.Context, params *GetApiAuthOauth2UserinfoParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostApiAuthOauth2UserinfoWithBody request with any body + PostApiAuthOauth2UserinfoWithBody(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + PostApiAuthOauth2Userinfo(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, body PostApiAuthOauth2UserinfoJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetApiAuthOk request GetApiAuthOk(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9223,12 +10777,15 @@ type ClientInterface interface { // GetApiAuthOrganizationGetActiveMemberRole request GetApiAuthOrganizationGetActiveMemberRole(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetOrganization request - GetOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetFullOrganization request + GetFullOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // GetApiAuthOrganizationGetInvitation request GetApiAuthOrganizationGetInvitation(ctx context.Context, params *GetApiAuthOrganizationGetInvitationParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetOrganization request + GetOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostApiAuthOrganizationHasPermissionWithBody request with any body PostApiAuthOrganizationHasPermissionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9324,11 +10881,6 @@ type ClientInterface interface { SignInEmail(ctx context.Context, body SignInEmailJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // PostApiAuthSignInOauth2WithBody request with any body - PostApiAuthSignInOauth2WithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - PostApiAuthSignInOauth2(ctx context.Context, body PostApiAuthSignInOauth2JSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // SocialSignInWithBody request with any body SocialSignInWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9349,6 +10901,9 @@ type ClientInterface interface { SignUpWithEmailAndPassword(ctx context.Context, body SignUpWithEmailAndPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetJSONWebToken request + GetJSONWebToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostApiAuthUnlinkAccountWithBody request with any body PostApiAuthUnlinkAccountWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9501,6 +11056,9 @@ type ClientInterface interface { // MarkNotificationRead request MarkNotificationRead(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListOAuthResourceScopes request + ListOAuthResourceScopes(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // ListObjects request ListObjects(ctx context.Context, params *ListObjectsParams, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -9925,22 +11483,6 @@ type ClientInterface interface { // GetUserProfile request GetUserProfile(ctx context.Context, username string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // ListWorkspaceAgentApiKeys request - ListWorkspaceAgentApiKeys(ctx context.Context, orgId string, params *ListWorkspaceAgentApiKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) - - // CreateWorkspaceAgentApiKeyWithBody request with any body - CreateWorkspaceAgentApiKeyWithBody(ctx context.Context, orgId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateWorkspaceAgentApiKey(ctx context.Context, orgId string, body CreateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RevokeWorkspaceAgentApiKey request - RevokeWorkspaceAgentApiKey(ctx context.Context, orgId string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) - - // RotateWorkspaceAgentApiKeyWithBody request with any body - RotateWorkspaceAgentApiKeyWithBody(ctx context.Context, orgId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - RotateWorkspaceAgentApiKey(ctx context.Context, orgId string, keyId string, body RotateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) } func (c *Client) GetAgentOAuthConsentContext(ctx context.Context, params *GetAgentOAuthConsentContextParams, reqEditors ...RequestEditorFn) (*http.Response, error) { @@ -10759,6 +12301,18 @@ func (c *Client) PostApiAuthIsUsernameAvailable(ctx context.Context, body PostAp return c.Client.Do(req) } +func (c *Client) GetJSONWebKeySet(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetJSONWebKeySetRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) LinkSocialAccountWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewLinkSocialAccountRequestWithBody(c.Server, contentType, body) if err != nil { @@ -10819,8 +12373,8 @@ func (c *Client) GetApiAuthOauth2Authorize(ctx context.Context, params *GetApiAu return c.Client.Do(req) } -func (c *Client) GetApiAuthOauth2CallbackProviderId(ctx context.Context, providerId string, params *GetApiAuthOauth2CallbackProviderIdParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetApiAuthOauth2CallbackProviderIdRequest(c.Server, providerId, params) +func (c *Client) PostApiAuthOauth2Authorize(ctx context.Context, params *PostApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiAuthOauth2AuthorizeRequest(c.Server, params) if err != nil { return nil, err } @@ -11059,30 +12613,6 @@ func (c *Client) PostApiAuthOauth2Introspect(ctx context.Context, body PostApiAu return c.Client.Do(req) } -func (c *Client) PostApiAuthOauth2LinkWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthOauth2LinkRequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PostApiAuthOauth2Link(ctx context.Context, body PostApiAuthOauth2LinkJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthOauth2LinkRequest(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) GetApiAuthOauth2PublicClient(ctx context.Context, params *GetApiAuthOauth2PublicClientParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiAuthOauth2PublicClientRequest(c.Server, params) if err != nil { @@ -11167,8 +12697,8 @@ func (c *Client) PostApiAuthOauth2Revoke(ctx context.Context, body PostApiAuthOa return c.Client.Do(req) } -func (c *Client) PostApiAuthOauth2TokenWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthOauth2TokenRequestWithBody(c.Server, contentType, body) +func (c *Client) PostApiAuthOauth2TokenWithBody(ctx context.Context, params *PostApiAuthOauth2TokenParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiAuthOauth2TokenRequestWithBody(c.Server, params, contentType, body) if err != nil { return nil, err } @@ -11179,8 +12709,8 @@ func (c *Client) PostApiAuthOauth2TokenWithBody(ctx context.Context, contentType return c.Client.Do(req) } -func (c *Client) PostApiAuthOauth2Token(ctx context.Context, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthOauth2TokenRequest(c.Server, body) +func (c *Client) PostApiAuthOauth2Token(ctx context.Context, params *PostApiAuthOauth2TokenParams, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiAuthOauth2TokenRequest(c.Server, params, body) if err != nil { return nil, err } @@ -11251,6 +12781,30 @@ func (c *Client) GetApiAuthOauth2Userinfo(ctx context.Context, params *GetApiAut return c.Client.Do(req) } +func (c *Client) PostApiAuthOauth2UserinfoWithBody(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiAuthOauth2UserinfoRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PostApiAuthOauth2Userinfo(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, body PostApiAuthOauth2UserinfoJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPostApiAuthOauth2UserinfoRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) GetApiAuthOk(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewGetApiAuthOkRequest(c.Server) if err != nil { @@ -11407,8 +12961,8 @@ func (c *Client) GetApiAuthOrganizationGetActiveMemberRole(ctx context.Context, return c.Client.Do(req) } -func (c *Client) GetOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetOrganizationRequest(c.Server) +func (c *Client) GetFullOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetFullOrganizationRequest(c.Server) if err != nil { return nil, err } @@ -11431,6 +12985,18 @@ func (c *Client) GetApiAuthOrganizationGetInvitation(ctx context.Context, params return c.Client.Do(req) } +func (c *Client) GetOrganization(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetOrganizationRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) PostApiAuthOrganizationHasPermissionWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewPostApiAuthOrganizationHasPermissionRequestWithBody(c.Server, contentType, body) if err != nil { @@ -11875,30 +13441,6 @@ func (c *Client) SignInEmail(ctx context.Context, body SignInEmailJSONRequestBod return c.Client.Do(req) } -func (c *Client) PostApiAuthSignInOauth2WithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthSignInOauth2RequestWithBody(c.Server, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) PostApiAuthSignInOauth2(ctx context.Context, body PostApiAuthSignInOauth2JSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewPostApiAuthSignInOauth2Request(c.Server, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - func (c *Client) SocialSignInWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewSocialSignInRequestWithBody(c.Server, contentType, body) if err != nil { @@ -11995,6 +13537,18 @@ func (c *Client) SignUpWithEmailAndPassword(ctx context.Context, body SignUpWith return c.Client.Do(req) } +func (c *Client) GetJSONWebToken(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetJSONWebTokenRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) PostApiAuthUnlinkAccountWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewPostApiAuthUnlinkAccountRequestWithBody(c.Server, contentType, body) if err != nil { @@ -12667,6 +14221,18 @@ func (c *Client) MarkNotificationRead(ctx context.Context, id string, reqEditors return c.Client.Do(req) } +func (c *Client) ListOAuthResourceScopes(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListOAuthResourceScopesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ListObjects(ctx context.Context, params *ListObjectsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewListObjectsRequest(c.Server, params) if err != nil { @@ -14527,78 +16093,6 @@ func (c *Client) GetUserProfile(ctx context.Context, username string, reqEditors return c.Client.Do(req) } -func (c *Client) ListWorkspaceAgentApiKeys(ctx context.Context, orgId string, params *ListWorkspaceAgentApiKeysParams, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListWorkspaceAgentApiKeysRequest(c.Server, orgId, params) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateWorkspaceAgentApiKeyWithBody(ctx context.Context, orgId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateWorkspaceAgentApiKeyRequestWithBody(c.Server, orgId, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) CreateWorkspaceAgentApiKey(ctx context.Context, orgId string, body CreateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateWorkspaceAgentApiKeyRequest(c.Server, orgId, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RevokeWorkspaceAgentApiKey(ctx context.Context, orgId string, keyId string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRevokeWorkspaceAgentApiKeyRequest(c.Server, orgId, keyId) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RotateWorkspaceAgentApiKeyWithBody(ctx context.Context, orgId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRotateWorkspaceAgentApiKeyRequestWithBody(c.Server, orgId, keyId, contentType, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) RotateWorkspaceAgentApiKey(ctx context.Context, orgId string, keyId string, body RotateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewRotateWorkspaceAgentApiKeyRequest(c.Server, orgId, keyId, body) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - // NewGetAgentOAuthConsentContextRequest generates requests for GetAgentOAuthConsentContext func NewGetAgentOAuthConsentContextRequest(server string, params *GetAgentOAuthConsentContextParams) (*http.Request, error) { var err error @@ -15097,7 +16591,7 @@ func NewListUsersRequest(server string, params *ListUsersParams) (*http.Request, if params.Limit != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "limit", *params.Limit, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "limit", *params.Limit, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -15109,7 +16603,7 @@ func NewListUsersRequest(server string, params *ListUsersParams) (*http.Request, if params.Offset != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "offset", *params.Offset, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "offset", *params.Offset, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -15157,7 +16651,7 @@ func NewListUsersRequest(server string, params *ListUsersParams) (*http.Request, if params.FilterValue != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filterValue", *params.FilterValue, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "filterValue", *params.FilterValue, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -16368,6 +17862,33 @@ func NewPostApiAuthIsUsernameAvailableRequestWithBody(server string, contentType return req, nil } +// NewGetJSONWebKeySetRequest generates requests for GetJSONWebKeySet +func NewGetJSONWebKeySetRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/auth/jwks") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewLinkSocialAccountRequest calls the generic LinkSocialAccount builder with application/json body func NewLinkSocialAccountRequest(server string, body LinkSocialAccountJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -16594,6 +18115,30 @@ func NewGetApiAuthOauth2AuthorizeRequest(server string, params *GetApiAuthOauth2 } + if params.MaxAge != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "max_age", *params.MaxAge, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Resource != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "resource", *params.Resource, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + if params.Prompt != nil { if queryFrag, err := runtime.StyleParamWithOptions("form", true, "prompt", *params.Prompt, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { @@ -16620,23 +18165,16 @@ func NewGetApiAuthOauth2AuthorizeRequest(server string, params *GetApiAuthOauth2 return req, nil } -// NewGetApiAuthOauth2CallbackProviderIdRequest generates requests for GetApiAuthOauth2CallbackProviderId -func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId string, params *GetApiAuthOauth2CallbackProviderIdParams) (*http.Request, error) { +// NewPostApiAuthOauth2AuthorizeRequest generates requests for PostApiAuthOauth2Authorize +func NewPostApiAuthOauth2AuthorizeRequest(server string, params *PostApiAuthOauth2AuthorizeParams) (*http.Request, error) { var err error - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "providerId", providerId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/api/auth/oauth2/callback/%s", pathParam0) + operationPath := fmt.Sprintf("/api/auth/oauth2/authorize") if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -16655,9 +18193,9 @@ func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId stri // per the OpenAPI spec (e.g. "color=blue,black,brown"). var rawQueryFragments []string - if params.Code != nil { + if params.ResponseType != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "code", *params.Code, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "response_type", *params.ResponseType, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -16667,9 +18205,17 @@ func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId stri } - if params.Error != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "client_id", params.ClientId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "error", *params.Error, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if params.RedirectUri != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "redirect_uri", *params.RedirectUri, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: "uri"}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -16679,9 +18225,9 @@ func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId stri } - if params.ErrorDescription != nil { + if params.Scope != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "error_description", *params.ErrorDescription, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "scope", *params.Scope, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -16703,9 +18249,81 @@ func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId stri } - if params.Iss != nil { + if params.RequestUri != nil { - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "iss", *params.Iss, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "request_uri", *params.RequestUri, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.CodeChallenge != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "code_challenge", *params.CodeChallenge, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.CodeChallengeMethod != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "code_challenge_method", *params.CodeChallengeMethod, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Nonce != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "nonce", *params.Nonce, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.MaxAge != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "max_age", *params.MaxAge, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Resource != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "resource", *params.Resource, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "array", Format: ""}); err != nil { + return nil, err + } else { + for _, qp := range strings.Split(queryFrag, "&") { + rawQueryFragments = append(rawQueryFragments, qp) + } + } + + } + + if params.Prompt != nil { + + if queryFrag, err := runtime.StyleParamWithOptions("form", true, "prompt", *params.Prompt, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "string", Format: ""}); err != nil { return nil, err } else { for _, qp := range strings.Split(queryFrag, "&") { @@ -16721,7 +18339,7 @@ func NewGetApiAuthOauth2CallbackProviderIdRequest(server string, providerId stri queryURL.RawQuery = strings.Join(rawQueryFragments, "&") } - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + req, err := http.NewRequest(http.MethodPost, queryURL.String(), nil) if err != nil { return nil, err } @@ -17261,46 +18879,6 @@ func NewPostApiAuthOauth2IntrospectRequestWithBody(server string, contentType st return req, nil } -// NewPostApiAuthOauth2LinkRequest calls the generic PostApiAuthOauth2Link builder with application/json body -func NewPostApiAuthOauth2LinkRequest(server string, body PostApiAuthOauth2LinkJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPostApiAuthOauth2LinkRequestWithBody(server, "application/json", bodyReader) -} - -// NewPostApiAuthOauth2LinkRequestWithBody generates requests for PostApiAuthOauth2Link with any type of body -func NewPostApiAuthOauth2LinkRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/auth/oauth2/link") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - // NewGetApiAuthOauth2PublicClientRequest generates requests for GetApiAuthOauth2PublicClient func NewGetApiAuthOauth2PublicClientRequest(server string, params *GetApiAuthOauth2PublicClientParams) (*http.Request, error) { var err error @@ -17476,18 +19054,18 @@ func NewPostApiAuthOauth2RevokeRequestWithBody(server string, contentType string } // NewPostApiAuthOauth2TokenRequest calls the generic PostApiAuthOauth2Token builder with application/json body -func NewPostApiAuthOauth2TokenRequest(server string, body PostApiAuthOauth2TokenJSONRequestBody) (*http.Request, error) { +func NewPostApiAuthOauth2TokenRequest(server string, params *PostApiAuthOauth2TokenParams, body PostApiAuthOauth2TokenJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader buf, err := json.Marshal(body) if err != nil { return nil, err } bodyReader = bytes.NewReader(buf) - return NewPostApiAuthOauth2TokenRequestWithBody(server, "application/json", bodyReader) + return NewPostApiAuthOauth2TokenRequestWithBody(server, params, "application/json", bodyReader) } // NewPostApiAuthOauth2TokenRequestWithBody generates requests for PostApiAuthOauth2Token with any type of body -func NewPostApiAuthOauth2TokenRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +func NewPostApiAuthOauth2TokenRequestWithBody(server string, params *PostApiAuthOauth2TokenParams, contentType string, body io.Reader) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -17512,6 +19090,21 @@ func NewPostApiAuthOauth2TokenRequestWithBody(server string, contentType string, req.Header.Add("Content-Type", contentType) + if params != nil { + + if params.DPoP != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "DPoP", *params.DPoP, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("DPoP", headerParam0) + } + + } + return req, nil } @@ -17632,6 +19225,83 @@ func NewGetApiAuthOauth2UserinfoRequest(server string, params *GetApiAuthOauth2U req.Header.Set("Authorization", headerParam0) } + if params.DPoP != nil { + var headerParam1 string + + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "DPoP", *params.DPoP, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("DPoP", headerParam1) + } + + } + + return req, nil +} + +// NewPostApiAuthOauth2UserinfoRequest calls the generic PostApiAuthOauth2Userinfo builder with application/json body +func NewPostApiAuthOauth2UserinfoRequest(server string, params *PostApiAuthOauth2UserinfoParams, body PostApiAuthOauth2UserinfoJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewPostApiAuthOauth2UserinfoRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewPostApiAuthOauth2UserinfoRequestWithBody generates requests for PostApiAuthOauth2Userinfo with any type of body +func NewPostApiAuthOauth2UserinfoRequestWithBody(server string, params *PostApiAuthOauth2UserinfoParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/auth/oauth2/userinfo") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + if params.Authorization != nil { + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithOptions("simple", false, "Authorization", *params.Authorization, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", headerParam0) + } + + if params.DPoP != nil { + var headerParam1 string + + headerParam1, err = runtime.StyleParamWithOptions("simple", false, "DPoP", *params.DPoP, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationHeader, Type: "string", Format: ""}) + if err != nil { + return nil, err + } + + req.Header.Set("DPoP", headerParam1) + } + } return req, nil @@ -17918,8 +19588,8 @@ func NewGetApiAuthOrganizationGetActiveMemberRoleRequest(server string) (*http.R return req, nil } -// NewGetOrganizationRequest generates requests for GetOrganization -func NewGetOrganizationRequest(server string) (*http.Request, error) { +// NewGetFullOrganizationRequest generates requests for GetFullOrganization +func NewGetFullOrganizationRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -17999,6 +19669,33 @@ func NewGetApiAuthOrganizationGetInvitationRequest(server string, params *GetApi return req, nil } +// NewGetOrganizationRequest generates requests for GetOrganization +func NewGetOrganizationRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/auth/organization/get-organization") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPostApiAuthOrganizationHasPermissionRequest calls the generic PostApiAuthOrganizationHasPermission builder with application/json body func NewPostApiAuthOrganizationHasPermissionRequest(server string, body PostApiAuthOrganizationHasPermissionJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -18804,46 +20501,6 @@ func NewSignInEmailRequestWithBody(server string, contentType string, body io.Re return req, nil } -// NewPostApiAuthSignInOauth2Request calls the generic PostApiAuthSignInOauth2 builder with application/json body -func NewPostApiAuthSignInOauth2Request(server string, body PostApiAuthSignInOauth2JSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewPostApiAuthSignInOauth2RequestWithBody(server, "application/json", bodyReader) -} - -// NewPostApiAuthSignInOauth2RequestWithBody generates requests for PostApiAuthSignInOauth2 with any type of body -func NewPostApiAuthSignInOauth2RequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/auth/sign-in/oauth2") - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - // NewSocialSignInRequest calls the generic SocialSignIn builder with application/json body func NewSocialSignInRequest(server string, body SocialSignInJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -19004,6 +20661,33 @@ func NewSignUpWithEmailAndPasswordRequestWithBody(server string, contentType str return req, nil } +// NewGetJSONWebTokenRequest generates requests for GetJSONWebToken +func NewGetJSONWebTokenRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/auth/token") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPostApiAuthUnlinkAccountRequest calls the generic PostApiAuthUnlinkAccount builder with application/json body func NewPostApiAuthUnlinkAccountRequest(server string, body PostApiAuthUnlinkAccountJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -20754,6 +22438,33 @@ func NewMarkNotificationReadRequest(server string, id string) (*http.Request, er return req, nil } +// NewListOAuthResourceScopesRequest generates requests for ListOAuthResourceScopes +func NewListOAuthResourceScopesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/oauth-resource-scopes") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewListObjectsRequest generates requests for ListObjects func NewListObjectsRequest(server string, params *ListObjectsParams) (*http.Request, error) { var err error @@ -25819,221 +27530,6 @@ func NewGetUserProfileRequest(server string, username string) (*http.Request, er return req, nil } -// NewListWorkspaceAgentApiKeysRequest generates requests for ListWorkspaceAgentApiKeys -func NewListWorkspaceAgentApiKeysRequest(server string, orgId string, params *ListWorkspaceAgentApiKeysParams) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "orgId", orgId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/workspaces/%s/agent-api-keys", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - if params != nil { - // queryValues collects non-styled parameters (passthrough, JSON) - // that are safe to round-trip through url.Values.Encode(). - queryValues := queryURL.Query() - // rawQueryFragments collects pre-encoded query fragments from - // styled parameters, preserving literal commas as delimiters - // per the OpenAPI spec (e.g. "color=blue,black,brown"). - var rawQueryFragments []string - - if params.Page != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "page", *params.Page, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if params.PageSize != nil { - - if queryFrag, err := runtime.StyleParamWithOptions("form", true, "pageSize", *params.PageSize, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationQuery, Type: "integer", Format: ""}); err != nil { - return nil, err - } else { - for _, qp := range strings.Split(queryFrag, "&") { - rawQueryFragments = append(rawQueryFragments, qp) - } - } - - } - - if encoded := queryValues.Encode(); encoded != "" { - rawQueryFragments = append(rawQueryFragments, encoded) - } - queryURL.RawQuery = strings.Join(rawQueryFragments, "&") - } - - req, err := http.NewRequest(http.MethodGet, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewCreateWorkspaceAgentApiKeyRequest calls the generic CreateWorkspaceAgentApiKey builder with application/json body -func NewCreateWorkspaceAgentApiKeyRequest(server string, orgId string, body CreateWorkspaceAgentApiKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateWorkspaceAgentApiKeyRequestWithBody(server, orgId, "application/json", bodyReader) -} - -// NewCreateWorkspaceAgentApiKeyRequestWithBody generates requests for CreateWorkspaceAgentApiKey with any type of body -func NewCreateWorkspaceAgentApiKeyRequestWithBody(server string, orgId string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "orgId", orgId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/workspaces/%s/agent-api-keys", pathParam0) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - -// NewRevokeWorkspaceAgentApiKeyRequest generates requests for RevokeWorkspaceAgentApiKey -func NewRevokeWorkspaceAgentApiKeyRequest(server string, orgId string, keyId string) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "orgId", orgId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "keyId", keyId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/workspaces/%s/agent-api-keys/%s", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodDelete, queryURL.String(), nil) - if err != nil { - return nil, err - } - - return req, nil -} - -// NewRotateWorkspaceAgentApiKeyRequest calls the generic RotateWorkspaceAgentApiKey builder with application/json body -func NewRotateWorkspaceAgentApiKeyRequest(server string, orgId string, keyId string, body RotateWorkspaceAgentApiKeyJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewRotateWorkspaceAgentApiKeyRequestWithBody(server, orgId, keyId, "application/json", bodyReader) -} - -// NewRotateWorkspaceAgentApiKeyRequestWithBody generates requests for RotateWorkspaceAgentApiKey with any type of body -func NewRotateWorkspaceAgentApiKeyRequestWithBody(server string, orgId string, keyId string, contentType string, body io.Reader) (*http.Request, error) { - var err error - - var pathParam0 string - - pathParam0, err = runtime.StyleParamWithOptions("simple", false, "orgId", orgId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - var pathParam1 string - - pathParam1, err = runtime.StyleParamWithOptions("simple", false, "keyId", keyId, runtime.StyleParamOptions{ParamLocation: runtime.ParamLocationPath, Type: "string", Format: ""}) - if err != nil { - return nil, err - } - - serverURL, err := url.Parse(server) - if err != nil { - return nil, err - } - - operationPath := fmt.Sprintf("/api/workspaces/%s/agent-api-keys/%s/rotations", pathParam0, pathParam1) - if operationPath[0] == '/' { - operationPath = "." + operationPath - } - - queryURL, err := serverURL.Parse(operationPath) - if err != nil { - return nil, err - } - - req, err := http.NewRequest(http.MethodPost, queryURL.String(), body) - if err != nil { - return nil, err - } - - req.Header.Add("Content-Type", contentType) - - return req, nil -} - func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { for _, r := range c.RequestEditors { if err := r(ctx, req); err != nil { @@ -26254,6 +27750,9 @@ type ClientWithResponsesInterface interface { PostApiAuthIsUsernameAvailableWithResponse(ctx context.Context, body PostApiAuthIsUsernameAvailableJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthIsUsernameAvailableResponse, error) + // GetJSONWebKeySetWithResponse request + GetJSONWebKeySetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJSONWebKeySetResponse, error) + // LinkSocialAccountWithBodyWithResponse request with any body LinkSocialAccountWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LinkSocialAccountResponse, error) @@ -26268,8 +27767,8 @@ type ClientWithResponsesInterface interface { // GetApiAuthOauth2AuthorizeWithResponse request GetApiAuthOauth2AuthorizeWithResponse(ctx context.Context, params *GetApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2AuthorizeResponse, error) - // GetApiAuthOauth2CallbackProviderIdWithResponse request - GetApiAuthOauth2CallbackProviderIdWithResponse(ctx context.Context, providerId string, params *GetApiAuthOauth2CallbackProviderIdParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2CallbackProviderIdResponse, error) + // PostApiAuthOauth2AuthorizeWithResponse request + PostApiAuthOauth2AuthorizeWithResponse(ctx context.Context, params *PostApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2AuthorizeResponse, error) // PostApiAuthOauth2ClientRotateSecretWithBodyWithResponse request with any body PostApiAuthOauth2ClientRotateSecretWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2ClientRotateSecretResponse, error) @@ -26321,11 +27820,6 @@ type ClientWithResponsesInterface interface { PostApiAuthOauth2IntrospectWithResponse(ctx context.Context, body PostApiAuthOauth2IntrospectJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2IntrospectResponse, error) - // PostApiAuthOauth2LinkWithBodyWithResponse request with any body - PostApiAuthOauth2LinkWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2LinkResponse, error) - - PostApiAuthOauth2LinkWithResponse(ctx context.Context, body PostApiAuthOauth2LinkJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2LinkResponse, error) - // GetApiAuthOauth2PublicClientWithResponse request GetApiAuthOauth2PublicClientWithResponse(ctx context.Context, params *GetApiAuthOauth2PublicClientParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2PublicClientResponse, error) @@ -26345,9 +27839,9 @@ type ClientWithResponsesInterface interface { PostApiAuthOauth2RevokeWithResponse(ctx context.Context, body PostApiAuthOauth2RevokeJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2RevokeResponse, error) // PostApiAuthOauth2TokenWithBodyWithResponse request with any body - PostApiAuthOauth2TokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) + PostApiAuthOauth2TokenWithBodyWithResponse(ctx context.Context, params *PostApiAuthOauth2TokenParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) - PostApiAuthOauth2TokenWithResponse(ctx context.Context, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) + PostApiAuthOauth2TokenWithResponse(ctx context.Context, params *PostApiAuthOauth2TokenParams, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) // PostApiAuthOauth2UpdateClientWithBodyWithResponse request with any body PostApiAuthOauth2UpdateClientWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2UpdateClientResponse, error) @@ -26362,6 +27856,11 @@ type ClientWithResponsesInterface interface { // GetApiAuthOauth2UserinfoWithResponse request GetApiAuthOauth2UserinfoWithResponse(ctx context.Context, params *GetApiAuthOauth2UserinfoParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2UserinfoResponse, error) + // PostApiAuthOauth2UserinfoWithBodyWithResponse request with any body + PostApiAuthOauth2UserinfoWithBodyWithResponse(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2UserinfoResponse, error) + + PostApiAuthOauth2UserinfoWithResponse(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, body PostApiAuthOauth2UserinfoJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2UserinfoResponse, error) + // GetApiAuthOkWithResponse request GetApiAuthOkWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiAuthOkResponse, error) @@ -26396,12 +27895,15 @@ type ClientWithResponsesInterface interface { // GetApiAuthOrganizationGetActiveMemberRoleWithResponse request GetApiAuthOrganizationGetActiveMemberRoleWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiAuthOrganizationGetActiveMemberRoleResponse, error) - // GetOrganizationWithResponse request - GetOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOrganizationResponse, error) + // GetFullOrganizationWithResponse request + GetFullOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetFullOrganizationResponse, error) // GetApiAuthOrganizationGetInvitationWithResponse request GetApiAuthOrganizationGetInvitationWithResponse(ctx context.Context, params *GetApiAuthOrganizationGetInvitationParams, reqEditors ...RequestEditorFn) (*GetApiAuthOrganizationGetInvitationResponse, error) + // GetOrganizationWithResponse request + GetOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOrganizationResponse, error) + // PostApiAuthOrganizationHasPermissionWithBodyWithResponse request with any body PostApiAuthOrganizationHasPermissionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOrganizationHasPermissionResponse, error) @@ -26497,11 +27999,6 @@ type ClientWithResponsesInterface interface { SignInEmailWithResponse(ctx context.Context, body SignInEmailJSONRequestBody, reqEditors ...RequestEditorFn) (*SignInEmailResponse, error) - // PostApiAuthSignInOauth2WithBodyWithResponse request with any body - PostApiAuthSignInOauth2WithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthSignInOauth2Response, error) - - PostApiAuthSignInOauth2WithResponse(ctx context.Context, body PostApiAuthSignInOauth2JSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthSignInOauth2Response, error) - // SocialSignInWithBodyWithResponse request with any body SocialSignInWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SocialSignInResponse, error) @@ -26522,6 +28019,9 @@ type ClientWithResponsesInterface interface { SignUpWithEmailAndPasswordWithResponse(ctx context.Context, body SignUpWithEmailAndPasswordJSONRequestBody, reqEditors ...RequestEditorFn) (*SignUpWithEmailAndPasswordResponse, error) + // GetJSONWebTokenWithResponse request + GetJSONWebTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJSONWebTokenResponse, error) + // PostApiAuthUnlinkAccountWithBodyWithResponse request with any body PostApiAuthUnlinkAccountWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthUnlinkAccountResponse, error) @@ -26674,6 +28174,9 @@ type ClientWithResponsesInterface interface { // MarkNotificationReadWithResponse request MarkNotificationReadWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*MarkNotificationReadResponse, error) + // ListOAuthResourceScopesWithResponse request + ListOAuthResourceScopesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListOAuthResourceScopesResponse, error) + // ListObjectsWithResponse request ListObjectsWithResponse(ctx context.Context, params *ListObjectsParams, reqEditors ...RequestEditorFn) (*ListObjectsResponse, error) @@ -27098,22 +28601,6 @@ type ClientWithResponsesInterface interface { // GetUserProfileWithResponse request GetUserProfileWithResponse(ctx context.Context, username string, reqEditors ...RequestEditorFn) (*GetUserProfileResponse, error) - - // ListWorkspaceAgentApiKeysWithResponse request - ListWorkspaceAgentApiKeysWithResponse(ctx context.Context, orgId string, params *ListWorkspaceAgentApiKeysParams, reqEditors ...RequestEditorFn) (*ListWorkspaceAgentApiKeysResponse, error) - - // CreateWorkspaceAgentApiKeyWithBodyWithResponse request with any body - CreateWorkspaceAgentApiKeyWithBodyWithResponse(ctx context.Context, orgId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWorkspaceAgentApiKeyResponse, error) - - CreateWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, body CreateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWorkspaceAgentApiKeyResponse, error) - - // RevokeWorkspaceAgentApiKeyWithResponse request - RevokeWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, keyId string, reqEditors ...RequestEditorFn) (*RevokeWorkspaceAgentApiKeyResponse, error) - - // RotateWorkspaceAgentApiKeyWithBodyWithResponse request with any body - RotateWorkspaceAgentApiKeyWithBodyWithResponse(ctx context.Context, orgId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RotateWorkspaceAgentApiKeyResponse, error) - - RotateWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, keyId string, body RotateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*RotateWorkspaceAgentApiKeyResponse, error) } type GetAgentOAuthConsentContextResponse struct { @@ -27203,7 +28690,7 @@ type ListAgentOAuthGrantsResponse struct { JSON200 *struct { Items []struct { ClientId string `json:"clientId"` - ClientName *string `json:"clientName,omitempty"` + ClientName string `json:"clientName"` CreatedAt string `json:"createdAt"` Id string `json:"id"` LastUsedAt *string `json:"lastUsedAt"` @@ -27274,11 +28761,16 @@ type GetApiAuthAccountInfoResponse struct { Body []byte HTTPResponse *http.Response JSON200 *struct { + Account struct { + Id string `json:"id"` + Issuer string `json:"issuer"` + ProviderAccountId string `json:"providerAccountId"` + ProviderId string `json:"providerId"` + } `json:"account"` Data map[string]interface{} `json:"data"` User struct { Email *string `json:"email,omitempty"` EmailVerified bool `json:"emailVerified"` - Id string `json:"id"` Image *string `json:"image,omitempty"` Name *string `json:"name,omitempty"` } `json:"user"` @@ -29427,6 +30919,84 @@ func (r PostApiAuthIsUsernameAvailableResponse) ContentType() string { return "" } +type GetJSONWebKeySetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + // Keys Array of public JSON Web Keys + Keys []struct { + // Alg Algorithm intended for use with the key (e.g., 'EdDSA', 'RS256') + Alg string `json:"alg"` + + // Crv Curve name for elliptic curve keys (e.g., 'Ed25519', 'P-256') + Crv *string `json:"crv,omitempty"` + + // E Exponent for RSA keys (base64url-encoded) + E *string `json:"e,omitempty"` + + // Kid Key ID uniquely identifying the key, corresponds to the 'id' from the stored Jwk + Kid string `json:"kid"` + + // Kty Key type (e.g., 'RSA', 'EC', 'OKP') + Kty string `json:"kty"` + + // N Modulus for RSA keys (base64url-encoded) + N *string `json:"n,omitempty"` + + // Use Intended use of the public key (e.g., 'sig' for signature) + Use *GetJSONWebKeySet200JSONResponseBodyKeysUse `json:"use,omitempty"` + + // X X coordinate for elliptic curve keys (base64url-encoded) + X *string `json:"x,omitempty"` + + // Y Y coordinate for elliptic curve keys (base64url-encoded) + Y *string `json:"y,omitempty"` + } `json:"keys"` + } + JSON400 *struct { + Message string `json:"message"` + } + JSON401 *struct { + Message string `json:"message"` + } + JSON403 *struct { + Message *string `json:"message,omitempty"` + } + JSON404 *struct { + Message *string `json:"message,omitempty"` + } + JSON429 *struct { + Message *string `json:"message,omitempty"` + } + JSON500 *struct { + Message *string `json:"message,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetJSONWebKeySetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetJSONWebKeySetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetJSONWebKeySetResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type LinkSocialAccountResponse struct { Body []byte HTTPResponse *http.Response @@ -29486,13 +31056,14 @@ type ListUserAccountsResponse struct { Body []byte HTTPResponse *http.Response JSON200 *[]struct { - AccountId string `json:"accountId"` - CreatedAt time.Time `json:"createdAt"` - Id string `json:"id"` - ProviderId string `json:"providerId"` - Scopes []string `json:"scopes"` - UpdatedAt time.Time `json:"updatedAt"` - UserId string `json:"userId"` + CreatedAt time.Time `json:"createdAt"` + Id string `json:"id"` + Issuer string `json:"issuer"` + ProviderAccountId string `json:"providerAccountId"` + ProviderId string `json:"providerId"` + Scopes []string `json:"scopes"` + UpdatedAt time.Time `json:"updatedAt"` + UserId string `json:"userId"` } JSON400 *struct { Message string `json:"message"` @@ -29635,14 +31206,13 @@ func (r GetApiAuthOauth2AuthorizeResponse) ContentType() string { return "" } -type GetApiAuthOauth2CallbackProviderIdResponse struct { +type PostApiAuthOauth2AuthorizeResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *struct { - Url *string `json:"url,omitempty"` - } - JSON400 *struct { - Message string `json:"message"` + JSON400 *struct { + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + State *string `json:"state,omitempty"` } JSON401 *struct { Message string `json:"message"` @@ -29662,7 +31232,7 @@ type GetApiAuthOauth2CallbackProviderIdResponse struct { } // Status returns HTTPResponse.Status -func (r GetApiAuthOauth2CallbackProviderIdResponse) Status() string { +func (r PostApiAuthOauth2AuthorizeResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -29670,7 +31240,7 @@ func (r GetApiAuthOauth2CallbackProviderIdResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetApiAuthOauth2CallbackProviderIdResponse) StatusCode() int { +func (r PostApiAuthOauth2AuthorizeResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -29678,7 +31248,7 @@ func (r GetApiAuthOauth2CallbackProviderIdResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetApiAuthOauth2CallbackProviderIdResponse) ContentType() string { +func (r PostApiAuthOauth2AuthorizeResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } @@ -29837,7 +31407,7 @@ func (r PostApiAuthOauth2ContinueResponse) ContentType() string { type PostApiAuthOauth2CreateClientResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *struct { + JSON201 *struct { // ClientId Unique identifier for the client ClientId string `json:"client_id"` @@ -29862,8 +31432,8 @@ type PostApiAuthOauth2CreateClientResponse struct { // Disabled Whether the client is disabled Disabled *bool `json:"disabled,omitempty"` - // GrantTypes Requested authentication method for the token endpoint - GrantTypes *[]PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes `json:"grant_types,omitempty"` + // GrantTypes Grant types the client may use at the token endpoint + GrantTypes *[]string `json:"grant_types,omitempty"` // LogoUri Icon URI for the application LogoUri *string `json:"logo_uri,omitempty"` @@ -29880,8 +31450,8 @@ type PostApiAuthOauth2CreateClientResponse struct { // RedirectUris List of allowed redirect uris RedirectUris *[]string `json:"redirect_uris,omitempty"` - // ResponseTypes Requested authentication method for the token endpoint - ResponseTypes *[]PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes `json:"response_types,omitempty"` + // ResponseTypes Response types the client may use at the authorization endpoint + ResponseTypes *[]PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes `json:"response_types,omitempty"` // Scope Space-separated scopes allowed by the client Scope *string `json:"scope,omitempty"` @@ -29895,14 +31465,14 @@ type PostApiAuthOauth2CreateClientResponse struct { // SoftwareVersion Version identifier for the software_id SoftwareVersion *string `json:"software_version,omitempty"` - // TokenEndpointAuthMethod Response types the client may use - TokenEndpointAuthMethod *PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod `json:"token_endpoint_auth_method,omitempty"` + // TokenEndpointAuthMethod Requested authentication method for the token endpoint + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` // TosUri Client's terms of service uri TosUri *string `json:"tos_uri,omitempty"` // Type Type of the client - Type *PostApiAuthOauth2CreateClient200JSONResponseBodyType `json:"type,omitempty"` + Type *PostApiAuthOauth2CreateClient201JSONResponseBodyType `json:"type,omitempty"` // UserId ID of the user who registered the client, null if registered anonymously UserId *string `json:"user_id,omitempty"` @@ -30373,60 +31943,6 @@ func (r PostApiAuthOauth2IntrospectResponse) ContentType() string { return "" } -type PostApiAuthOauth2LinkResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - // Redirect Indicates that the client should redirect to the provided URL - Redirect PostApiAuthOauth2Link200JSONResponseBodyRedirect `json:"redirect"` - - // Url The authorization URL to redirect the user to for linking the OAuth2 account - Url string `json:"url"` - } - JSON400 *struct { - Message string `json:"message"` - } - JSON401 *struct { - Message string `json:"message"` - } - JSON403 *struct { - Message *string `json:"message,omitempty"` - } - JSON404 *struct { - Message *string `json:"message,omitempty"` - } - JSON429 *struct { - Message *string `json:"message,omitempty"` - } - JSON500 *struct { - Message *string `json:"message,omitempty"` - } -} - -// Status returns HTTPResponse.Status -func (r PostApiAuthOauth2LinkResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostApiAuthOauth2LinkResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r PostApiAuthOauth2LinkResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - type GetApiAuthOauth2PublicClientResponse struct { Body []byte HTTPResponse *http.Response @@ -30524,7 +32040,13 @@ func (r PostApiAuthOauth2PublicClientPreloginResponse) ContentType() string { type PostApiAuthOauth2RegisterResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *struct { + JSON201 *struct { + // BackchannelLogoutSessionRequired Whether the RP requires a `sid` claim in every Logout Token + BackchannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"` + + // BackchannelLogoutUri RP URL to receive signed Logout Tokens when the end-user's OP session terminates + BackchannelLogoutUri *string `json:"backchannel_logout_uri,omitempty"` + // ClientId Unique identifier for the client ClientId string `json:"client_id"` @@ -30549,8 +32071,8 @@ type PostApiAuthOauth2RegisterResponse struct { // Disabled Whether the client is disabled Disabled *bool `json:"disabled,omitempty"` - // GrantTypes Requested authentication method for the token endpoint - GrantTypes *[]PostApiAuthOauth2Register200JSONResponseBodyGrantTypes `json:"grant_types,omitempty"` + // GrantTypes Grant types the client may use at the token endpoint + GrantTypes *[]string `json:"grant_types,omitempty"` // LogoUri Icon URL for the application LogoUri *string `json:"logo_uri,omitempty"` @@ -30567,8 +32089,8 @@ type PostApiAuthOauth2RegisterResponse struct { // RedirectUris List of allowed redirect uris RedirectUris *[]string `json:"redirect_uris,omitempty"` - // ResponseTypes Requested authentication method for the token endpoint - ResponseTypes *[]PostApiAuthOauth2Register200JSONResponseBodyResponseTypes `json:"response_types,omitempty"` + // ResponseTypes Response types the client may use at the authorization endpoint + ResponseTypes *[]PostApiAuthOauth2Register201JSONResponseBodyResponseTypes `json:"response_types,omitempty"` // Scope Space-separated scopes allowed by the client Scope *string `json:"scope,omitempty"` @@ -30583,13 +32105,13 @@ type PostApiAuthOauth2RegisterResponse struct { SoftwareVersion *string `json:"software_version,omitempty"` // TokenEndpointAuthMethod Requested authentication method for the token endpoint - TokenEndpointAuthMethod *PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod `json:"token_endpoint_auth_method,omitempty"` + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` // TosUri Client's terms of service uri TosUri *string `json:"tos_uri,omitempty"` // Type Type of the client - Type *PostApiAuthOauth2Register200JSONResponseBodyType `json:"type,omitempty"` + Type *PostApiAuthOauth2Register201JSONResponseBodyType `json:"type,omitempty"` // UserId ID of the user who registered the client, null if registered anonymously UserId *string `json:"user_id,omitempty"` @@ -30921,6 +32443,77 @@ func (r GetApiAuthOauth2UserinfoResponse) ContentType() string { return "" } +type PostApiAuthOauth2UserinfoResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + // Email User's email address, included if 'email' scope is granted + Email *openapi_types.Email `json:"email,omitempty"` + + // EmailVerified Whether the email is verified, included if 'email' scope is granted + EmailVerified *bool `json:"email_verified,omitempty"` + + // FamilyName User's family name, included if 'profile' scope is granted + FamilyName *string `json:"family_name,omitempty"` + + // GivenName User's given name, included if 'profile' scope is granted + GivenName *string `json:"given_name,omitempty"` + + // Name User's full name, included if 'profile' scope is granted + Name *string `json:"name,omitempty"` + + // Picture User's profile picture URL, included if 'profile' scope is granted + Picture *string `json:"picture,omitempty"` + + // Sub Subject identifier (user ID) + Sub string `json:"sub"` + } + JSON400 *struct { + Message string `json:"message"` + } + JSON401 *struct { + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + } + JSON403 *struct { + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + } + JSON404 *struct { + Message *string `json:"message,omitempty"` + } + JSON429 *struct { + Message *string `json:"message,omitempty"` + } + JSON500 *struct { + Message *string `json:"message,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r PostApiAuthOauth2UserinfoResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PostApiAuthOauth2UserinfoResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r PostApiAuthOauth2UserinfoResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type GetApiAuthOkResponse struct { Body []byte HTTPResponse *http.Response @@ -31313,7 +32906,7 @@ func (r GetApiAuthOrganizationGetActiveMemberRoleResponse) ContentType() string return "" } -type GetOrganizationResponse struct { +type GetFullOrganizationResponse struct { Body []byte HTTPResponse *http.Response JSON200 *Organization @@ -31338,7 +32931,7 @@ type GetOrganizationResponse struct { } // Status returns HTTPResponse.Status -func (r GetOrganizationResponse) Status() string { +func (r GetFullOrganizationResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -31346,7 +32939,7 @@ func (r GetOrganizationResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetOrganizationResponse) StatusCode() int { +func (r GetFullOrganizationResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -31354,7 +32947,7 @@ func (r GetOrganizationResponse) StatusCode() int { } // ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r GetOrganizationResponse) ContentType() string { +func (r GetFullOrganizationResponse) ContentType() string { if r.HTTPResponse != nil { return r.HTTPResponse.Header.Get("Content-Type") } @@ -31420,6 +33013,54 @@ func (r GetApiAuthOrganizationGetInvitationResponse) ContentType() string { return "" } +type GetOrganizationResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Organization + JSON400 *struct { + Message string `json:"message"` + } + JSON401 *struct { + Message string `json:"message"` + } + JSON403 *struct { + Message *string `json:"message,omitempty"` + } + JSON404 *struct { + Message *string `json:"message,omitempty"` + } + JSON429 *struct { + Message *string `json:"message,omitempty"` + } + JSON500 *struct { + Message *string `json:"message,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetOrganizationResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetOrganizationResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetOrganizationResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type PostApiAuthOrganizationHasPermissionResponse struct { Body []byte HTTPResponse *http.Response @@ -32500,57 +34141,6 @@ func (r SignInEmailResponse) ContentType() string { return "" } -type PostApiAuthSignInOauth2Response struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - Redirect *bool `json:"redirect,omitempty"` - Url *string `json:"url,omitempty"` - } - JSON400 *struct { - Message string `json:"message"` - } - JSON401 *struct { - Message string `json:"message"` - } - JSON403 *struct { - Message *string `json:"message,omitempty"` - } - JSON404 *struct { - Message *string `json:"message,omitempty"` - } - JSON429 *struct { - Message *string `json:"message,omitempty"` - } - JSON500 *struct { - Message *string `json:"message,omitempty"` - } -} - -// Status returns HTTPResponse.Status -func (r PostApiAuthSignInOauth2Response) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r PostApiAuthSignInOauth2Response) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r PostApiAuthSignInOauth2Response) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - type SocialSignInResponse struct { Body []byte HTTPResponse *http.Response @@ -32791,6 +34381,56 @@ func (r SignUpWithEmailAndPasswordResponse) ContentType() string { return "" } +type GetJSONWebTokenResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Token *string `json:"token,omitempty"` + } + JSON400 *struct { + Message string `json:"message"` + } + JSON401 *struct { + Message string `json:"message"` + } + JSON403 *struct { + Message *string `json:"message,omitempty"` + } + JSON404 *struct { + Message *string `json:"message,omitempty"` + } + JSON429 *struct { + Message *string `json:"message,omitempty"` + } + JSON500 *struct { + Message *string `json:"message,omitempty"` + } +} + +// Status returns HTTPResponse.Status +func (r GetJSONWebTokenResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetJSONWebTokenResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r GetJSONWebTokenResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type PostApiAuthUnlinkAccountResponse struct { Body []byte HTTPResponse *http.Response @@ -34170,6 +35810,41 @@ func (r MarkNotificationReadResponse) ContentType() string { return "" } +type ListOAuthResourceScopesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Scopes []struct { + Description string `json:"description"` + Value string `json:"value"` + } `json:"scopes"` + } +} + +// Status returns HTTPResponse.Status +func (r ListOAuthResourceScopesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListOAuthResourceScopesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers +func (r ListOAuthResourceScopesResponse) ContentType() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Header.Get("Content-Type") + } + return "" +} + type ListObjectsResponse struct { Body []byte HTTPResponse *http.Response @@ -34228,6 +35903,8 @@ type CreateObjectResponse struct { Parts []struct { ExpiresAt string `json:"expiresAt"` Headers map[string]string `json:"headers"` + Length int `json:"length"` + Offset int `json:"offset"` PartNumber int `json:"partNumber"` Url string `json:"url"` } `json:"parts"` @@ -34236,6 +35913,34 @@ type CreateObjectResponse struct { SessionId string `json:"sessionId"` UploadId *string `json:"uploadId"` Urls []string `json:"urls"` + Workflow struct { + Abort struct { + Method CreateObject201JSONResponseBodyUploadWorkflowAbortMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId `json:"operationId"` + Path string `json:"path"` + } `json:"abort"` + Complete struct { + Method CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId `json:"operationId"` + PartsBodyField CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField `json:"partsBodyField"` + Path string `json:"path"` + } `json:"complete"` + RePresign struct { + Method CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId `json:"operationId"` + PartNumbersBodyField CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField `json:"partNumbersBodyField"` + Path string `json:"path"` + } `json:"rePresign"` + Upload struct { + ContentLengthField CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField `json:"contentLengthField"` + EtagHeader CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader `json:"etagHeader"` + FileOffsetField CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField `json:"fileOffsetField"` + HeadersField CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField `json:"headersField"` + Method CreateObject201JSONResponseBodyUploadWorkflowUploadMethod `json:"method"` + UrlField CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField `json:"urlField"` + } `json:"upload"` + Version CreateObject201JSONResponseBodyUploadWorkflowVersion `json:"version"` + } `json:"workflow"` } `json:"upload,omitempty"` } JSON400 *Error @@ -34525,6 +36230,8 @@ type PresignObjectUploadPartsResponse struct { Parts []struct { ExpiresAt string `json:"expiresAt"` Headers map[string]string `json:"headers"` + Length int `json:"length"` + Offset int `json:"offset"` PartNumber int `json:"partNumber"` Url string `json:"url"` } `json:"parts"` @@ -37890,175 +39597,6 @@ func (r GetUserProfileResponse) ContentType() string { return "" } -type ListWorkspaceAgentApiKeysResponse struct { - Body []byte - HTTPResponse *http.Response - JSON200 *struct { - Items []struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes `json:"scopes"` - Status ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"items"` - Page int `json:"page"` - PageSize int `json:"pageSize"` - Total int `json:"total"` - } - JSON403 *Error -} - -// Status returns HTTPResponse.Status -func (r ListWorkspaceAgentApiKeysResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r ListWorkspaceAgentApiKeysResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r ListWorkspaceAgentApiKeysResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type CreateWorkspaceAgentApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *struct { - Item struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes `json:"scopes"` - Status CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"item"` - Key string `json:"key"` - } - JSON400 *Error - JSON403 *Error -} - -// Status returns HTTPResponse.Status -func (r CreateWorkspaceAgentApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r CreateWorkspaceAgentApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r CreateWorkspaceAgentApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type RevokeWorkspaceAgentApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON403 *Error - JSON404 *Error -} - -// Status returns HTTPResponse.Status -func (r RevokeWorkspaceAgentApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r RevokeWorkspaceAgentApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RevokeWorkspaceAgentApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - -type RotateWorkspaceAgentApiKeyResponse struct { - Body []byte - HTTPResponse *http.Response - JSON201 *struct { - Item struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes `json:"scopes"` - Status RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"item"` - Key string `json:"key"` - } - JSON400 *Error - JSON403 *Error - JSON404 *Error - JSON409 *Error -} - -// Status returns HTTPResponse.Status -func (r RotateWorkspaceAgentApiKeyResponse) Status() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Status - } - return http.StatusText(0) -} - -// StatusCode returns HTTPResponse.StatusCode -func (r RotateWorkspaceAgentApiKeyResponse) StatusCode() int { - if r.HTTPResponse != nil { - return r.HTTPResponse.StatusCode - } - return 0 -} - -// ContentType is a convenience method to retrieve the Content-Type value from the HTTP response headers -func (r RotateWorkspaceAgentApiKeyResponse) ContentType() string { - if r.HTTPResponse != nil { - return r.HTTPResponse.Header.Get("Content-Type") - } - return "" -} - // GetAgentOAuthConsentContextWithResponse request returning *GetAgentOAuthConsentContextResponse func (c *ClientWithResponses) GetAgentOAuthConsentContextWithResponse(ctx context.Context, params *GetAgentOAuthConsentContextParams, reqEditors ...RequestEditorFn) (*GetAgentOAuthConsentContextResponse, error) { rsp, err := c.GetAgentOAuthConsentContext(ctx, params, reqEditors...) @@ -38644,6 +40182,15 @@ func (c *ClientWithResponses) PostApiAuthIsUsernameAvailableWithResponse(ctx con return ParsePostApiAuthIsUsernameAvailableResponse(rsp) } +// GetJSONWebKeySetWithResponse request returning *GetJSONWebKeySetResponse +func (c *ClientWithResponses) GetJSONWebKeySetWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJSONWebKeySetResponse, error) { + rsp, err := c.GetJSONWebKeySet(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetJSONWebKeySetResponse(rsp) +} + // LinkSocialAccountWithBodyWithResponse request with arbitrary body returning *LinkSocialAccountResponse func (c *ClientWithResponses) LinkSocialAccountWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*LinkSocialAccountResponse, error) { rsp, err := c.LinkSocialAccountWithBody(ctx, contentType, body, reqEditors...) @@ -38688,13 +40235,13 @@ func (c *ClientWithResponses) GetApiAuthOauth2AuthorizeWithResponse(ctx context. return ParseGetApiAuthOauth2AuthorizeResponse(rsp) } -// GetApiAuthOauth2CallbackProviderIdWithResponse request returning *GetApiAuthOauth2CallbackProviderIdResponse -func (c *ClientWithResponses) GetApiAuthOauth2CallbackProviderIdWithResponse(ctx context.Context, providerId string, params *GetApiAuthOauth2CallbackProviderIdParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2CallbackProviderIdResponse, error) { - rsp, err := c.GetApiAuthOauth2CallbackProviderId(ctx, providerId, params, reqEditors...) +// PostApiAuthOauth2AuthorizeWithResponse request returning *PostApiAuthOauth2AuthorizeResponse +func (c *ClientWithResponses) PostApiAuthOauth2AuthorizeWithResponse(ctx context.Context, params *PostApiAuthOauth2AuthorizeParams, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2AuthorizeResponse, error) { + rsp, err := c.PostApiAuthOauth2Authorize(ctx, params, reqEditors...) if err != nil { return nil, err } - return ParseGetApiAuthOauth2CallbackProviderIdResponse(rsp) + return ParsePostApiAuthOauth2AuthorizeResponse(rsp) } // PostApiAuthOauth2ClientRotateSecretWithBodyWithResponse request with arbitrary body returning *PostApiAuthOauth2ClientRotateSecretResponse @@ -38861,23 +40408,6 @@ func (c *ClientWithResponses) PostApiAuthOauth2IntrospectWithResponse(ctx contex return ParsePostApiAuthOauth2IntrospectResponse(rsp) } -// PostApiAuthOauth2LinkWithBodyWithResponse request with arbitrary body returning *PostApiAuthOauth2LinkResponse -func (c *ClientWithResponses) PostApiAuthOauth2LinkWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2LinkResponse, error) { - rsp, err := c.PostApiAuthOauth2LinkWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostApiAuthOauth2LinkResponse(rsp) -} - -func (c *ClientWithResponses) PostApiAuthOauth2LinkWithResponse(ctx context.Context, body PostApiAuthOauth2LinkJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2LinkResponse, error) { - rsp, err := c.PostApiAuthOauth2Link(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostApiAuthOauth2LinkResponse(rsp) -} - // GetApiAuthOauth2PublicClientWithResponse request returning *GetApiAuthOauth2PublicClientResponse func (c *ClientWithResponses) GetApiAuthOauth2PublicClientWithResponse(ctx context.Context, params *GetApiAuthOauth2PublicClientParams, reqEditors ...RequestEditorFn) (*GetApiAuthOauth2PublicClientResponse, error) { rsp, err := c.GetApiAuthOauth2PublicClient(ctx, params, reqEditors...) @@ -38939,16 +40469,16 @@ func (c *ClientWithResponses) PostApiAuthOauth2RevokeWithResponse(ctx context.Co } // PostApiAuthOauth2TokenWithBodyWithResponse request with arbitrary body returning *PostApiAuthOauth2TokenResponse -func (c *ClientWithResponses) PostApiAuthOauth2TokenWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) { - rsp, err := c.PostApiAuthOauth2TokenWithBody(ctx, contentType, body, reqEditors...) +func (c *ClientWithResponses) PostApiAuthOauth2TokenWithBodyWithResponse(ctx context.Context, params *PostApiAuthOauth2TokenParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) { + rsp, err := c.PostApiAuthOauth2TokenWithBody(ctx, params, contentType, body, reqEditors...) if err != nil { return nil, err } return ParsePostApiAuthOauth2TokenResponse(rsp) } -func (c *ClientWithResponses) PostApiAuthOauth2TokenWithResponse(ctx context.Context, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) { - rsp, err := c.PostApiAuthOauth2Token(ctx, body, reqEditors...) +func (c *ClientWithResponses) PostApiAuthOauth2TokenWithResponse(ctx context.Context, params *PostApiAuthOauth2TokenParams, body PostApiAuthOauth2TokenJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2TokenResponse, error) { + rsp, err := c.PostApiAuthOauth2Token(ctx, params, body, reqEditors...) if err != nil { return nil, err } @@ -38998,6 +40528,23 @@ func (c *ClientWithResponses) GetApiAuthOauth2UserinfoWithResponse(ctx context.C return ParseGetApiAuthOauth2UserinfoResponse(rsp) } +// PostApiAuthOauth2UserinfoWithBodyWithResponse request with arbitrary body returning *PostApiAuthOauth2UserinfoResponse +func (c *ClientWithResponses) PostApiAuthOauth2UserinfoWithBodyWithResponse(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2UserinfoResponse, error) { + rsp, err := c.PostApiAuthOauth2UserinfoWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostApiAuthOauth2UserinfoResponse(rsp) +} + +func (c *ClientWithResponses) PostApiAuthOauth2UserinfoWithResponse(ctx context.Context, params *PostApiAuthOauth2UserinfoParams, body PostApiAuthOauth2UserinfoJSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthOauth2UserinfoResponse, error) { + rsp, err := c.PostApiAuthOauth2Userinfo(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParsePostApiAuthOauth2UserinfoResponse(rsp) +} + // GetApiAuthOkWithResponse request returning *GetApiAuthOkResponse func (c *ClientWithResponses) GetApiAuthOkWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetApiAuthOkResponse, error) { rsp, err := c.GetApiAuthOk(ctx, reqEditors...) @@ -39110,13 +40657,13 @@ func (c *ClientWithResponses) GetApiAuthOrganizationGetActiveMemberRoleWithRespo return ParseGetApiAuthOrganizationGetActiveMemberRoleResponse(rsp) } -// GetOrganizationWithResponse request returning *GetOrganizationResponse -func (c *ClientWithResponses) GetOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOrganizationResponse, error) { - rsp, err := c.GetOrganization(ctx, reqEditors...) +// GetFullOrganizationWithResponse request returning *GetFullOrganizationResponse +func (c *ClientWithResponses) GetFullOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetFullOrganizationResponse, error) { + rsp, err := c.GetFullOrganization(ctx, reqEditors...) if err != nil { return nil, err } - return ParseGetOrganizationResponse(rsp) + return ParseGetFullOrganizationResponse(rsp) } // GetApiAuthOrganizationGetInvitationWithResponse request returning *GetApiAuthOrganizationGetInvitationResponse @@ -39128,6 +40675,15 @@ func (c *ClientWithResponses) GetApiAuthOrganizationGetInvitationWithResponse(ct return ParseGetApiAuthOrganizationGetInvitationResponse(rsp) } +// GetOrganizationWithResponse request returning *GetOrganizationResponse +func (c *ClientWithResponses) GetOrganizationWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetOrganizationResponse, error) { + rsp, err := c.GetOrganization(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetOrganizationResponse(rsp) +} + // PostApiAuthOrganizationHasPermissionWithBodyWithResponse request with arbitrary body returning *PostApiAuthOrganizationHasPermissionResponse func (c *ClientWithResponses) PostApiAuthOrganizationHasPermissionWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthOrganizationHasPermissionResponse, error) { rsp, err := c.PostApiAuthOrganizationHasPermissionWithBody(ctx, contentType, body, reqEditors...) @@ -39445,23 +41001,6 @@ func (c *ClientWithResponses) SignInEmailWithResponse(ctx context.Context, body return ParseSignInEmailResponse(rsp) } -// PostApiAuthSignInOauth2WithBodyWithResponse request with arbitrary body returning *PostApiAuthSignInOauth2Response -func (c *ClientWithResponses) PostApiAuthSignInOauth2WithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthSignInOauth2Response, error) { - rsp, err := c.PostApiAuthSignInOauth2WithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostApiAuthSignInOauth2Response(rsp) -} - -func (c *ClientWithResponses) PostApiAuthSignInOauth2WithResponse(ctx context.Context, body PostApiAuthSignInOauth2JSONRequestBody, reqEditors ...RequestEditorFn) (*PostApiAuthSignInOauth2Response, error) { - rsp, err := c.PostApiAuthSignInOauth2(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParsePostApiAuthSignInOauth2Response(rsp) -} - // SocialSignInWithBodyWithResponse request with arbitrary body returning *SocialSignInResponse func (c *ClientWithResponses) SocialSignInWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SocialSignInResponse, error) { rsp, err := c.SocialSignInWithBody(ctx, contentType, body, reqEditors...) @@ -39530,6 +41069,15 @@ func (c *ClientWithResponses) SignUpWithEmailAndPasswordWithResponse(ctx context return ParseSignUpWithEmailAndPasswordResponse(rsp) } +// GetJSONWebTokenWithResponse request returning *GetJSONWebTokenResponse +func (c *ClientWithResponses) GetJSONWebTokenWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetJSONWebTokenResponse, error) { + rsp, err := c.GetJSONWebToken(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetJSONWebTokenResponse(rsp) +} + // PostApiAuthUnlinkAccountWithBodyWithResponse request with arbitrary body returning *PostApiAuthUnlinkAccountResponse func (c *ClientWithResponses) PostApiAuthUnlinkAccountWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostApiAuthUnlinkAccountResponse, error) { rsp, err := c.PostApiAuthUnlinkAccountWithBody(ctx, contentType, body, reqEditors...) @@ -40018,6 +41566,15 @@ func (c *ClientWithResponses) MarkNotificationReadWithResponse(ctx context.Conte return ParseMarkNotificationReadResponse(rsp) } +// ListOAuthResourceScopesWithResponse request returning *ListOAuthResourceScopesResponse +func (c *ClientWithResponses) ListOAuthResourceScopesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListOAuthResourceScopesResponse, error) { + rsp, err := c.ListOAuthResourceScopes(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListOAuthResourceScopesResponse(rsp) +} + // ListObjectsWithResponse request returning *ListObjectsResponse func (c *ClientWithResponses) ListObjectsWithResponse(ctx context.Context, params *ListObjectsParams, reqEditors ...RequestEditorFn) (*ListObjectsResponse, error) { rsp, err := c.ListObjects(ctx, params, reqEditors...) @@ -41373,58 +42930,6 @@ func (c *ClientWithResponses) GetUserProfileWithResponse(ctx context.Context, us return ParseGetUserProfileResponse(rsp) } -// ListWorkspaceAgentApiKeysWithResponse request returning *ListWorkspaceAgentApiKeysResponse -func (c *ClientWithResponses) ListWorkspaceAgentApiKeysWithResponse(ctx context.Context, orgId string, params *ListWorkspaceAgentApiKeysParams, reqEditors ...RequestEditorFn) (*ListWorkspaceAgentApiKeysResponse, error) { - rsp, err := c.ListWorkspaceAgentApiKeys(ctx, orgId, params, reqEditors...) - if err != nil { - return nil, err - } - return ParseListWorkspaceAgentApiKeysResponse(rsp) -} - -// CreateWorkspaceAgentApiKeyWithBodyWithResponse request with arbitrary body returning *CreateWorkspaceAgentApiKeyResponse -func (c *ClientWithResponses) CreateWorkspaceAgentApiKeyWithBodyWithResponse(ctx context.Context, orgId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWorkspaceAgentApiKeyResponse, error) { - rsp, err := c.CreateWorkspaceAgentApiKeyWithBody(ctx, orgId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateWorkspaceAgentApiKeyResponse(rsp) -} - -func (c *ClientWithResponses) CreateWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, body CreateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWorkspaceAgentApiKeyResponse, error) { - rsp, err := c.CreateWorkspaceAgentApiKey(ctx, orgId, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateWorkspaceAgentApiKeyResponse(rsp) -} - -// RevokeWorkspaceAgentApiKeyWithResponse request returning *RevokeWorkspaceAgentApiKeyResponse -func (c *ClientWithResponses) RevokeWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, keyId string, reqEditors ...RequestEditorFn) (*RevokeWorkspaceAgentApiKeyResponse, error) { - rsp, err := c.RevokeWorkspaceAgentApiKey(ctx, orgId, keyId, reqEditors...) - if err != nil { - return nil, err - } - return ParseRevokeWorkspaceAgentApiKeyResponse(rsp) -} - -// RotateWorkspaceAgentApiKeyWithBodyWithResponse request with arbitrary body returning *RotateWorkspaceAgentApiKeyResponse -func (c *ClientWithResponses) RotateWorkspaceAgentApiKeyWithBodyWithResponse(ctx context.Context, orgId string, keyId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*RotateWorkspaceAgentApiKeyResponse, error) { - rsp, err := c.RotateWorkspaceAgentApiKeyWithBody(ctx, orgId, keyId, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseRotateWorkspaceAgentApiKeyResponse(rsp) -} - -func (c *ClientWithResponses) RotateWorkspaceAgentApiKeyWithResponse(ctx context.Context, orgId string, keyId string, body RotateWorkspaceAgentApiKeyJSONRequestBody, reqEditors ...RequestEditorFn) (*RotateWorkspaceAgentApiKeyResponse, error) { - rsp, err := c.RotateWorkspaceAgentApiKey(ctx, orgId, keyId, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseRotateWorkspaceAgentApiKeyResponse(rsp) -} - // ParseGetAgentOAuthConsentContextResponse parses an HTTP response from a GetAgentOAuthConsentContextWithResponse call func ParseGetAgentOAuthConsentContextResponse(rsp *http.Response) (*GetAgentOAuthConsentContextResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -41540,7 +43045,7 @@ func ParseListAgentOAuthGrantsResponse(rsp *http.Response) (*ListAgentOAuthGrant var dest struct { Items []struct { ClientId string `json:"clientId"` - ClientName *string `json:"clientName,omitempty"` + ClientName string `json:"clientName"` CreatedAt string `json:"createdAt"` Id string `json:"id"` LastUsedAt *string `json:"lastUsedAt"` @@ -41603,11 +43108,16 @@ func ParseGetApiAuthAccountInfoResponse(rsp *http.Response) (*GetApiAuthAccountI switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: var dest struct { + Account struct { + Id string `json:"id"` + Issuer string `json:"issuer"` + ProviderAccountId string `json:"providerAccountId"` + ProviderId string `json:"providerId"` + } `json:"account"` Data map[string]interface{} `json:"data"` User struct { Email *string `json:"email,omitempty"` EmailVerified bool `json:"emailVerified"` - Id string `json:"id"` Image *string `json:"image,omitempty"` Name *string `json:"name,omitempty"` } `json:"user"` @@ -44892,6 +46402,116 @@ func ParsePostApiAuthIsUsernameAvailableResponse(rsp *http.Response) (*PostApiAu return response, nil } +// ParseGetJSONWebKeySetResponse parses an HTTP response from a GetJSONWebKeySetWithResponse call +func ParseGetJSONWebKeySetResponse(rsp *http.Response) (*GetJSONWebKeySetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetJSONWebKeySetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + // Keys Array of public JSON Web Keys + Keys []struct { + // Alg Algorithm intended for use with the key (e.g., 'EdDSA', 'RS256') + Alg string `json:"alg"` + + // Crv Curve name for elliptic curve keys (e.g., 'Ed25519', 'P-256') + Crv *string `json:"crv,omitempty"` + + // E Exponent for RSA keys (base64url-encoded) + E *string `json:"e,omitempty"` + + // Kid Key ID uniquely identifying the key, corresponds to the 'id' from the stored Jwk + Kid string `json:"kid"` + + // Kty Key type (e.g., 'RSA', 'EC', 'OKP') + Kty string `json:"kty"` + + // N Modulus for RSA keys (base64url-encoded) + N *string `json:"n,omitempty"` + + // Use Intended use of the public key (e.g., 'sig' for signature) + Use *GetJSONWebKeySet200JSONResponseBodyKeysUse `json:"use,omitempty"` + + // X X coordinate for elliptic curve keys (base64url-encoded) + X *string `json:"x,omitempty"` + + // Y Y coordinate for elliptic curve keys (base64url-encoded) + Y *string `json:"y,omitempty"` + } `json:"keys"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseLinkSocialAccountResponse parses an HTTP response from a LinkSocialAccountWithResponse call func ParseLinkSocialAccountResponse(rsp *http.Response) (*LinkSocialAccountResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -44995,13 +46615,14 @@ func ParseListUserAccountsResponse(rsp *http.Response) (*ListUserAccountsRespons switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: var dest []struct { - AccountId string `json:"accountId"` - CreatedAt time.Time `json:"createdAt"` - Id string `json:"id"` - ProviderId string `json:"providerId"` - Scopes []string `json:"scopes"` - UpdatedAt time.Time `json:"updatedAt"` - UserId string `json:"userId"` + CreatedAt time.Time `json:"createdAt"` + Id string `json:"id"` + Issuer string `json:"issuer"` + ProviderAccountId string `json:"providerAccountId"` + ProviderId string `json:"providerId"` + Scopes []string `json:"scopes"` + UpdatedAt time.Time `json:"updatedAt"` + UserId string `json:"userId"` } if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err @@ -45222,32 +46843,25 @@ func ParseGetApiAuthOauth2AuthorizeResponse(rsp *http.Response) (*GetApiAuthOaut return response, nil } -// ParseGetApiAuthOauth2CallbackProviderIdResponse parses an HTTP response from a GetApiAuthOauth2CallbackProviderIdWithResponse call -func ParseGetApiAuthOauth2CallbackProviderIdResponse(rsp *http.Response) (*GetApiAuthOauth2CallbackProviderIdResponse, error) { +// ParsePostApiAuthOauth2AuthorizeResponse parses an HTTP response from a PostApiAuthOauth2AuthorizeWithResponse call +func ParsePostApiAuthOauth2AuthorizeResponse(rsp *http.Response) (*PostApiAuthOauth2AuthorizeResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetApiAuthOauth2CallbackProviderIdResponse{ + response := &PostApiAuthOauth2AuthorizeResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - Url *string `json:"url,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest struct { - Message string `json:"message"` + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + State *string `json:"state,omitempty"` } if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err @@ -45557,7 +47171,7 @@ func ParsePostApiAuthOauth2CreateClientResponse(rsp *http.Response) (*PostApiAut } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: var dest struct { // ClientId Unique identifier for the client ClientId string `json:"client_id"` @@ -45583,8 +47197,8 @@ func ParsePostApiAuthOauth2CreateClientResponse(rsp *http.Response) (*PostApiAut // Disabled Whether the client is disabled Disabled *bool `json:"disabled,omitempty"` - // GrantTypes Requested authentication method for the token endpoint - GrantTypes *[]PostApiAuthOauth2CreateClient200JSONResponseBodyGrantTypes `json:"grant_types,omitempty"` + // GrantTypes Grant types the client may use at the token endpoint + GrantTypes *[]string `json:"grant_types,omitempty"` // LogoUri Icon URI for the application LogoUri *string `json:"logo_uri,omitempty"` @@ -45601,8 +47215,8 @@ func ParsePostApiAuthOauth2CreateClientResponse(rsp *http.Response) (*PostApiAut // RedirectUris List of allowed redirect uris RedirectUris *[]string `json:"redirect_uris,omitempty"` - // ResponseTypes Requested authentication method for the token endpoint - ResponseTypes *[]PostApiAuthOauth2CreateClient200JSONResponseBodyResponseTypes `json:"response_types,omitempty"` + // ResponseTypes Response types the client may use at the authorization endpoint + ResponseTypes *[]PostApiAuthOauth2CreateClient201JSONResponseBodyResponseTypes `json:"response_types,omitempty"` // Scope Space-separated scopes allowed by the client Scope *string `json:"scope,omitempty"` @@ -45616,14 +47230,14 @@ func ParsePostApiAuthOauth2CreateClientResponse(rsp *http.Response) (*PostApiAut // SoftwareVersion Version identifier for the software_id SoftwareVersion *string `json:"software_version,omitempty"` - // TokenEndpointAuthMethod Response types the client may use - TokenEndpointAuthMethod *PostApiAuthOauth2CreateClient200JSONResponseBodyTokenEndpointAuthMethod `json:"token_endpoint_auth_method,omitempty"` + // TokenEndpointAuthMethod Requested authentication method for the token endpoint + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` // TosUri Client's terms of service uri TosUri *string `json:"tos_uri,omitempty"` // Type Type of the client - Type *PostApiAuthOauth2CreateClient200JSONResponseBodyType `json:"type,omitempty"` + Type *PostApiAuthOauth2CreateClient201JSONResponseBodyType `json:"type,omitempty"` // UserId ID of the user who registered the client, null if registered anonymously UserId *string `json:"user_id,omitempty"` @@ -45631,7 +47245,7 @@ func ParsePostApiAuthOauth2CreateClientResponse(rsp *http.Response) (*PostApiAut if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest struct { @@ -46334,92 +47948,6 @@ func ParsePostApiAuthOauth2IntrospectResponse(rsp *http.Response) (*PostApiAuthO return response, nil } -// ParsePostApiAuthOauth2LinkResponse parses an HTTP response from a PostApiAuthOauth2LinkWithResponse call -func ParsePostApiAuthOauth2LinkResponse(rsp *http.Response) (*PostApiAuthOauth2LinkResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostApiAuthOauth2LinkResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - // Redirect Indicates that the client should redirect to the provided URL - Redirect PostApiAuthOauth2Link200JSONResponseBodyRedirect `json:"redirect"` - - // Url The authorization URL to redirect the user to for linking the OAuth2 account - Url string `json:"url"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest struct { - Message string `json:"message"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest struct { - Message string `json:"message"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON429 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - // ParseGetApiAuthOauth2PublicClientResponse parses an HTTP response from a GetApiAuthOauth2PublicClientWithResponse call func ParseGetApiAuthOauth2PublicClientResponse(rsp *http.Response) (*GetApiAuthOauth2PublicClientResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -46580,8 +48108,14 @@ func ParsePostApiAuthOauth2RegisterResponse(rsp *http.Response) (*PostApiAuthOau } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: var dest struct { + // BackchannelLogoutSessionRequired Whether the RP requires a `sid` claim in every Logout Token + BackchannelLogoutSessionRequired *bool `json:"backchannel_logout_session_required,omitempty"` + + // BackchannelLogoutUri RP URL to receive signed Logout Tokens when the end-user's OP session terminates + BackchannelLogoutUri *string `json:"backchannel_logout_uri,omitempty"` + // ClientId Unique identifier for the client ClientId string `json:"client_id"` @@ -46606,8 +48140,8 @@ func ParsePostApiAuthOauth2RegisterResponse(rsp *http.Response) (*PostApiAuthOau // Disabled Whether the client is disabled Disabled *bool `json:"disabled,omitempty"` - // GrantTypes Requested authentication method for the token endpoint - GrantTypes *[]PostApiAuthOauth2Register200JSONResponseBodyGrantTypes `json:"grant_types,omitempty"` + // GrantTypes Grant types the client may use at the token endpoint + GrantTypes *[]string `json:"grant_types,omitempty"` // LogoUri Icon URL for the application LogoUri *string `json:"logo_uri,omitempty"` @@ -46624,8 +48158,8 @@ func ParsePostApiAuthOauth2RegisterResponse(rsp *http.Response) (*PostApiAuthOau // RedirectUris List of allowed redirect uris RedirectUris *[]string `json:"redirect_uris,omitempty"` - // ResponseTypes Requested authentication method for the token endpoint - ResponseTypes *[]PostApiAuthOauth2Register200JSONResponseBodyResponseTypes `json:"response_types,omitempty"` + // ResponseTypes Response types the client may use at the authorization endpoint + ResponseTypes *[]PostApiAuthOauth2Register201JSONResponseBodyResponseTypes `json:"response_types,omitempty"` // Scope Space-separated scopes allowed by the client Scope *string `json:"scope,omitempty"` @@ -46640,13 +48174,13 @@ func ParsePostApiAuthOauth2RegisterResponse(rsp *http.Response) (*PostApiAuthOau SoftwareVersion *string `json:"software_version,omitempty"` // TokenEndpointAuthMethod Requested authentication method for the token endpoint - TokenEndpointAuthMethod *PostApiAuthOauth2Register200JSONResponseBodyTokenEndpointAuthMethod `json:"token_endpoint_auth_method,omitempty"` + TokenEndpointAuthMethod *string `json:"token_endpoint_auth_method,omitempty"` // TosUri Client's terms of service uri TosUri *string `json:"tos_uri,omitempty"` // Type Type of the client - Type *PostApiAuthOauth2Register200JSONResponseBodyType `json:"type,omitempty"` + Type *PostApiAuthOauth2Register201JSONResponseBodyType `json:"type,omitempty"` // UserId ID of the user who registered the client, null if registered anonymously UserId *string `json:"user_id,omitempty"` @@ -46654,7 +48188,7 @@ func ParsePostApiAuthOauth2RegisterResponse(rsp *http.Response) (*PostApiAuthOau if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON200 = &dest + response.JSON201 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest struct { @@ -47146,6 +48680,109 @@ func ParseGetApiAuthOauth2UserinfoResponse(rsp *http.Response) (*GetApiAuthOauth return response, nil } +// ParsePostApiAuthOauth2UserinfoResponse parses an HTTP response from a PostApiAuthOauth2UserinfoWithResponse call +func ParsePostApiAuthOauth2UserinfoResponse(rsp *http.Response) (*PostApiAuthOauth2UserinfoResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PostApiAuthOauth2UserinfoResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + // Email User's email address, included if 'email' scope is granted + Email *openapi_types.Email `json:"email,omitempty"` + + // EmailVerified Whether the email is verified, included if 'email' scope is granted + EmailVerified *bool `json:"email_verified,omitempty"` + + // FamilyName User's family name, included if 'profile' scope is granted + FamilyName *string `json:"family_name,omitempty"` + + // GivenName User's given name, included if 'profile' scope is granted + GivenName *string `json:"given_name,omitempty"` + + // Name User's full name, included if 'profile' scope is granted + Name *string `json:"name,omitempty"` + + // Picture User's profile picture URL, included if 'profile' scope is granted + Picture *string `json:"picture,omitempty"` + + // Sub Subject identifier (user ID) + Sub string `json:"sub"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest struct { + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest struct { + Error string `json:"error"` + ErrorDescription *string `json:"error_description,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseGetApiAuthOkResponse parses an HTTP response from a GetApiAuthOkWithResponse call func ParseGetApiAuthOkResponse(rsp *http.Response) (*GetApiAuthOkResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -47776,15 +49413,15 @@ func ParseGetApiAuthOrganizationGetActiveMemberRoleResponse(rsp *http.Response) return response, nil } -// ParseGetOrganizationResponse parses an HTTP response from a GetOrganizationWithResponse call -func ParseGetOrganizationResponse(rsp *http.Response) (*GetOrganizationResponse, error) { +// ParseGetFullOrganizationResponse parses an HTTP response from a GetFullOrganizationWithResponse call +func ParseGetFullOrganizationResponse(rsp *http.Response) (*GetFullOrganizationResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &GetOrganizationResponse{ + response := &GetFullOrganizationResponse{ Body: bodyBytes, HTTPResponse: rsp, } @@ -47947,6 +49584,86 @@ func ParseGetApiAuthOrganizationGetInvitationResponse(rsp *http.Response) (*GetA return response, nil } +// ParseGetOrganizationResponse parses an HTTP response from a GetOrganizationWithResponse call +func ParseGetOrganizationResponse(rsp *http.Response) (*GetOrganizationResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetOrganizationResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Organization + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParsePostApiAuthOrganizationHasPermissionResponse parses an HTTP response from a PostApiAuthOrganizationHasPermissionWithResponse call func ParsePostApiAuthOrganizationHasPermissionResponse(rsp *http.Response) (*PostApiAuthOrganizationHasPermissionResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -49675,89 +51392,6 @@ func ParseSignInEmailResponse(rsp *http.Response) (*SignInEmailResponse, error) return response, nil } -// ParsePostApiAuthSignInOauth2Response parses an HTTP response from a PostApiAuthSignInOauth2WithResponse call -func ParsePostApiAuthSignInOauth2Response(rsp *http.Response) (*PostApiAuthSignInOauth2Response, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &PostApiAuthSignInOauth2Response{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - Redirect *bool `json:"redirect,omitempty"` - Url *string `json:"url,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest struct { - Message string `json:"message"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: - var dest struct { - Message string `json:"message"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON401 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON429 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: - var dest struct { - Message *string `json:"message,omitempty"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON500 = &dest - - } - - return response, nil -} - // ParseSocialSignInResponse parses an HTTP response from a SocialSignInWithResponse call func ParseSocialSignInResponse(rsp *http.Response) (*SocialSignInResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -50138,6 +51772,88 @@ func ParseSignUpWithEmailAndPasswordResponse(rsp *http.Response) (*SignUpWithEma return response, nil } +// ParseGetJSONWebTokenResponse parses an HTTP response from a GetJSONWebTokenWithResponse call +func ParseGetJSONWebTokenResponse(rsp *http.Response) (*GetJSONWebTokenResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetJSONWebTokenResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Token *string `json:"token,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest struct { + Message string `json:"message"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest struct { + Message *string `json:"message,omitempty"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParsePostApiAuthUnlinkAccountResponse parses an HTTP response from a PostApiAuthUnlinkAccountWithResponse call func ParsePostApiAuthUnlinkAccountResponse(rsp *http.Response) (*PostApiAuthUnlinkAccountResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -51945,6 +53661,37 @@ func ParseMarkNotificationReadResponse(rsp *http.Response) (*MarkNotificationRea return response, nil } +// ParseListOAuthResourceScopesResponse parses an HTTP response from a ListOAuthResourceScopesWithResponse call +func ParseListOAuthResourceScopesResponse(rsp *http.Response) (*ListOAuthResourceScopesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListOAuthResourceScopesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Scopes []struct { + Description string `json:"description"` + Value string `json:"value"` + } `json:"scopes"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseListObjectsResponse parses an HTTP response from a ListObjectsWithResponse call func ParseListObjectsResponse(rsp *http.Response) (*ListObjectsResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -52023,6 +53770,8 @@ func ParseCreateObjectResponse(rsp *http.Response) (*CreateObjectResponse, error Parts []struct { ExpiresAt string `json:"expiresAt"` Headers map[string]string `json:"headers"` + Length int `json:"length"` + Offset int `json:"offset"` PartNumber int `json:"partNumber"` Url string `json:"url"` } `json:"parts"` @@ -52031,6 +53780,34 @@ func ParseCreateObjectResponse(rsp *http.Response) (*CreateObjectResponse, error SessionId string `json:"sessionId"` UploadId *string `json:"uploadId"` Urls []string `json:"urls"` + Workflow struct { + Abort struct { + Method CreateObject201JSONResponseBodyUploadWorkflowAbortMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowAbortOperationId `json:"operationId"` + Path string `json:"path"` + } `json:"abort"` + Complete struct { + Method CreateObject201JSONResponseBodyUploadWorkflowCompleteMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowCompleteOperationId `json:"operationId"` + PartsBodyField CreateObject201JSONResponseBodyUploadWorkflowCompletePartsBodyField `json:"partsBodyField"` + Path string `json:"path"` + } `json:"complete"` + RePresign struct { + Method CreateObject201JSONResponseBodyUploadWorkflowRePresignMethod `json:"method"` + OperationId CreateObject201JSONResponseBodyUploadWorkflowRePresignOperationId `json:"operationId"` + PartNumbersBodyField CreateObject201JSONResponseBodyUploadWorkflowRePresignPartNumbersBodyField `json:"partNumbersBodyField"` + Path string `json:"path"` + } `json:"rePresign"` + Upload struct { + ContentLengthField CreateObject201JSONResponseBodyUploadWorkflowUploadContentLengthField `json:"contentLengthField"` + EtagHeader CreateObject201JSONResponseBodyUploadWorkflowUploadEtagHeader `json:"etagHeader"` + FileOffsetField CreateObject201JSONResponseBodyUploadWorkflowUploadFileOffsetField `json:"fileOffsetField"` + HeadersField CreateObject201JSONResponseBodyUploadWorkflowUploadHeadersField `json:"headersField"` + Method CreateObject201JSONResponseBodyUploadWorkflowUploadMethod `json:"method"` + UrlField CreateObject201JSONResponseBodyUploadWorkflowUploadUrlField `json:"urlField"` + } `json:"upload"` + Version CreateObject201JSONResponseBodyUploadWorkflowVersion `json:"version"` + } `json:"workflow"` } `json:"upload,omitempty"` } if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -52438,6 +54215,8 @@ func ParsePresignObjectUploadPartsResponse(rsp *http.Response) (*PresignObjectUp Parts []struct { ExpiresAt string `json:"expiresAt"` Headers map[string]string `json:"headers"` + Length int `json:"length"` + Offset int `json:"offset"` PartNumber int `json:"partNumber"` Url string `json:"url"` } `json:"parts"` @@ -56215,204 +57994,3 @@ func ParseGetUserProfileResponse(rsp *http.Response) (*GetUserProfileResponse, e return response, nil } - -// ParseListWorkspaceAgentApiKeysResponse parses an HTTP response from a ListWorkspaceAgentApiKeysWithResponse call -func ParseListWorkspaceAgentApiKeysResponse(rsp *http.Response) (*ListWorkspaceAgentApiKeysResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &ListWorkspaceAgentApiKeysResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest struct { - Items []struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []ListWorkspaceAgentApiKeys200JSONResponseBodyItemsScopes `json:"scopes"` - Status ListWorkspaceAgentApiKeys200JSONResponseBodyItemsStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"items"` - Page int `json:"page"` - PageSize int `json:"pageSize"` - Total int `json:"total"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON200 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - } - - return response, nil -} - -// ParseCreateWorkspaceAgentApiKeyResponse parses an HTTP response from a CreateWorkspaceAgentApiKeyWithResponse call -func ParseCreateWorkspaceAgentApiKeyResponse(rsp *http.Response) (*CreateWorkspaceAgentApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &CreateWorkspaceAgentApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest struct { - Item struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []CreateWorkspaceAgentApiKey201JSONResponseBodyItemScopes `json:"scopes"` - Status CreateWorkspaceAgentApiKey201JSONResponseBodyItemStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"item"` - Key string `json:"key"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - } - - return response, nil -} - -// ParseRevokeWorkspaceAgentApiKeyResponse parses an HTTP response from a RevokeWorkspaceAgentApiKeyWithResponse call -func ParseRevokeWorkspaceAgentApiKeyResponse(rsp *http.Response) (*RevokeWorkspaceAgentApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RevokeWorkspaceAgentApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - } - - return response, nil -} - -// ParseRotateWorkspaceAgentApiKeyResponse parses an HTTP response from a RotateWorkspaceAgentApiKeyWithResponse call -func ParseRotateWorkspaceAgentApiKeyResponse(rsp *http.Response) (*RotateWorkspaceAgentApiKeyResponse, error) { - bodyBytes, err := io.ReadAll(rsp.Body) - defer func() { _ = rsp.Body.Close() }() - if err != nil { - return nil, err - } - - response := &RotateWorkspaceAgentApiKeyResponse{ - Body: bodyBytes, - HTTPResponse: rsp, - } - - switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest struct { - Item struct { - CreatedAt string `json:"createdAt"` - ExpiresAt string `json:"expiresAt"` - Id string `json:"id"` - LastUsedAt *string `json:"lastUsedAt"` - Name string `json:"name"` - OrgId string `json:"orgId"` - Scopes []RotateWorkspaceAgentApiKey201JSONResponseBodyItemScopes `json:"scopes"` - Status RotateWorkspaceAgentApiKey201JSONResponseBodyItemStatus `json:"status"` - WorkspaceName *string `json:"workspaceName"` - } `json:"item"` - Key string `json:"key"` - } - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON400 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON403 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON404 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: - var dest Error - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON409 = &dest - - } - - return response, nil -} diff --git a/cmd/internal/restishzpan/checkpoint.go b/cmd/internal/restishzpan/checkpoint.go deleted file mode 100644 index 6a9641bb..00000000 --- a/cmd/internal/restishzpan/checkpoint.go +++ /dev/null @@ -1,179 +0,0 @@ -package restishzpan - -import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "os" - "path/filepath" - "runtime" - "sort" - "time" -) - -const checkpointVersion = 1 - -type checkpoint struct { - Version int `json:"version"` - API string `json:"api"` - Profile string `json:"profile,omitempty"` - SourcePath string `json:"sourcePath"` - FileSize int64 `json:"fileSize"` - ModTimeUnixNS int64 `json:"modTimeUnixNs"` - ObjectID string `json:"objectId"` - SessionID string `json:"sessionId"` - UploadID *string `json:"uploadId"` - Mode string `json:"mode"` - PartSize int64 `json:"partSize"` - PartCount int `json:"partCount"` - Parent string `json:"parent"` - Name string `json:"name"` - Conflict string `json:"conflict"` - Parts map[int]string `json:"parts"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` -} - -func newCheckpoint(opts uploadOptions, src fileIdentity, matter matterResult, upload uploadInstructions) checkpoint { - return checkpoint{ - Version: checkpointVersion, - API: opts.API, - Profile: opts.Profile, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: matter.ID, - SessionID: upload.SessionID, - UploadID: upload.UploadID, - Mode: upload.Mode, - PartSize: upload.PartSize, - PartCount: upload.PartCount, - Parent: opts.Parent, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{}, - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), - } -} - -func (c checkpoint) completedParts() []completedPart { - parts := make([]completedPart, 0, len(c.Parts)) - for partNumber, etag := range c.Parts { - parts = append(parts, completedPart{PartNumber: partNumber, ETag: etag}) - } - sort.Slice(parts, func(i, j int) bool { return parts[i].PartNumber < parts[j].PartNumber }) - return parts -} - -type checkpointStore struct { - dir string -} - -func newCheckpointStore(dir string) (checkpointStore, error) { - if dir == "" { - cacheDir, err := os.UserCacheDir() - if err != nil { - return checkpointStore{}, err - } - dir = filepath.Join(cacheDir, "restish-zpan", "checkpoints") - } - if err := os.MkdirAll(dir, 0o700); err != nil { - return checkpointStore{}, err - } - return checkpointStore{dir: dir}, nil -} - -func (s checkpointStore) path(opts uploadOptions, src fileIdentity) string { - key := opts.API + "\x00" + opts.Profile + "\x00" + src.Path + "\x00" + opts.Parent + "\x00" + opts.Name - sum := sha256.Sum256([]byte(key)) - return filepath.Join(s.dir, hex.EncodeToString(sum[:])+".json") -} - -func (s checkpointStore) load(path string) (checkpoint, error) { - data, err := os.ReadFile(path) - if err != nil { - return checkpoint{}, err - } - var cp checkpoint - if err := json.Unmarshal(data, &cp); err != nil { - return checkpoint{}, err - } - if cp.Version != checkpointVersion { - return checkpoint{}, fmt.Errorf("unsupported checkpoint version %d", cp.Version) - } - if cp.Parts == nil { - cp.Parts = map[int]string{} - } - return cp, nil -} - -func (s checkpointStore) save(path string, cp checkpoint) error { - cp.UpdatedAt = time.Now().UTC() - data, err := json.MarshalIndent(cp, "", " ") - if err != nil { - return err - } - tmp, err := os.CreateTemp(s.dir, ".checkpoint-*") - if err != nil { - return err - } - tmpName := tmp.Name() - defer func() { _ = os.Remove(tmpName) }() - if runtime.GOOS != "windows" { - if err := tmp.Chmod(0o600); err != nil { - _ = tmp.Close() - return err - } - } - if _, err := tmp.Write(data); err != nil { - _ = tmp.Close() - return err - } - if err := tmp.Close(); err != nil { - return err - } - if runtime.GOOS != "windows" { - if err := os.Chmod(tmpName, 0o600); err != nil { - return err - } - } - return os.Rename(tmpName, path) -} - -func (s checkpointStore) remove(path string) error { - if err := os.Remove(path); err != nil && !os.IsNotExist(err) { - return err - } - return nil -} - -func validateCheckpoint(cp checkpoint, opts uploadOptions, src fileIdentity) error { - if cp.API != opts.API || cp.Profile != opts.Profile { - return fmt.Errorf("checkpoint belongs to api/profile %s/%s", cp.API, cp.Profile) - } - if cp.SourcePath != src.Path { - return fmt.Errorf("checkpoint source changed") - } - if cp.FileSize != src.Size || cp.ModTimeUnixNS != src.ModTime.UnixNano() { - return fmt.Errorf("source file changed since checkpoint was created") - } - if cp.Parent != opts.Parent || cp.Name != opts.Name || cp.Conflict != opts.Conflict { - return fmt.Errorf("checkpoint destination or conflict policy differs from this command") - } - return nil -} - -func validateAbortCheckpoint(cp checkpoint, opts uploadOptions, src fileIdentity) error { - if cp.API != opts.API || cp.Profile != opts.Profile { - return fmt.Errorf("checkpoint belongs to api/profile %s/%s", cp.API, cp.Profile) - } - if cp.SourcePath != src.Path { - return fmt.Errorf("checkpoint source changed") - } - if cp.Parent != opts.Parent || cp.Name != opts.Name { - return fmt.Errorf("checkpoint destination differs from this command") - } - return nil -} diff --git a/cmd/internal/restishzpan/checkpoint_test.go b/cmd/internal/restishzpan/checkpoint_test.go deleted file mode 100644 index 1739644c..00000000 --- a/cmd/internal/restishzpan/checkpoint_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package restishzpan - -import ( - "os" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestCheckpointStoreRoundTripAndDefaults(t *testing.T) { - t.Setenv("XDG_CACHE_HOME", t.TempDir()) - store, err := newCheckpointStore("") - if err != nil { - t.Fatal(err) - } - if !strings.Contains(store.dir, "restish-zpan") { - t.Fatalf("unexpected default dir: %s", store.dir) - } - - path := filepath.Join(store.dir, "cp.json") - if err := store.save(path, checkpoint{Version: checkpointVersion}); err != nil { - t.Fatal(err) - } - cp, err := store.load(path) - if err != nil { - t.Fatal(err) - } - if cp.Parts == nil || len(cp.Parts) != 0 { - t.Fatalf("expected empty parts map, got %#v", cp.Parts) - } - if cp.UpdatedAt.IsZero() { - t.Fatal("expected UpdatedAt to be set") - } -} - -func TestCheckpointStoreLoadAndRemoveErrors(t *testing.T) { - store, err := newCheckpointStore(t.TempDir()) - if err != nil { - t.Fatal(err) - } - - invalidVersionPath := filepath.Join(store.dir, "invalid-version.json") - if err := os.WriteFile(invalidVersionPath, []byte(`{"version":2}`), 0o600); err != nil { - t.Fatal(err) - } - if _, err := store.load(invalidVersionPath); err == nil || !strings.Contains(err.Error(), "unsupported checkpoint version") { - t.Fatalf("expected version error, got %v", err) - } - - invalidJSONPath := filepath.Join(store.dir, "invalid-json.json") - if err := os.WriteFile(invalidJSONPath, []byte(`{`), 0o600); err != nil { - t.Fatal(err) - } - if _, err := store.load(invalidJSONPath); err == nil { - t.Fatal("expected invalid json to fail") - } - - if err := store.remove(filepath.Join(store.dir, "missing.json")); err != nil { - t.Fatalf("remove missing file: %v", err) - } -} - -func TestCheckpointCompletedPartsAreSorted(t *testing.T) { - parts := checkpoint{ - Parts: map[int]string{2: "etag-2", 1: "etag-1"}, - }.completedParts() - if len(parts) != 2 || parts[0].PartNumber != 1 || parts[1].PartNumber != 2 { - t.Fatalf("unexpected parts: %#v", parts) - } -} - -func TestValidateAbortCheckpointIdentity(t *testing.T) { - src := fileIdentity{Path: "/tmp/source.bin"} - opts := uploadOptions{API: "zpan", Profile: "ci", Parent: "folder", Name: "source.bin", Conflict: "fail"} - cp := checkpoint{API: "zpan", Profile: "ci", SourcePath: src.Path, Parent: "folder", Name: "source.bin", Conflict: "rename"} - - if err := validateAbortCheckpoint(cp, opts, src); err != nil { - t.Fatalf("expected abort validation to ignore conflict and file metadata, got %v", err) - } - if err := validateAbortCheckpoint(cp, uploadOptions{API: "other", Profile: "ci", Parent: "folder", Name: "source.bin"}, src); err == nil || !strings.Contains(err.Error(), "api/profile") { - t.Fatalf("expected api/profile error, got %v", err) - } - if err := validateAbortCheckpoint(cp, opts, fileIdentity{Path: "/tmp/other.bin"}); err == nil || !strings.Contains(err.Error(), "checkpoint source changed") { - t.Fatalf("expected source error, got %v", err) - } - if err := validateAbortCheckpoint(cp, uploadOptions{API: "zpan", Profile: "ci", Parent: "other", Name: "source.bin"}, src); err == nil || !strings.Contains(err.Error(), "destination differs") { - t.Fatalf("expected destination error, got %v", err) - } -} - -func TestNewCheckpointCopiesUploadMetadata(t *testing.T) { - uploadID := "upload-1" - cp := newCheckpoint( - uploadOptions{API: "zpan", Profile: "ci", Parent: "folder", Name: "file.txt", Conflict: "rename"}, - fileIdentity{Path: "/tmp/file.txt", Size: 5, ModTime: time.Unix(1, 0)}, - matterResult{ID: "obj"}, - uploadInstructions{SessionID: "sess", UploadID: &uploadID, Mode: "multipart", PartSize: 2, PartCount: 3}, - ) - if cp.API != "zpan" || cp.Profile != "ci" || cp.ObjectID != "obj" || cp.SessionID != "sess" || cp.Mode != "multipart" { - t.Fatalf("unexpected checkpoint: %#v", cp) - } - if cp.UploadID == nil || *cp.UploadID != uploadID { - t.Fatalf("unexpected upload id: %#v", cp.UploadID) - } -} diff --git a/cmd/internal/restishzpan/host.go b/cmd/internal/restishzpan/host.go deleted file mode 100644 index 892ec203..00000000 --- a/cmd/internal/restishzpan/host.go +++ /dev/null @@ -1,82 +0,0 @@ -package restishzpan - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/rest-sh/restish/v2/plugin" -) - -type host interface { - FetchAPISpecContext(ctx context.Context, api, profile string) (*plugin.APISpecResponseMsg, error) - Do(req *plugin.HTTPRequestMsg) (*plugin.HTTPResponseMsg, error) - Response(status int, headers map[string][]string, body any) error - Progress(text string) error - Warn(text string) error -} - -type PluginHost struct { - client *plugin.CommandClient -} - -func NewPluginHost(client *plugin.CommandClient) *PluginHost { - return &PluginHost{client: client} -} - -func (h *PluginHost) FetchAPISpecContext(ctx context.Context, api, profile string) (*plugin.APISpecResponseMsg, error) { - return h.client.FetchAPISpecContext(ctx, api, profile) -} - -func (h *PluginHost) Do(req *plugin.HTTPRequestMsg) (*plugin.HTTPResponseMsg, error) { - return h.client.Do(req) -} - -func (h *PluginHost) Response(status int, headers map[string][]string, body any) error { - return h.client.Response(status, headers, body) -} - -func (h *PluginHost) Progress(text string) error { - return h.client.Progress(text) -} - -func (h *PluginHost) Warn(text string) error { - return h.client.Warn(text) -} - -func decodeBody[T any](resp *plugin.HTTPResponseMsg) (T, error) { - var out T - if resp == nil { - return out, fmt.Errorf("missing HTTP response") - } - if resp.Error != "" { - return out, fmt.Errorf("%s", resp.Error) - } - if resp.Status < 200 || resp.Status >= 300 { - return out, httpStatusError{status: resp.Status, body: resp.Body} - } - data, err := json.Marshal(resp.Body) - if err != nil { - return out, fmt.Errorf("encode delegated response body: %w", err) - } - if err := json.Unmarshal(data, &out); err != nil { - return out, fmt.Errorf("decode delegated response body: %w", err) - } - return out, nil -} - -type httpStatusError struct { - status int - body any -} - -func (e httpStatusError) Error() string { - if e.body == nil { - return fmt.Sprintf("delegated request failed with HTTP %d", e.status) - } - data, err := json.Marshal(e.body) - if err != nil { - return fmt.Sprintf("delegated request failed with HTTP %d", e.status) - } - return fmt.Sprintf("delegated request failed with HTTP %d: %s", e.status, string(data)) -} diff --git a/cmd/internal/restishzpan/host_test.go b/cmd/internal/restishzpan/host_test.go deleted file mode 100644 index 0b3d0528..00000000 --- a/cmd/internal/restishzpan/host_test.go +++ /dev/null @@ -1,188 +0,0 @@ -package restishzpan - -import ( - "bytes" - "context" - "io" - "strings" - "testing" - - "github.com/rest-sh/restish/v2/plugin" -) - -func TestPluginHostFetchAPISpecContextDelegates(t *testing.T) { - hostToPluginR, hostToPluginW := newPipePair(t) - pluginToHostR, pluginToHostW := newPipePair(t) - client := plugin.NewCommandClient(hostToPluginR, pluginToHostW) - h := NewPluginHost(client) - - requests := make(chan plugin.APISpecMsg, 1) - go func() { - defer close(requests) - var req plugin.APISpecMsg - if err := plugin.NewDecoder(pluginToHostR).ReadMessage(&req); err != nil { - t.Errorf("read request: %v", err) - return - } - requests <- req - if err := plugin.WriteMessage(hostToPluginW, plugin.APISpecResponseMsg{ - Type: plugin.MsgTypeAPISpecResponse, - RequestID: req.RequestID, - Name: req.Name, - Profile: req.Profile, - Operations: []plugin.APIOperation{ - {ID: opCreate, Method: "POST"}, - }, - }); err != nil { - t.Errorf("write response: %v", err) - } - }() - - resp, err := h.FetchAPISpecContext(context.Background(), "zpan", "ci") - if err != nil { - t.Fatal(err) - } - req := <-requests - if req.Name != "zpan" || req.Profile != "ci" { - t.Fatalf("unexpected request: %#v", req) - } - if resp.Name != "zpan" || resp.Profile != "ci" || len(resp.Operations) != 1 || resp.Operations[0].ID != opCreate { - t.Fatalf("unexpected response: %#v", resp) - } -} - -func TestPluginHostDoDelegates(t *testing.T) { - hostToPluginR, hostToPluginW := newPipePair(t) - pluginToHostR, pluginToHostW := newPipePair(t) - client := plugin.NewCommandClient(hostToPluginR, pluginToHostW) - h := NewPluginHost(client) - - requests := make(chan plugin.HTTPRequestMsg, 1) - go func() { - defer close(requests) - var req plugin.HTTPRequestMsg - if err := plugin.NewDecoder(pluginToHostR).ReadMessage(&req); err != nil { - t.Errorf("read request: %v", err) - return - } - requests <- req - if err := plugin.WriteMessage(hostToPluginW, plugin.HTTPResponseMsg{ - Type: plugin.MsgTypeHTTPResponse, - RequestID: req.RequestID, - Status: 200, - Body: map[string]any{"ok": true}, - }); err != nil { - t.Errorf("write response: %v", err) - } - }() - - resp, err := h.Do(&plugin.HTTPRequestMsg{Method: "POST", URI: "zpan/api/objects", Timeout: 1}) - if err != nil { - t.Fatal(err) - } - req := <-requests - if req.Method != "POST" || req.URI != "zpan/api/objects" { - t.Fatalf("unexpected request: %#v", req) - } - if resp.Status != 200 { - t.Fatalf("status = %d, want 200", resp.Status) - } -} - -func TestPluginHostWritesMessages(t *testing.T) { - var out bytes.Buffer - h := NewPluginHost(plugin.NewCommandClient(bytes.NewReader(nil), &out)) - - if err := h.Response(201, map[string][]string{"X-Test": {"1"}}, map[string]any{"id": "obj"}); err != nil { - t.Fatal(err) - } - if err := h.Progress("working"); err != nil { - t.Fatal(err) - } - if err := h.Warn("careful"); err != nil { - t.Fatal(err) - } - - dec := plugin.NewDecoder(&out) - var resp plugin.ResponseMsg - if err := dec.ReadMessage(&resp); err != nil { - t.Fatal(err) - } - if resp.Type != plugin.MsgTypeResponse || resp.Status != 201 { - t.Fatalf("unexpected response message: %#v", resp) - } - var progress plugin.ProgressMsg - if err := dec.ReadMessage(&progress); err != nil { - t.Fatal(err) - } - if progress.Text != "working" { - t.Fatalf("unexpected progress: %#v", progress) - } - var warn plugin.WarnMsg - if err := dec.ReadMessage(&warn); err != nil { - t.Fatal(err) - } - if warn.Text != "careful" { - t.Fatalf("unexpected warn: %#v", warn) - } -} - -func TestDecodeBody(t *testing.T) { - t.Run("success", func(t *testing.T) { - resp, err := decodeBody[map[string]string](&plugin.HTTPResponseMsg{Status: 200, Body: map[string]any{"id": "obj"}}) - if err != nil { - t.Fatal(err) - } - if resp["id"] != "obj" { - t.Fatalf("unexpected body: %#v", resp) - } - }) - - t.Run("nil response", func(t *testing.T) { - _, err := decodeBody[map[string]any](nil) - if err == nil || !strings.Contains(err.Error(), "missing HTTP response") { - t.Fatalf("unexpected error: %v", err) - } - }) - - t.Run("delegated error", func(t *testing.T) { - _, err := decodeBody[map[string]any](&plugin.HTTPResponseMsg{Status: 200, Error: "boom"}) - if err == nil || !strings.Contains(err.Error(), "boom") { - t.Fatalf("unexpected error: %v", err) - } - }) - - t.Run("status error", func(t *testing.T) { - _, err := decodeBody[map[string]any](&plugin.HTTPResponseMsg{Status: 400, Body: map[string]any{"error": "bad"}}) - if err == nil || !strings.Contains(err.Error(), "HTTP 400") { - t.Fatalf("unexpected error: %v", err) - } - }) - - t.Run("decode error", func(t *testing.T) { - _, err := decodeBody[struct { - ID string `json:"id"` - }](&plugin.HTTPResponseMsg{Status: 200, Body: map[string]any{"id": []string{"bad"}}}) - if err == nil || !strings.Contains(err.Error(), "decode delegated response body") { - t.Fatalf("unexpected error: %v", err) - } - }) -} - -func TestHTTPStatusErrorFormatting(t *testing.T) { - if msg := (httpStatusError{status: 500}).Error(); !strings.Contains(msg, "HTTP 500") { - t.Fatalf("unexpected error: %s", msg) - } - if msg := (httpStatusError{status: 400, body: map[string]any{"error": "bad"}}).Error(); !strings.Contains(msg, `"error":"bad"`) { - t.Fatalf("unexpected error: %s", msg) - } - errBody := map[string]any{"bad": func() {}} - if msg := (httpStatusError{status: 502, body: errBody}).Error(); !strings.Contains(msg, "HTTP 502") { - t.Fatalf("unexpected error: %s", msg) - } -} - -func newPipePair(t *testing.T) (*io.PipeReader, *io.PipeWriter) { - t.Helper() - return io.Pipe() -} diff --git a/cmd/internal/restishzpan/s3.go b/cmd/internal/restishzpan/s3.go deleted file mode 100644 index 1f5257f8..00000000 --- a/cmd/internal/restishzpan/s3.go +++ /dev/null @@ -1,79 +0,0 @@ -package restishzpan - -import ( - "context" - "errors" - "fmt" - "io" - "net/http" - "strings" - "time" -) - -type storageClient interface { - PutPart(ctx context.Context, part uploadPart, body io.Reader, size int64) (string, error) -} - -type httpStorageClient struct { - client *http.Client -} - -func newHTTPStorageClient() httpStorageClient { - return httpStorageClient{client: &http.Client{Timeout: 0}} -} - -func (c httpStorageClient) PutPart(ctx context.Context, part uploadPart, body io.Reader, size int64) (string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodPut, part.URL, body) - if err != nil { - return "", err - } - req.ContentLength = size - for name, value := range part.Headers { - req.Header.Set(name, value) - } - resp, err := c.client.Do(req) - if err != nil { - return "", err - } - defer resp.Body.Close() - _, _ = io.Copy(io.Discard, io.LimitReader(resp.Body, 1024)) - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return "", storageStatusError{status: resp.StatusCode} - } - etag := normalizeETag(resp.Header.Get("ETag")) - if etag == "" { - return "", fmt.Errorf("storage PUT for part %d did not return an ETag", part.PartNumber) - } - return etag, nil -} - -type storageStatusError struct { - status int -} - -func (e storageStatusError) Error() string { - return fmt.Sprintf("storage PUT failed with HTTP %d", e.status) -} - -func normalizeETag(value string) string { - return strings.Trim(strings.TrimSpace(value), `"`) -} - -func shouldResignAfterStorageError(err error) bool { - var statusErr storageStatusError - if !errors.As(err, &statusErr) { - return false - } - return statusErr.status == http.StatusForbidden || statusErr.status == http.StatusUnauthorized || statusErr.status == http.StatusBadRequest -} - -func expiresSoon(raw string, now time.Time) bool { - if raw == "" { - return false - } - expires, err := time.Parse(time.RFC3339, raw) - if err != nil { - return false - } - return !expires.After(now.Add(30 * time.Second)) -} diff --git a/cmd/internal/restishzpan/s3_test.go b/cmd/internal/restishzpan/s3_test.go deleted file mode 100644 index cc891862..00000000 --- a/cmd/internal/restishzpan/s3_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package restishzpan - -import ( - "bytes" - "context" - "errors" - "io" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestHTTPStorageClientPutPart(t *testing.T) { - t.Run("success", func(t *testing.T) { - var gotType string - var gotBody []byte - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotType = r.Header.Get("Content-Type") - var err error - gotBody, err = ioReadAll(r.Body) - if err != nil { - t.Errorf("read body: %v", err) - } - w.Header().Set("ETag", ` "etag-1" `) - w.WriteHeader(http.StatusOK) - })) - defer srv.Close() - - client := newHTTPStorageClient() - etag, err := client.PutPart(context.Background(), uploadPart{ - PartNumber: 1, - URL: srv.URL, - Headers: map[string]string{"Content-Type": "text/plain"}, - }, bytes.NewReader([]byte("abc")), 3) - if err != nil { - t.Fatal(err) - } - if etag != "etag-1" { - t.Fatalf("etag = %q, want %q", etag, "etag-1") - } - if gotType != "text/plain" || string(gotBody) != "abc" { - t.Fatalf("unexpected request: content-type=%q body=%q", gotType, gotBody) - } - }) - - t.Run("status error", func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "denied", http.StatusForbidden) - })) - defer srv.Close() - - _, err := newHTTPStorageClient().PutPart(context.Background(), uploadPart{PartNumber: 2, URL: srv.URL}, bytes.NewReader([]byte("x")), 1) - var statusErr storageStatusError - if !errors.As(err, &statusErr) || statusErr.status != http.StatusForbidden { - t.Fatalf("unexpected error: %v", err) - } - }) - - t.Run("missing etag", func(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - })) - defer srv.Close() - - _, err := newHTTPStorageClient().PutPart(context.Background(), uploadPart{PartNumber: 3, URL: srv.URL}, bytes.NewReader([]byte("x")), 1) - if err == nil || !strings.Contains(err.Error(), "did not return an ETag") { - t.Fatalf("unexpected error: %v", err) - } - }) -} - -func TestStorageHelpers(t *testing.T) { - if got := normalizeETag(` "abc" `); got != "abc" { - t.Fatalf("normalizeETag = %q", got) - } - - if !shouldResignAfterStorageError(storageStatusError{status: http.StatusForbidden}) { - t.Fatal("expected forbidden to require re-sign") - } - if !shouldResignAfterStorageError(storageStatusError{status: http.StatusUnauthorized}) { - t.Fatal("expected unauthorized to require re-sign") - } - if !shouldResignAfterStorageError(storageStatusError{status: http.StatusBadRequest}) { - t.Fatal("expected bad request to require re-sign") - } - if shouldResignAfterStorageError(storageStatusError{status: http.StatusInternalServerError}) { - t.Fatal("expected internal server error not to require re-sign") - } - if shouldResignAfterStorageError(errors.New("plain")) { - t.Fatal("expected plain error not to require re-sign") - } - - now := time.Now() - if expiresSoon("", now) { - t.Fatal("empty expiry should not expire soon") - } - if expiresSoon("not-a-time", now) { - t.Fatal("invalid expiry should not expire soon") - } - if !expiresSoon(now.Add(20*time.Second).Format(time.RFC3339), now) { - t.Fatal("expected near expiry to be true") - } - if expiresSoon(now.Add(2*time.Minute).Format(time.RFC3339), now) { - t.Fatal("expected distant expiry to be false") - } -} - -func ioReadAll(body io.ReadCloser) ([]byte, error) { - defer body.Close() - return io.ReadAll(body) -} diff --git a/cmd/internal/restishzpan/spec.go b/cmd/internal/restishzpan/spec.go deleted file mode 100644 index b0e3ae81..00000000 --- a/cmd/internal/restishzpan/spec.go +++ /dev/null @@ -1,153 +0,0 @@ -package restishzpan - -import ( - "context" - "fmt" - "strings" - - "github.com/rest-sh/restish/v2/plugin" -) - -const ( - opCreate = "createObject" - opPresign = "presignObjectUploadParts" - opComplete = "completeObjectUpload" - opAbort = "abortObjectUpload" -) - -var restishOperationAliases = map[string][]string{ - opCreate: {"create-object"}, - opPresign: {"presign-object-upload-parts"}, - opComplete: {"complete-object-upload"}, - opAbort: {"abort-object-upload"}, -} - -type operationSet struct { - Create plugin.APIOperation - Presign plugin.APIOperation - Complete plugin.APIOperation - Abort plugin.APIOperation -} - -func fetchOperations(ctx context.Context, h host, api, profile string) (operationSet, error) { - spec, err := h.FetchAPISpecContext(ctx, api, profile) - if err != nil { - return operationSet{}, err - } - if spec.Error != "" { - return operationSet{}, fmt.Errorf("%s", spec.Error) - } - ops := map[string]plugin.APIOperation{} - for _, op := range spec.Operations { - ops[op.ID] = op - } - required := map[string]string{ - opCreate: "POST", - opPresign: "POST", - opComplete: "POST", - opAbort: "DELETE", - } - matched := map[string]plugin.APIOperation{} - for id, method := range required { - op, ok := findOperation(ops, id) - if !ok { - return operationSet{}, fmt.Errorf("API %q is missing required operation %q", api, id) - } - if !strings.EqualFold(op.Method, method) { - return operationSet{}, fmt.Errorf("operation %q uses %s, want %s", id, op.Method, method) - } - matched[id] = op - } - if err := validateCreateOperation(matched[opCreate]); err != nil { - return operationSet{}, err - } - if err := validatePartsOperation(matched[opPresign], "partNumbers"); err != nil { - return operationSet{}, err - } - if err := validatePartsOperation(matched[opComplete], "parts"); err != nil { - return operationSet{}, err - } - for _, id := range []string{opPresign, opComplete, opAbort} { - if err := requirePathParams(matched[id], "id", "uploadSessionId"); err != nil { - return operationSet{}, fmt.Errorf("operation %q: %w", id, err) - } - } - return operationSet{ - Create: matched[opCreate], - Presign: matched[opPresign], - Complete: matched[opComplete], - Abort: matched[opAbort], - }, nil -} - -func findOperation(ops map[string]plugin.APIOperation, id string) (plugin.APIOperation, bool) { - if op, ok := ops[id]; ok { - return op, true - } - for _, alias := range restishOperationAliases[id] { - if op, ok := ops[alias]; ok { - return op, true - } - } - return plugin.APIOperation{}, false -} - -func validateCreateOperation(op plugin.APIOperation) error { - if !op.HasBody { - return fmt.Errorf("operation %q must accept a JSON body", opCreate) - } - for _, name := range []string{"name", "type", "size", "parent", "onConflict"} { - if !schemaHasProperty(op.RequestSchema, name) { - return fmt.Errorf("operation %q request schema missing %q", opCreate, name) - } - } - return nil -} - -func validatePartsOperation(op plugin.APIOperation, property string) error { - if !op.HasBody { - return fmt.Errorf("operation %q must accept a JSON body", op.ID) - } - if !schemaHasProperty(op.RequestSchema, property) { - return fmt.Errorf("operation %q request schema missing %q", op.ID, property) - } - return nil -} - -func schemaHasProperty(schema map[string]any, name string) bool { - if schema == nil { - return false - } - if props, ok := schema["properties"].(map[string]any); ok { - _, found := props[name] - return found - } - for _, key := range []string{"allOf", "anyOf", "oneOf"} { - items, ok := schema[key].([]any) - if !ok { - continue - } - for _, item := range items { - child, ok := item.(map[string]any) - if ok && schemaHasProperty(child, name) { - return true - } - } - } - return false -} - -func requirePathParams(op plugin.APIOperation, names ...string) error { - seen := map[string]bool{} - for _, param := range op.Parameters { - if param.In == "path" && param.Required { - seen[param.Name] = true - } - } - for _, name := range names { - if !seen[name] { - return fmt.Errorf("missing required path parameter %q", name) - } - } - return nil -} diff --git a/cmd/internal/restishzpan/types.go b/cmd/internal/restishzpan/types.go deleted file mode 100644 index 48f0f1af..00000000 --- a/cmd/internal/restishzpan/types.go +++ /dev/null @@ -1,70 +0,0 @@ -package restishzpan - -import "time" - -type uploadPart struct { - PartNumber int `json:"partNumber"` - URL string `json:"url"` - ExpiresAt string `json:"expiresAt"` - Headers map[string]string `json:"headers"` -} - -type uploadInstructions struct { - SessionID string `json:"sessionId"` - UploadID *string `json:"uploadId"` - Mode string `json:"mode"` - PartSize int64 `json:"partSize"` - PartCount int `json:"partCount"` - ExpiresAt string `json:"expiresAt"` - PresignedExpiresAt string `json:"presignedExpiresAt"` - RequiredHeaders map[string]string `json:"requiredHeaders"` - Parts []uploadPart `json:"parts"` -} - -type matterResult struct { - ID string `json:"id"` - OrgID string `json:"orgId,omitempty"` - Alias string `json:"alias,omitempty"` - Name string `json:"name"` - Type string `json:"type,omitempty"` - Size *int64 `json:"size,omitempty"` - Parent string `json:"parent,omitempty"` - Object string `json:"object,omitempty"` - StorageID string `json:"storageId,omitempty"` - Status string `json:"status,omitempty"` - CreatedAt string `json:"createdAt,omitempty"` - UpdatedAt string `json:"updatedAt,omitempty"` - Upload *uploadInstructions `json:"upload,omitempty"` -} - -type presignPartsResult struct { - UploadID *string `json:"uploadId"` - Mode string `json:"mode"` - PartSize int64 `json:"partSize"` - PartCount int `json:"partCount"` - PresignedExpiresAt string `json:"presignedExpiresAt"` - RequiredHeaders map[string]string `json:"requiredHeaders"` - Parts []uploadPart `json:"parts"` -} - -type completedPart struct { - PartNumber int `json:"partNumber"` - ETag string `json:"etag"` -} - -type uploadResult struct { - Object matterResult `json:"object"` - Upload resultUpload `json:"upload"` - Checkpoint string `json:"checkpoint,omitempty"` - CompletedAt time.Time `json:"completedAt"` -} - -type resultUpload struct { - API string `json:"api"` - Profile string `json:"profile,omitempty"` - SessionID string `json:"sessionId"` - Mode string `json:"mode"` - PartSize int64 `json:"partSize"` - PartCount int `json:"partCount"` - Parts []completedPart `json:"parts"` -} diff --git a/cmd/internal/restishzpan/upload.go b/cmd/internal/restishzpan/upload.go deleted file mode 100644 index 8d934ce4..00000000 --- a/cmd/internal/restishzpan/upload.go +++ /dev/null @@ -1,517 +0,0 @@ -package restishzpan - -import ( - "context" - "errors" - "flag" - "fmt" - "io" - "mime" - "os" - "os/signal" - "path/filepath" - "sort" - "strings" - "sync" - "syscall" - "time" - - "github.com/rest-sh/restish/v2/plugin" -) - -type uploadOptions struct { - API string - Profile string - Source string - Parent string - Name string - Conflict string - Concurrency int - Resume bool - Abort bool - CheckpointDir string - ContentType string -} - -type fileIdentity struct { - Path string - Size int64 - ModTime time.Time -} - -func Run(startupArgs, args []string, h host) error { - if wantsHelp(args) { - return h.Response(200, nil, map[string]any{ - "usage": "restish zpan-upload [flags] SOURCE [DESTINATION]", - "examples": []string{ - "RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager ./photo.jpg", - "RSH_PROFILE=ci restish zpan-upload --api zpan --profile ci --parent folder-id ./photo.jpg report.jpg", - "RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --resume ./large.bin", - "RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --abort ./large.bin", - }, - }) - } - opts, err := parseOptions(args) - if err != nil { - return err - } - _ = startupArgs - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - defer stop() - return runWithStorage(ctx, opts, h, newHTTPStorageClient()) -} - -func wantsHelp(args []string) bool { - for _, arg := range args { - if arg == "-h" || arg == "--help" { - return true - } - } - return false -} - -func parseOptions(args []string) (uploadOptions, error) { - opts := uploadOptions{API: "zpan", Conflict: "fail", Concurrency: 4} - fs := flag.NewFlagSet("zpan-upload", flag.ContinueOnError) - fs.SetOutput(io.Discard) - fs.StringVar(&opts.API, "api", opts.API, "Restish API name") - fs.StringVar(&opts.Profile, "profile", "", "Restish profile name") - fs.StringVar(&opts.Parent, "parent", "", "destination folder/object parent") - fs.StringVar(&opts.Parent, "folder", "", "destination folder/object parent") - fs.StringVar(&opts.Name, "name", "", "destination object name") - fs.StringVar(&opts.Conflict, "conflict", opts.Conflict, "conflict policy: fail, rename, replace") - fs.IntVar(&opts.Concurrency, "concurrency", opts.Concurrency, "maximum concurrent part uploads") - fs.BoolVar(&opts.Resume, "resume", false, "resume an interrupted upload from the local checkpoint") - fs.BoolVar(&opts.Abort, "abort", false, "abort the checkpointed upload session and delete the local checkpoint") - fs.StringVar(&opts.CheckpointDir, "checkpoint-dir", "", "checkpoint directory") - fs.StringVar(&opts.ContentType, "content-type", "", "override detected content type") - if err := fs.Parse(args); err != nil { - return uploadOptions{}, err - } - if opts.API == "" { - return uploadOptions{}, fmt.Errorf("--api is required") - } - if opts.Concurrency < 1 || opts.Concurrency > 32 { - return uploadOptions{}, fmt.Errorf("--concurrency must be between 1 and 32") - } - if opts.Conflict != "fail" && opts.Conflict != "rename" && opts.Conflict != "replace" { - return uploadOptions{}, fmt.Errorf("--conflict must be fail, rename, or replace") - } - positional := fs.Args() - if len(positional) < 1 || len(positional) > 2 { - return uploadOptions{}, fmt.Errorf("usage: restish zpan-upload [flags] SOURCE [DESTINATION]") - } - opts.Source = positional[0] - if opts.Name == "" { - opts.Name = filepath.Base(opts.Source) - } - if len(positional) == 2 { - parent, name := splitDestination(positional[1], opts.Name) - if opts.Parent == "" { - opts.Parent = parent - } - if name != "" { - opts.Name = name - } - } - return opts, nil -} - -func splitDestination(dest, fallbackName string) (string, string) { - dest = filepath.ToSlash(strings.TrimSpace(dest)) - if dest == "" { - return "", fallbackName - } - if strings.HasSuffix(dest, "/") { - return strings.TrimSuffix(dest, "/"), fallbackName - } - parent, name := filepath.Split(dest) - return strings.TrimSuffix(filepath.ToSlash(parent), "/"), name -} - -func runWithStorage(ctx context.Context, opts uploadOptions, h host, storage storageClient) error { - store, err := newCheckpointStore(opts.CheckpointDir) - if err != nil { - return err - } - if opts.Abort { - src, err := sourcePathIdentity(opts.Source) - if err != nil { - return err - } - checkpointPath := store.path(opts, src) - ops, err := fetchOperations(ctx, h, opts.API, opts.Profile) - if err != nil { - return err - } - return abortCheckpoint(ctx, opts, h, store, checkpointPath, src, ops) - } - src, err := statSource(opts.Source) - if err != nil { - return err - } - if opts.ContentType == "" { - opts.ContentType = detectContentType(src.Path) - } - checkpointPath := store.path(opts, src) - ops, err := fetchOperations(ctx, h, opts.API, opts.Profile) - if err != nil { - return err - } - cp, initialParts, err := prepareUpload(ctx, opts, h, store, checkpointPath, src, ops) - if err != nil { - return err - } - if err := uploadMissingParts(ctx, opts, h, storage, store, checkpointPath, src, ops, &cp, initialParts); err != nil { - return err - } - object, err := completeUpload(ctx, opts, h, ops, cp) - if err != nil { - return err - } - if err := store.remove(checkpointPath); err != nil { - return err - } - return h.Response(200, nil, uploadResult{ - Object: object, - Upload: resultUpload{ - API: opts.API, - Profile: opts.Profile, - SessionID: cp.SessionID, - Mode: cp.Mode, - PartSize: cp.PartSize, - PartCount: cp.PartCount, - Parts: cp.completedParts(), - }, - CompletedAt: time.Now().UTC(), - }) -} - -func statSource(path string) (fileIdentity, error) { - abs, err := filepath.Abs(path) - if err != nil { - return fileIdentity{}, err - } - info, err := os.Stat(abs) - if err != nil { - return fileIdentity{}, err - } - if info.IsDir() { - return fileIdentity{}, fmt.Errorf("source must be a file: %s", abs) - } - return fileIdentity{Path: abs, Size: info.Size(), ModTime: info.ModTime()}, nil -} - -func sourcePathIdentity(path string) (fileIdentity, error) { - abs, err := filepath.Abs(path) - if err != nil { - return fileIdentity{}, err - } - return fileIdentity{Path: abs}, nil -} - -func detectContentType(path string) string { - return mime.TypeByExtension(filepath.Ext(path)) -} - -func prepareUpload(ctx context.Context, opts uploadOptions, h host, store checkpointStore, checkpointPath string, src fileIdentity, ops operationSet) (checkpoint, []uploadPart, error) { - if opts.Resume { - cp, err := store.load(checkpointPath) - if err != nil { - return checkpoint{}, nil, err - } - if err := validateCheckpoint(cp, opts, src); err != nil { - return checkpoint{}, nil, err - } - return cp, nil, nil - } - body := map[string]any{ - "name": opts.Name, - "size": src.Size, - "parent": opts.Parent, - "onConflict": opts.Conflict, - } - if opts.ContentType != "" { - body["type"] = opts.ContentType - } - resp, err := h.Do(&plugin.HTTPRequestMsg{ - Method: ops.Create.Method, - URI: opts.API + ops.Create.Path, - Body: body, - ContentType: "application/json", - NoCache: true, - Timeout: 60, - }) - if err != nil { - return checkpoint{}, nil, err - } - matter, err := decodeBody[matterResult](resp) - if err != nil { - return checkpoint{}, nil, err - } - if matter.Upload == nil { - return checkpoint{}, nil, fmt.Errorf("createObject did not return upload instructions for file draft") - } - cp := newCheckpoint(opts, src, matter, *matter.Upload) - if err := store.save(checkpointPath, cp); err != nil { - return checkpoint{}, nil, err - } - return cp, matter.Upload.Parts, nil -} - -func uploadMissingParts(ctx context.Context, opts uploadOptions, h host, storage storageClient, store checkpointStore, checkpointPath string, src fileIdentity, ops operationSet, cp *checkpoint, initialParts []uploadPart) error { - missing := missingPartNumbers(*cp) - parts, err := currentParts(ctx, opts, h, ops, *cp, missing, initialParts) - if err != nil { - return err - } - partByNumber := map[int]uploadPart{} - for _, part := range parts { - partByNumber[part.PartNumber] = part - } - work := make(chan int) - errs := make(chan error, 1) - var mu sync.Mutex - var wg sync.WaitGroup - workers := min(opts.Concurrency, cp.PartCount) - for range workers { - wg.Add(1) - go func() { - defer wg.Done() - for partNumber := range work { - part := partByNumber[partNumber] - etag, err := putPartWithRetry(ctx, opts, h, storage, ops, *cp, src, part) - if err != nil { - select { - case errs <- err: - default: - } - continue - } - mu.Lock() - cp.Parts[partNumber] = etag - saveErr := store.save(checkpointPath, *cp) - mu.Unlock() - if saveErr != nil { - select { - case errs <- saveErr: - default: - } - } - _ = h.Progress(fmt.Sprintf("uploaded part %d/%d", partNumber, cp.PartCount)) - } - }() - } - for _, partNumber := range missing { - select { - case <-ctx.Done(): - close(work) - wg.Wait() - return ctx.Err() - case err := <-errs: - close(work) - wg.Wait() - return err - case work <- partNumber: - } - } - close(work) - wg.Wait() - select { - case err := <-errs: - return err - default: - return nil - } -} - -func currentParts(ctx context.Context, opts uploadOptions, h host, ops operationSet, cp checkpoint, partNumbers []int, initialParts []uploadPart) ([]uploadPart, error) { - if len(partNumbers) == 0 { - return nil, nil - } - if len(initialParts) > 0 { - byNumber := map[int]uploadPart{} - for _, part := range initialParts { - byNumber[part.PartNumber] = part - } - parts := make([]uploadPart, 0, len(partNumbers)) - for _, partNumber := range partNumbers { - part, ok := byNumber[partNumber] - if !ok { - return nil, fmt.Errorf("createObject did not return upload instructions for part %d", partNumber) - } - parts = append(parts, part) - } - return parts, nil - } - return resignParts(ctx, opts, h, ops, cp, partNumbers) -} - -func putPartWithRetry(ctx context.Context, opts uploadOptions, h host, storage storageClient, ops operationSet, cp checkpoint, src fileIdentity, part uploadPart) (string, error) { - var lastErr error - for attempt := 1; attempt <= 3; attempt++ { - if part.URL == "" || expiresSoon(part.ExpiresAt, time.Now()) { - parts, err := resignParts(ctx, opts, h, ops, cp, []int{part.PartNumber}) - if err != nil { - return "", err - } - if len(parts) != 1 || parts[0].PartNumber != part.PartNumber { - return "", fmt.Errorf("re-sign response missing part %d", part.PartNumber) - } - part = parts[0] - } - etag, err := putPart(ctx, storage, src, cp.PartSize, part) - if err == nil { - return etag, nil - } - lastErr = err - if shouldResignAfterStorageError(err) { - part.URL = "" - } - select { - case <-ctx.Done(): - return "", ctx.Err() - case <-time.After(time.Duration(attempt) * 200 * time.Millisecond): - } - } - return "", lastErr -} - -func putPart(ctx context.Context, storage storageClient, src fileIdentity, partSize int64, part uploadPart) (string, error) { - file, err := os.Open(src.Path) - if err != nil { - return "", err - } - defer file.Close() - offset, size := partRange(src.Size, partSize, part.PartNumber) - reader := io.NewSectionReader(file, offset, size) - return storage.PutPart(ctx, part, reader, size) -} - -func partRange(fileSize, partSize int64, partNumber int) (int64, int64) { - if fileSize == 0 { - return 0, 0 - } - offset := int64(partNumber-1) * partSize - size := partSize - if remaining := fileSize - offset; remaining < size { - size = remaining - } - return offset, size -} - -func missingPartNumbers(cp checkpoint) []int { - missing := make([]int, 0, cp.PartCount-len(cp.Parts)) - for i := 1; i <= cp.PartCount; i++ { - if cp.Parts[i] == "" { - missing = append(missing, i) - } - } - return missing -} - -func resignParts(ctx context.Context, opts uploadOptions, h host, ops operationSet, cp checkpoint, partNumbers []int) ([]uploadPart, error) { - var all []uploadPart - for start := 0; start < len(partNumbers); start += 100 { - end := min(start+100, len(partNumbers)) - parts, err := resignPartBatch(ctx, opts, h, ops, cp, partNumbers[start:end]) - if err != nil { - return nil, err - } - all = append(all, parts...) - } - sort.Slice(all, func(i, j int) bool { return all[i].PartNumber < all[j].PartNumber }) - seen := map[int]bool{} - for _, part := range all { - seen[part.PartNumber] = true - } - for _, partNumber := range partNumbers { - if !seen[partNumber] { - return nil, fmt.Errorf("re-sign response missing part %d", partNumber) - } - } - return all, nil -} - -func resignPartBatch(ctx context.Context, opts uploadOptions, h host, ops operationSet, cp checkpoint, partNumbers []int) ([]uploadPart, error) { - resp, err := h.Do(&plugin.HTTPRequestMsg{ - Method: ops.Presign.Method, - URI: opts.API + expandUploadPath(ops.Presign.Path, cp), - Body: map[string]any{"partNumbers": partNumbers}, - ContentType: "application/json", - NoCache: true, - Timeout: 60, - }) - if err != nil { - return nil, err - } - _ = ctx - result, err := decodeBody[presignPartsResult](resp) - if err != nil { - return nil, err - } - sort.Slice(result.Parts, func(i, j int) bool { return result.Parts[i].PartNumber < result.Parts[j].PartNumber }) - return result.Parts, nil -} - -func completeUpload(ctx context.Context, opts uploadOptions, h host, ops operationSet, cp checkpoint) (matterResult, error) { - parts := cp.completedParts() - if len(parts) != cp.PartCount { - return matterResult{}, fmt.Errorf("cannot complete: %d of %d parts uploaded", len(parts), cp.PartCount) - } - resp, err := h.Do(&plugin.HTTPRequestMsg{ - Method: ops.Complete.Method, - URI: opts.API + expandUploadPath(ops.Complete.Path, cp), - Body: map[string]any{"parts": parts}, - ContentType: "application/json", - NoCache: true, - Timeout: 120, - }) - if err != nil { - return matterResult{}, err - } - _ = ctx - return decodeBody[matterResult](resp) -} - -func abortCheckpoint(ctx context.Context, opts uploadOptions, h host, store checkpointStore, checkpointPath string, src fileIdentity, ops operationSet) error { - cp, err := store.load(checkpointPath) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("no checkpoint found for %s", src.Path) - } - return err - } - if err := validateAbortCheckpoint(cp, opts, src); err != nil { - return err - } - resp, err := h.Do(&plugin.HTTPRequestMsg{ - Method: ops.Abort.Method, - URI: opts.API + expandUploadPath(ops.Abort.Path, cp), - NoCache: true, - Timeout: 60, - }) - if err != nil { - return err - } - if _, err := decodeBody[map[string]any](resp); err != nil && resp.Status != 204 { - return err - } - if err := store.remove(checkpointPath); err != nil { - return err - } - _ = ctx - return h.Response(200, nil, map[string]any{ - "aborted": true, - "api": opts.API, - "profile": opts.Profile, - "objectId": cp.ObjectID, - "sessionId": cp.SessionID, - }) -} - -func expandUploadPath(path string, cp checkpoint) string { - out := strings.ReplaceAll(path, "{id}", cp.ObjectID) - out = strings.ReplaceAll(out, "{uploadSessionId}", cp.SessionID) - return out -} diff --git a/cmd/internal/restishzpan/upload_test.go b/cmd/internal/restishzpan/upload_test.go deleted file mode 100644 index dbd574a9..00000000 --- a/cmd/internal/restishzpan/upload_test.go +++ /dev/null @@ -1,1249 +0,0 @@ -package restishzpan - -import ( - "context" - "errors" - "io" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "testing" - "time" - - "github.com/rest-sh/restish/v2/plugin" -) - -type fakeHost struct { - spec *plugin.APISpecResponseMsg - specErr error - requests []plugin.HTTPRequestMsg - responses []*plugin.HTTPResponseMsg - body any - mu sync.Mutex -} - -func (h *fakeHost) FetchAPISpecContext(_ context.Context, api, profile string) (*plugin.APISpecResponseMsg, error) { - if h.specErr != nil { - return nil, h.specErr - } - if h.spec == nil { - return &plugin.APISpecResponseMsg{Name: api, Profile: profile, Operations: validOps()}, nil - } - return h.spec, nil -} - -func (h *fakeHost) Do(req *plugin.HTTPRequestMsg) (*plugin.HTTPResponseMsg, error) { - h.mu.Lock() - defer h.mu.Unlock() - h.requests = append(h.requests, *req) - if len(h.responses) == 0 { - return nil, errors.New("unexpected request") - } - resp := h.responses[0] - h.responses = h.responses[1:] - return resp, nil -} - -func (h *fakeHost) Response(_ int, _ map[string][]string, body any) error { - h.body = body - return nil -} - -func (h *fakeHost) Progress(string) error { return nil } -func (h *fakeHost) Warn(string) error { return nil } - -type fakeStorage struct { - failFirst bool - alwaysErr error - active int - maxActive int - mu sync.Mutex - seen map[int]string -} - -func (s *fakeStorage) PutPart(ctx context.Context, part uploadPart, body io.Reader, size int64) (string, error) { - if err := ctx.Err(); err != nil { - return "", err - } - s.mu.Lock() - if s.seen == nil { - s.seen = map[int]string{} - } - s.active++ - if s.active > s.maxActive { - s.maxActive = s.active - } - s.mu.Unlock() - defer func() { - s.mu.Lock() - s.active-- - s.mu.Unlock() - }() - data, err := io.ReadAll(body) - if err != nil { - return "", err - } - if int64(len(data)) != size { - return "", errors.New("size mismatch") - } - if s.alwaysErr != nil { - return "", s.alwaysErr - } - if s.failFirst { - s.failFirst = false - return "", storageStatusError{status: 403} - } - s.mu.Lock() - s.seen[part.PartNumber] = string(data) - s.mu.Unlock() - return `"` + "etag-" + string(rune('0'+part.PartNumber)) + `"`, nil -} - -func TestParseOptions(t *testing.T) { - opts, err := parseOptions([]string{"--api", "zpan", "--profile", "ci", "--conflict", "rename", "--concurrency", "2", "--parent", "folder", "file.txt", "name.txt"}) - if err != nil { - t.Fatal(err) - } - if opts.API != "zpan" || opts.Profile != "ci" || opts.Conflict != "rename" || opts.Concurrency != 2 || opts.Parent != "folder" || opts.Name != "name.txt" { - t.Fatalf("unexpected opts: %#v", opts) - } - if _, err := parseOptions([]string{"--conflict", "merge", "file.txt"}); err == nil { - t.Fatal("expected invalid conflict to fail") - } - if _, err := parseOptions([]string{"--concurrency", "0", "file.txt"}); err == nil { - t.Fatal("expected invalid concurrency to fail") - } - opts, err = parseOptions([]string{"--folder", "nested", "file.txt", "child/"}) - if err != nil { - t.Fatal(err) - } - if opts.Parent != "nested" || opts.Name != "file.txt" { - t.Fatalf("unexpected folder parse: %#v", opts) - } -} - -func TestFetchOperationsValidatesContract(t *testing.T) { - _, err := fetchOperations(context.Background(), &fakeHost{specErr: errors.New("spec failed")}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "spec failed") { - t.Fatalf("expected spec error, got %v", err) - } - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Error: "host spec failed"}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "host spec failed") { - t.Fatalf("expected host spec error, got %v", err) - } - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{ - Operations: []plugin.APIOperation{{ID: opCreate, Method: "POST", Path: "/api/objects", HasBody: true}}, - }}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "missing required operation") { - t.Fatalf("expected missing operation error, got %v", err) - } - _, err = fetchOperations(context.Background(), &fakeHost{}, "zpan", "") - if err != nil { - t.Fatal(err) - } - restishCommandOps := validOps() - restishCommandOps[0].ID = "create-object" - restishCommandOps[1].ID = "presign-object-upload-parts" - restishCommandOps[2].ID = "complete-object-upload" - restishCommandOps[3].ID = "abort-object-upload" - gotOps, err := fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: restishCommandOps}}, "zpan", "") - if err != nil { - t.Fatalf("expected Restish command operation aliases to validate: %v", err) - } - if gotOps.Create.ID != "create-object" || gotOps.Presign.ID != "presign-object-upload-parts" || gotOps.Complete.ID != "complete-object-upload" || gotOps.Abort.ID != "abort-object-upload" { - t.Fatalf("unexpected aliased operations: %#v", gotOps) - } - badMethod := validOps() - badMethod[0].Method = "GET" - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: badMethod}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "uses GET") { - t.Fatalf("expected method error, got %v", err) - } - badSchema := validOps() - badSchema[0].RequestSchema = map[string]any{} - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: badSchema}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "missing") { - t.Fatalf("expected schema error, got %v", err) - } - badPartsSchema := validOps() - badPartsSchema[1].RequestSchema = map[string]any{} - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: badPartsSchema}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "partNumbers") { - t.Fatalf("expected presign schema error, got %v", err) - } - badParams := validOps() - badParams[2].Parameters = nil - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: badParams}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "path parameter") { - t.Fatalf("expected path param error, got %v", err) - } -} - -func TestFetchOperationsSpecErrors(t *testing.T) { - _, err := fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Error: "bad spec"}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "bad spec") { - t.Fatalf("expected spec error, got %v", err) - } - - badMethodOps := validOps() - badMethodOps[0].Method = "GET" - _, err = fetchOperations(context.Background(), &fakeHost{spec: &plugin.APISpecResponseMsg{Operations: badMethodOps}}, "zpan", "") - if err == nil || !strings.Contains(err.Error(), "want POST") { - t.Fatalf("expected method validation error, got %v", err) - } -} - -func TestSchemaValidationHelpers(t *testing.T) { - if !schemaHasProperty(map[string]any{ - "allOf": []any{ - map[string]any{"properties": map[string]any{"name": map[string]any{}}}, - }, - }, "name") { - t.Fatal("expected nested property to be found") - } - if schemaHasProperty(nil, "missing") { - t.Fatal("nil schema should not contain properties") - } - if err := requirePathParams(plugin.APIOperation{Parameters: []plugin.APIParam{{Name: "id", In: "path", Required: true}}}, "id", "uploadSessionId"); err == nil { - t.Fatal("expected missing path parameter to fail") - } - if err := validateCreateOperation(plugin.APIOperation{ID: opCreate, HasBody: false}); err == nil { - t.Fatal("expected missing create body to fail") - } - if err := validatePartsOperation(plugin.APIOperation{ID: opPresign, HasBody: false}, "partNumbers"); err == nil { - t.Fatal("expected missing parts body to fail") - } -} - -func TestCheckpointSaveModeAndNoURLLeak(t *testing.T) { - dir := t.TempDir() - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{ - Version: checkpointVersion, - API: "zpan", - SourcePath: "/tmp/file", - FileSize: 3, - ModTimeUnixNS: 1, - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 2, - Parts: map[int]string{1: "etag"}, - } - path := filepath.Join(dir, "cp.json") - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - for _, forbidden := range []string{"https://", "Authorization", "Cookie", "secret"} { - if strings.Contains(string(data), forbidden) { - t.Fatalf("checkpoint leaked %q: %s", forbidden, data) - } - } - if runtime.GOOS != "windows" { - info, err := os.Stat(path) - if err != nil { - t.Fatal(err) - } - if got := info.Mode().Perm(); got != 0o600 { - t.Fatalf("mode = %o, want 0600", got) - } - } -} - -func TestResumeRejectsChangedFile(t *testing.T) { - src := fileIdentity{Path: "/tmp/file", Size: 10, ModTime: time.Unix(1, 0)} - cp := checkpoint{API: "zpan", SourcePath: src.Path, FileSize: src.Size, ModTimeUnixNS: src.ModTime.UnixNano(), Parent: "", Name: "file", Conflict: "fail"} - pathChanged := cp - pathChanged.SourcePath = "/tmp/other-file" - err := validateCheckpoint(pathChanged, uploadOptions{API: "zpan", Name: "file", Conflict: "fail"}, src) - if err == nil || !strings.Contains(err.Error(), "checkpoint source changed") { - t.Fatalf("expected source mismatch error, got %v", err) - } - changed := src - changed.Size = 11 - err = validateCheckpoint(cp, uploadOptions{API: "zpan", Name: "file", Conflict: "fail"}, changed) - if err == nil || !strings.Contains(err.Error(), "source file changed") { - t.Fatalf("expected changed file error, got %v", err) - } -} - -func TestValidateCheckpointRejectsChangedCommandContext(t *testing.T) { - src := fileIdentity{Path: "/tmp/file", Size: 10, ModTime: time.Unix(1, 0)} - cp := checkpoint{API: "zpan", Profile: "ci", SourcePath: src.Path, FileSize: src.Size, ModTimeUnixNS: src.ModTime.UnixNano(), Parent: "a", Name: "file", Conflict: "fail"} - if err := validateCheckpoint(cp, uploadOptions{API: "other", Profile: "ci", Parent: "a", Name: "file", Conflict: "fail"}, src); err == nil { - t.Fatal("expected api/profile mismatch") - } - if err := validateCheckpoint(cp, uploadOptions{API: "zpan", Profile: "ci", Parent: "b", Name: "file", Conflict: "fail"}, src); err == nil { - t.Fatal("expected destination mismatch") - } -} - -func TestRunReturnsParseErrorBeforeHostUse(t *testing.T) { - err := Run(nil, nil, &fakeHost{}) - if err == nil || !strings.Contains(err.Error(), "usage: restish zpan-upload") { - t.Fatalf("expected usage error, got %v", err) - } -} - -func TestParseOptionsAdditionalErrorsAndDestinationForms(t *testing.T) { - if _, err := parseOptions([]string{"--api", "", "file.txt"}); err == nil || !strings.Contains(err.Error(), "--api") { - t.Fatalf("expected api error, got %v", err) - } - if _, err := parseOptions([]string{"--concurrency", "0", "file.txt"}); err == nil || !strings.Contains(err.Error(), "concurrency") { - t.Fatalf("expected concurrency error, got %v", err) - } - if _, err := parseOptions([]string{"--bad", "file.txt"}); err == nil { - t.Fatal("expected bad flag to fail") - } - opts, err := parseOptions([]string{"--parent", "explicit", "file.txt", "folder/"}) - if err != nil { - t.Fatal(err) - } - if opts.Parent != "explicit" || opts.Name != "file.txt" { - t.Fatalf("unexpected explicit parent opts: %#v", opts) - } - parent, name := splitDestination("", "fallback") - if parent != "" || name != "fallback" { - t.Fatalf("unexpected empty destination parent=%q name=%q", parent, name) - } -} - -func TestStatSourceRejectsDirectoryAndMissingFile(t *testing.T) { - if _, err := statSource(t.TempDir()); err == nil || !strings.Contains(err.Error(), "source must be a file") { - t.Fatalf("expected directory error, got %v", err) - } - if _, err := statSource(filepath.Join(t.TempDir(), "missing")); err == nil { - t.Fatal("expected missing file error") - } -} - -func TestPrepareUploadResumeLoadsCheckpoint(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Resume: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - want := checkpoint{Version: checkpointVersion, API: opts.API, SourcePath: src.Path, FileSize: src.Size, ModTimeUnixNS: src.ModTime.UnixNano(), ObjectID: "obj", SessionID: "sess", Mode: "multipart", PartSize: 2, PartCount: 2, Name: opts.Name, Conflict: opts.Conflict, Parts: map[int]string{}} - if err := store.save(store.path(opts, src), want); err != nil { - t.Fatal(err) - } - got, initial, err := prepareUpload(context.Background(), opts, &fakeHost{}, store, store.path(opts, src), src, operationSet{}) - if err != nil { - t.Fatal(err) - } - if got.ObjectID != "obj" || len(initial) != 0 { - t.Fatalf("unexpected resume checkpoint=%#v initial=%#v", got, initial) - } -} - -func TestPrepareUploadFailsWithoutUploadInstructions(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 201, Body: map[string]any{"id": "folder", "name": "folder"}}}} - _, _, err = prepareUpload(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail"}, host, checkpointStore{dir: dir}, filepath.Join(dir, "cp.json"), src, operationSet{Create: validOps()[0]}) - if err == nil || !strings.Contains(err.Error(), "upload instructions") { - t.Fatalf("expected upload instructions error, got %v", err) - } -} - -func TestPrepareUploadCreateAndSaveErrors(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - _, _, err = prepareUpload(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail"}, &fakeHost{}, checkpointStore{dir: dir}, filepath.Join(dir, "cp.json"), src, operationSet{Create: validOps()[0]}) - if err == nil || !strings.Contains(err.Error(), "unexpected request") { - t.Fatalf("expected delegated request error, got %v", err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 201, Body: map[string]any{ - "id": "obj", "name": "file.bin", - "upload": map[string]any{ - "sessionId": "sess", "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/part", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}}, - }, - }}}} - _, _, err = prepareUpload(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail"}, host, checkpointStore{dir: filepath.Join(dir, "missing")}, filepath.Join(dir, "missing", "cp.json"), src, operationSet{Create: validOps()[0]}) - if err == nil { - t.Fatal("expected checkpoint save error") - } -} - -func TestSingleUploadUsesInitialPresignedURLAndDelegatedControlPlane(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.txt") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{ - { - Status: 201, - Body: map[string]any{ - "id": "obj", "name": "file.txt", "size": float64(3), - "upload": map[string]any{ - "sessionId": "sess", "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{"content-type": "text/plain"}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/part", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{"content-type": "text/plain"}}}, - }, - }, - }, - {Status: 200, Body: map[string]any{"id": "obj", "name": "file.txt", "status": "active"}}, - }} - storage := &fakeStorage{} - err := runWithStorage(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.txt", Conflict: "fail", Concurrency: 1, CheckpointDir: dir}, host, storage) - if err != nil { - t.Fatal(err) - } - if len(host.requests) != 2 { - t.Fatalf("requests = %d, want 2", len(host.requests)) - } - if host.requests[0].URI != "zpan/api/objects" || host.requests[1].URI != "zpan/api/objects/obj/uploads/sess/completions" { - t.Fatalf("unexpected delegated URIs: %#v", host.requests) - } - if storage.seen[1] != "abc" { - t.Fatalf("uploaded body = %q", storage.seen[1]) - } -} - -func TestRunWithStoragePreservesCheckpointOnCompleteFailure(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.txt") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{ - { - Status: 201, - Body: map[string]any{ - "id": "obj", "name": "file.txt", "size": float64(3), - "upload": map[string]any{ - "sessionId": "sess", "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/part", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}}, - }, - }, - }, - {Status: 409, Body: map[string]any{"error": "conflict"}}, - }} - opts := uploadOptions{API: "zpan", Source: source, Name: "file.txt", Conflict: "fail", Concurrency: 1, CheckpointDir: dir} - err := runWithStorage(context.Background(), opts, host, &fakeStorage{}) - if err == nil || !strings.Contains(err.Error(), "HTTP 409") { - t.Fatalf("expected complete failure, got %v", err) - } - src, statErr := statSource(source) - if statErr != nil { - t.Fatal(statErr) - } - if _, statErr := os.Stat((checkpointStore{dir: dir}).path(opts, src)); statErr != nil { - t.Fatalf("expected checkpoint to remain: %v", statErr) - } -} - -func TestRunWithStorageFetchSpecError(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.txt") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - err := runWithStorage(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.txt", Conflict: "fail", Concurrency: 1, CheckpointDir: dir}, &fakeHost{specErr: errors.New("spec failed")}, &fakeStorage{}) - if err == nil || !strings.Contains(err.Error(), "spec failed") { - t.Fatalf("expected spec error, got %v", err) - } -} - -func TestMultipartResumeResignsMissingPartsAndBoundsConcurrency(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abcdef"), 0o600); err != nil { - t.Fatal(err) - } - info, err := os.Stat(source) - if err != nil { - t.Fatal(err) - } - src := fileIdentity{Path: source, Size: info.Size(), ModTime: info.ModTime()} - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Concurrency: 2, Resume: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{ - Version: checkpointVersion, - API: opts.API, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 3, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{1: "etag-1"}, - } - if err := store.save(store.path(opts, src), cp); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{ - {Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(2), "partCount": float64(3), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{ - map[string]any{"partNumber": float64(2), "url": "https://storage.test/2", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}, - map[string]any{"partNumber": float64(3), "url": "https://storage.test/3", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}, - }, - }}, - {Status: 200, Body: map[string]any{"id": "obj", "name": "file.bin", "status": "active"}}, - }} - storage := &fakeStorage{} - if err := runWithStorage(context.Background(), opts, host, storage); err != nil { - t.Fatal(err) - } - if storage.maxActive > 2 { - t.Fatalf("max concurrency = %d, want <= 2", storage.maxActive) - } - if storage.seen[2] != "cd" || storage.seen[3] != "ef" { - t.Fatalf("unexpected part bodies: %#v", storage.seen) - } -} - -func TestUploadMissingPartsNoopsWhenComplete(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{PartCount: 1, PartSize: 3, Parts: map[int]string{1: "etag"}} - err = uploadMissingParts(context.Background(), uploadOptions{API: "zpan", Concurrency: 1}, &fakeHost{}, &fakeStorage{}, checkpointStore{dir: dir}, filepath.Join(dir, "cp.json"), src, operationSet{}, &cp, nil) - if err != nil { - t.Fatal(err) - } -} - -func TestUploadMissingPartsReturnsStorageError(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{Mode: "single", PartCount: 1, PartSize: 3, Parts: map[int]string{}} - err = uploadMissingParts(context.Background(), uploadOptions{API: "zpan", Concurrency: 1}, &fakeHost{}, &fakeStorage{alwaysErr: storageStatusError{status: 500}}, checkpointStore{dir: dir}, filepath.Join(dir, "cp.json"), src, operationSet{}, &cp, []uploadPart{{PartNumber: 1, URL: "https://storage.test/1", ExpiresAt: time.Now().Add(time.Hour).Format(time.RFC3339)}}) - if err == nil || !strings.Contains(err.Error(), "HTTP 500") { - t.Fatalf("expected storage error, got %v", err) - } -} - -func TestUploadMissingPartsResumesSingleUploadWithResign(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{ - Version: checkpointVersion, - API: "zpan", - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "single", - PartSize: 3, - PartCount: 1, - Name: "file.bin", - Conflict: "fail", - Parts: map[int]string{}, - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{"content-type": "application/octet-stream"}, - "parts": []any{map[string]any{ - "partNumber": float64(1), - "url": "https://storage.test/single", - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "headers": map[string]any{"content-type": "application/octet-stream"}, - }}, - }}}} - storage := &fakeStorage{} - path := filepath.Join(dir, "cp.json") - - err = uploadMissingParts(context.Background(), uploadOptions{API: "zpan", Concurrency: 1}, host, storage, checkpointStore{dir: dir}, path, src, operationSet{Presign: validOps()[1]}, &cp, nil) - if err != nil { - t.Fatal(err) - } - if len(host.requests) != 1 || host.requests[0].URI != "zpan/api/objects/obj/uploads/sess/parts" { - t.Fatalf("expected single re-sign request, got %#v", host.requests) - } - body := host.requests[0].Body.(map[string]any) - if got := body["partNumbers"].([]int); len(got) != 1 || got[0] != 1 { - t.Fatalf("unexpected re-sign body: %#v", body) - } - if cp.Parts[1] == "" || storage.seen[1] != "abc" { - t.Fatalf("expected checkpoint etag and uploaded bytes, cp=%#v seen=%#v", cp.Parts, storage.seen) - } - data, err := os.ReadFile(path) - if err != nil { - t.Fatal(err) - } - if strings.Contains(string(data), "https://storage.test") { - t.Fatalf("checkpoint stored presigned URL: %s", data) - } -} - -func TestPutPartWithRetryResignsAfterExpiredURL(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/new", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}}, - }}}} - storage := &fakeStorage{} - etag, err := putPartWithRetry(context.Background(), uploadOptions{API: "zpan"}, host, storage, operationSet{Presign: validOps()[1]}, checkpoint{Mode: "multipart", ObjectID: "obj", SessionID: "sess", PartSize: 3, PartCount: 1}, src, uploadPart{PartNumber: 1, URL: "https://storage.test/old", ExpiresAt: time.Now().Add(-time.Minute).Format(time.RFC3339)}) - if err != nil { - t.Fatal(err) - } - if etag == "" || len(host.requests) != 1 { - t.Fatalf("expected re-sign and etag, etag=%q requests=%d", etag, len(host.requests)) - } -} - -func TestPutPartWithRetryResignsSingleAfterExpiredURL(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{"content-type": "application/octet-stream"}, - "parts": []any{map[string]any{ - "partNumber": float64(1), - "url": "https://storage.test/new-single", - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "headers": map[string]any{"content-type": "application/octet-stream"}, - }}, - }}}} - storage := &fakeStorage{} - etag, err := putPartWithRetry(context.Background(), uploadOptions{API: "zpan"}, host, storage, operationSet{Presign: validOps()[1]}, checkpoint{ - Mode: "single", - ObjectID: "obj", - SessionID: "sess", - PartSize: 3, - PartCount: 1, - }, src, uploadPart{ - PartNumber: 1, - URL: "https://storage.test/expired-single", - ExpiresAt: time.Now().Add(-time.Minute).Format(time.RFC3339), - Headers: map[string]string{"content-type": "application/octet-stream"}, - }) - if err != nil { - t.Fatal(err) - } - if etag == "" || len(host.requests) != 1 { - t.Fatalf("expected single re-sign and etag, etag=%q requests=%d", etag, len(host.requests)) - } - if got := storage.seen[1]; got != "abc" { - t.Fatalf("expected uploaded bytes after re-sign, got %#v", storage.seen) - } -} - -func TestPutPartWithRetryErrors(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - _, err = putPartWithRetry(context.Background(), uploadOptions{API: "zpan"}, &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "requiredHeaders": map[string]any{}, "parts": []any{}, - }}}}, &fakeStorage{}, operationSet{Presign: validOps()[1]}, checkpoint{Mode: "multipart", ObjectID: "obj", SessionID: "sess", PartSize: 3, PartCount: 1}, src, uploadPart{PartNumber: 1}) - if err == nil || !strings.Contains(err.Error(), "missing part 1") { - t.Fatalf("expected missing re-sign part error, got %v", err) - } - _, err = putPart(context.Background(), &fakeStorage{}, fileIdentity{Path: filepath.Join(dir, "missing"), Size: 1}, 1, uploadPart{PartNumber: 1}) - if err == nil { - t.Fatal("expected missing source error") - } -} - -func TestResignPartsBatchesAtSchemaLimit(t *testing.T) { - host := &fakeHost{} - for batch := 0; batch < 3; batch++ { - parts := []any{} - start := batch*100 + 1 - end := min(start+100, 206) - for partNumber := start; partNumber < end; partNumber++ { - parts = append(parts, map[string]any{ - "partNumber": float64(partNumber), - "url": "https://storage.test/part", - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "headers": map[string]any{}, - }) - } - host.responses = append(host.responses, &plugin.HTTPResponseMsg{Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(1), "partCount": float64(205), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": parts, - }}) - } - partNumbers := make([]int, 205) - for i := range partNumbers { - partNumbers[i] = i + 1 - } - parts, err := resignParts(context.Background(), uploadOptions{API: "zpan"}, host, operationSet{Presign: validOps()[1]}, checkpoint{Mode: "multipart", ObjectID: "obj", SessionID: "sess"}, partNumbers) - if err != nil { - t.Fatal(err) - } - if len(parts) != 205 || len(host.requests) != 3 { - t.Fatalf("parts=%d requests=%d, want 205 parts in 3 requests", len(parts), len(host.requests)) - } - for i, req := range host.requests { - body := req.Body.(map[string]any) - got := body["partNumbers"].([]int) - if len(got) > 100 { - t.Fatalf("batch %d has %d part numbers", i, len(got)) - } - } -} - -func TestResignPartsRequiresRequestedPartDescriptors(t *testing.T) { - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(1), "partCount": float64(2), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/1", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}}, - }}}} - _, err := resignParts(context.Background(), uploadOptions{API: "zpan"}, host, operationSet{Presign: validOps()[1]}, checkpoint{Mode: "multipart", ObjectID: "obj", SessionID: "sess"}, []int{1, 2}) - if err == nil || !strings.Contains(err.Error(), "missing part 2") { - t.Fatalf("expected missing part error, got %v", err) - } -} - -func TestResignAndCompleteErrors(t *testing.T) { - _, err := resignParts(context.Background(), uploadOptions{API: "zpan"}, &fakeHost{}, operationSet{Presign: validOps()[1]}, checkpoint{Mode: "multipart", ObjectID: "obj", SessionID: "sess"}, []int{1}) - if err == nil || !strings.Contains(err.Error(), "unexpected request") { - t.Fatalf("expected delegated request error, got %v", err) - } - _, err = completeUpload(context.Background(), uploadOptions{API: "zpan"}, &fakeHost{}, operationSet{}, checkpoint{PartCount: 2, Parts: map[int]string{1: "etag"}}) - if err == nil || !strings.Contains(err.Error(), "cannot complete") { - t.Fatalf("expected incomplete error, got %v", err) - } - _, err = completeUpload(context.Background(), uploadOptions{API: "zpan"}, &fakeHost{}, operationSet{Complete: validOps()[2]}, checkpoint{ObjectID: "obj", SessionID: "sess", PartCount: 1, Parts: map[int]string{1: "etag"}}) - if err == nil || !strings.Contains(err.Error(), "unexpected request") { - t.Fatalf("expected complete request error, got %v", err) - } -} - -func TestAbortCheckpointErrors(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Abort: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - err = abortCheckpoint(context.Background(), opts, &fakeHost{}, store, store.path(opts, src), src, operationSet{}) - if err == nil || !strings.Contains(err.Error(), "no checkpoint found") { - t.Fatalf("expected missing checkpoint error, got %v", err) - } - cp := checkpoint{Version: checkpointVersion, API: opts.API, SourcePath: src.Path, FileSize: src.Size, ModTimeUnixNS: src.ModTime.UnixNano(), ObjectID: "obj", SessionID: "sess", Mode: "multipart", PartSize: 2, PartCount: 2, Name: opts.Name, Conflict: opts.Conflict, Parts: map[int]string{}} - path := store.path(opts, src) - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 500, Body: map[string]any{"error": "bad"}}}} - err = abortCheckpoint(context.Background(), opts, host, store, path, src, operationSet{Abort: validOps()[3]}) - if err == nil || !strings.Contains(err.Error(), "HTTP 500") { - t.Fatalf("expected abort status error, got %v", err) - } - wrongDestination := opts - wrongDestination.Name = "other.bin" - err = abortCheckpoint(context.Background(), wrongDestination, &fakeHost{}, store, path, src, operationSet{Abort: validOps()[3]}) - if err == nil || !strings.Contains(err.Error(), "checkpoint destination differs") { - t.Fatalf("expected checkpoint validation error, got %v", err) - } -} - -func TestAbortUsesDelegatedDeleteAndRemovesCheckpoint(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - info, err := os.Stat(source) - if err != nil { - t.Fatal(err) - } - src := fileIdentity{Path: source, Size: info.Size(), ModTime: info.ModTime()} - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Abort: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{ - Version: checkpointVersion, - API: opts.API, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 2, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{}, - } - path := store.path(opts, src) - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 204}}} - if err := runWithStorage(context.Background(), opts, host, &fakeStorage{}); err != nil { - t.Fatal(err) - } - if len(host.requests) != 1 || host.requests[0].Method != "DELETE" || host.requests[0].URI != "zpan/api/objects/obj/uploads/sess" { - t.Fatalf("unexpected abort request: %#v", host.requests) - } - if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("checkpoint still exists: %v", err) - } -} - -func TestAbortAcceptsSuccessBody(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Abort: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - cp := checkpoint{ - Version: checkpointVersion, - API: opts.API, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "single", - PartSize: 3, - PartCount: 1, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{}, - } - path := store.path(opts, src) - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{"aborted": true}}}} - if err := abortCheckpoint(context.Background(), opts, host, store, path, src, operationSet{Abort: validOps()[3]}); err != nil { - t.Fatal(err) - } - if host.body == nil { - t.Fatal("expected abort response body") - } - if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("checkpoint still exists: %v", err) - } -} - -func TestAbortDoesNotRequireCurrentSourceFile(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Abort: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - path := store.path(opts, src) - cp := checkpoint{ - Version: checkpointVersion, - API: opts.API, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 2, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{}, - } - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - if err := os.Remove(source); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 204}}} - if err := runWithStorage(context.Background(), opts, host, &fakeStorage{}); err != nil { - t.Fatal(err) - } - if len(host.requests) != 1 || host.requests[0].Method != "DELETE" { - t.Fatalf("unexpected abort request: %#v", host.requests) - } - if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("checkpoint still exists: %v", err) - } -} - -func TestAbortIgnoresChangedSourceFile(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - src, err := statSource(source) - if err != nil { - t.Fatal(err) - } - opts := uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail", Abort: true, CheckpointDir: dir} - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - path := store.path(opts, src) - cp := checkpoint{ - Version: checkpointVersion, - API: opts.API, - SourcePath: src.Path, - FileSize: src.Size, - ModTimeUnixNS: src.ModTime.UnixNano(), - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 2, - Name: opts.Name, - Conflict: opts.Conflict, - Parts: map[int]string{}, - } - if err := store.save(path, cp); err != nil { - t.Fatal(err) - } - if err := os.WriteFile(source, []byte("changed contents"), 0o600); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 204}}} - if err := runWithStorage(context.Background(), opts, host, &fakeStorage{}); err != nil { - t.Fatal(err) - } - if len(host.requests) != 1 || host.requests[0].URI != "zpan/api/objects/obj/uploads/sess" { - t.Fatalf("unexpected abort request: %#v", host.requests) - } - if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) { - t.Fatalf("checkpoint still exists: %v", err) - } -} - -func TestRunRejectsInvalidArgs(t *testing.T) { - if err := Run(nil, []string{}, &fakeHost{}); err == nil || !strings.Contains(err.Error(), "usage: restish zpan-upload") { - t.Fatalf("expected usage error, got %v", err) - } -} - -func TestRunReturnsHelp(t *testing.T) { - host := &fakeHost{} - if err := Run(nil, []string{"--help"}, host); err != nil { - t.Fatal(err) - } - body, ok := host.body.(map[string]any) - if !ok { - t.Fatalf("unexpected help body: %#v", host.body) - } - examples := strings.Join(body["examples"].([]string), "\n") - if !strings.Contains(examples, "RSH_PROFILE=file-manager") || !strings.Contains(examples, "--profile file-manager") { - t.Fatalf("help examples do not select profiles: %q", examples) - } - if len(host.requests) != 0 { - t.Fatalf("help should not make delegated requests: %#v", host.requests) - } -} - -func TestRunWithStorageRejectsMissingSource(t *testing.T) { - missing := filepath.Join(t.TempDir(), "missing.bin") - err := runWithStorage(context.Background(), uploadOptions{API: "zpan", Source: missing, Name: "missing.bin", Conflict: "fail", Concurrency: 1}, &fakeHost{}, &fakeStorage{}) - if err == nil || !strings.Contains(err.Error(), "missing.bin") { - t.Fatalf("expected missing source error, got %v", err) - } -} - -func TestCurrentPartsAndCompleteUploadErrors(t *testing.T) { - ops := operationSet{Presign: validOps()[1], Complete: validOps()[2]} - opts := uploadOptions{API: "zpan"} - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{{Status: 200, Body: map[string]any{ - "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{map[string]any{"partNumber": float64(1), "url": "https://storage.test/1", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}}, - }}}} - parts, err := currentParts(context.Background(), opts, host, ops, checkpoint{Mode: "single", ObjectID: "obj", SessionID: "sess", PartCount: 1}, []int{1}, nil) - if err != nil || len(parts) != 1 || parts[0].URL == "" { - t.Fatalf("expected re-signed single part, got parts=%#v err=%v", parts, err) - } - _, err = currentParts(context.Background(), opts, &fakeHost{}, ops, checkpoint{}, []int{2}, []uploadPart{{PartNumber: 1}}) - if err == nil || !strings.Contains(err.Error(), "part 2") { - t.Fatalf("expected missing initial part error, got %v", err) - } - _, err = completeUpload(context.Background(), opts, &fakeHost{}, ops, checkpoint{PartCount: 2, Parts: map[int]string{1: "etag"}}) - if err == nil || !strings.Contains(err.Error(), "cannot complete") { - t.Fatalf("expected incomplete upload error, got %v", err) - } -} - -func TestPutPartWithRetryResignsMultipart(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abcd"), 0o600); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{ - {Status: 200, Body: map[string]any{ - "uploadId": "mp", "mode": "multipart", "partSize": float64(2), "partCount": float64(2), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{}, - "parts": []any{ - map[string]any{"partNumber": float64(1), "url": "https://storage.test/1b", "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), "headers": map[string]any{}}, - }, - }}, - }} - storage := &fakeStorage{failFirst: true} - etag, err := putPartWithRetry(context.Background(), uploadOptions{API: "zpan"}, host, storage, operationSet{Presign: validOps()[1]}, checkpoint{ - ObjectID: "obj", - SessionID: "sess", - Mode: "multipart", - PartSize: 2, - PartCount: 2, - }, fileIdentity{Path: source, Size: 4}, uploadPart{PartNumber: 1, URL: "https://storage.test/1", ExpiresAt: time.Now().Add(time.Hour).Format(time.RFC3339)}) - if err != nil { - t.Fatal(err) - } - if etag == "" { - t.Fatal("expected etag after retry") - } - if len(host.requests) != 1 || !strings.Contains(host.requests[0].URI, "/uploads/sess/parts") { - t.Fatalf("expected re-sign request, got %#v", host.requests) - } -} - -func TestPutPartWithRetryResignsSingleAfterStorage403(t *testing.T) { - dir := t.TempDir() - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - host := &fakeHost{responses: []*plugin.HTTPResponseMsg{ - {Status: 200, Body: map[string]any{ - "uploadId": nil, "mode": "single", "partSize": float64(3), "partCount": float64(1), - "presignedExpiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "requiredHeaders": map[string]any{"content-type": "application/octet-stream"}, - "parts": []any{ - map[string]any{ - "partNumber": float64(1), - "url": "https://storage.test/1b", - "expiresAt": time.Now().Add(time.Hour).Format(time.RFC3339), - "headers": map[string]any{"content-type": "application/octet-stream"}, - }, - }, - }}, - }} - storage := &fakeStorage{failFirst: true} - etag, err := putPartWithRetry(context.Background(), uploadOptions{API: "zpan"}, host, storage, operationSet{Presign: validOps()[1]}, checkpoint{ - ObjectID: "obj", - SessionID: "sess", - Mode: "single", - PartSize: 3, - PartCount: 1, - }, fileIdentity{Path: source, Size: 3}, uploadPart{ - PartNumber: 1, - URL: "https://storage.test/1", - ExpiresAt: time.Now().Add(time.Hour).Format(time.RFC3339), - Headers: map[string]string{"content-type": "application/octet-stream"}, - }) - if err != nil { - t.Fatal(err) - } - if etag == "" { - t.Fatal("expected etag after retry") - } - if len(host.requests) != 1 || !strings.Contains(host.requests[0].URI, "/uploads/sess/parts") { - t.Fatalf("expected single re-sign request, got %#v", host.requests) - } - body := host.requests[0].Body.(map[string]any) - if got := body["partNumbers"].([]int); len(got) != 1 || got[0] != 1 { - t.Fatalf("unexpected re-sign body: %#v", body) - } - if storage.seen[1] != "abc" { - t.Fatalf("expected retried bytes to upload, got %#v", storage.seen) - } -} - -func TestAbortCheckpointMissingState(t *testing.T) { - dir := t.TempDir() - store, err := newCheckpointStore(dir) - if err != nil { - t.Fatal(err) - } - source := filepath.Join(dir, "file.bin") - if err := os.WriteFile(source, []byte("abc"), 0o600); err != nil { - t.Fatal(err) - } - info, err := os.Stat(source) - if err != nil { - t.Fatal(err) - } - err = abortCheckpoint(context.Background(), uploadOptions{API: "zpan", Source: source, Name: "file.bin", Conflict: "fail"}, &fakeHost{}, store, filepath.Join(dir, "missing.json"), fileIdentity{ - Path: source, Size: info.Size(), ModTime: info.ModTime(), - }, operationSet{Abort: validOps()[3]}) - if err == nil || !strings.Contains(err.Error(), "no checkpoint found") { - t.Fatalf("expected missing checkpoint error, got %v", err) - } -} - -func TestStatSourceAndPartRangeHelpers(t *testing.T) { - dir := t.TempDir() - if _, err := statSource(dir); err == nil || !strings.Contains(err.Error(), "source must be a file") { - t.Fatalf("expected directory error, got %v", err) - } - offset, size := partRange(0, 10, 1) - if offset != 0 || size != 0 { - t.Fatalf("unexpected zero-length range: %d %d", offset, size) - } - offset, size = partRange(5, 4, 2) - if offset != 4 || size != 1 { - t.Fatalf("unexpected final range: %d %d", offset, size) - } - if got := expandUploadPath("/api/objects/{id}/uploads/{uploadSessionId}", checkpoint{ObjectID: "obj", SessionID: "sess"}); got != "/api/objects/obj/uploads/sess" { - t.Fatalf("unexpected expanded path: %s", got) - } -} - -func validOps() []plugin.APIOperation { - return []plugin.APIOperation{ - { - ID: opCreate, - Method: "POST", - Path: "/api/objects", - HasBody: true, - RequestSchema: map[string]any{"properties": map[string]any{"name": map[string]any{}, "type": map[string]any{}, "size": map[string]any{}, "parent": map[string]any{}, "onConflict": map[string]any{}}}, - }, - { - ID: opPresign, - Method: "POST", - Path: "/api/objects/{id}/uploads/{uploadSessionId}/parts", - HasBody: true, - RequestSchema: map[string]any{"properties": map[string]any{"partNumbers": map[string]any{}}}, - Parameters: uploadParams(), - }, - { - ID: opComplete, - Method: "POST", - Path: "/api/objects/{id}/uploads/{uploadSessionId}/completions", - HasBody: true, - RequestSchema: map[string]any{"properties": map[string]any{"parts": map[string]any{}}}, - Parameters: uploadParams(), - }, - {ID: opAbort, Method: "DELETE", Path: "/api/objects/{id}/uploads/{uploadSessionId}", Parameters: uploadParams()}, - } -} - -func uploadParams() []plugin.APIParam { - return []plugin.APIParam{ - {Name: "id", In: "path", Required: true}, - {Name: "uploadSessionId", In: "path", Required: true}, - } -} diff --git a/cmd/restish-zpan/main.go b/cmd/restish-zpan/main.go deleted file mode 100644 index 89e32969..00000000 --- a/cmd/restish-zpan/main.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/rest-sh/restish/v2/plugin" - "github.com/saltbo/zpan/internal/restishzpan" -) - -var version = "dev" - -func main() { - plugin.Run(manifest(), commands(), runCommand) -} - -func manifest() plugin.Manifest { - return plugin.Manifest{ - Name: "zpan", - Version: version, - Description: "ZPan upload workflow commands for Restish", - RestishAPIVersion: 2, - Hooks: []string{"command"}, - NeedsAuthSecrets: false, - } -} - -func commands() []plugin.CommandDecl { - return []plugin.CommandDecl{ - { - Name: "zpan-upload", - Short: "Upload a local file to ZPan", - Long: "Upload a local file to ZPan using Restish-managed API auth and direct presigned storage PUTs.\n\n" + - "Examples:\n" + - " RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager ./photo.jpg\n" + - " RSH_PROFILE=ci restish zpan-upload --api zpan --profile ci --parent folder-id ./photo.jpg report.jpg\n" + - " RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --resume ./large.bin\n" + - " RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --abort ./large.bin", - }, - } -} - -func runCommand(command string, args []string, client *plugin.CommandClient) error { - if command != "zpan-upload" { - return fmt.Errorf("unknown command: %s", command) - } - return restishzpan.Run(os.Args[1:], args, restishzpan.NewPluginHost(client)) -} diff --git a/cmd/restish-zpan/main_test.go b/cmd/restish-zpan/main_test.go deleted file mode 100644 index 9700970e..00000000 --- a/cmd/restish-zpan/main_test.go +++ /dev/null @@ -1,167 +0,0 @@ -package main - -import ( - "bytes" - "io" - "os" - "strings" - "testing" - - "github.com/rest-sh/restish/v2/plugin" -) - -func TestManifestContract(t *testing.T) { - var out bytes.Buffer - err := plugin.WriteManifest(&out, manifest()) - if err != nil { - t.Fatal(err) - } - var manifest plugin.Manifest - if err := plugin.NewDecoder(&out).ReadMessage(&manifest); err != nil { - t.Fatal(err) - } - if manifest.Name != "zpan" || manifest.NeedsAuthSecrets || len(manifest.Hooks) != 1 || manifest.Hooks[0] != "command" { - t.Fatalf("unexpected manifest: %#v", manifest) - } -} - -func TestCommandDiscoveryContract(t *testing.T) { - var out bytes.Buffer - if err := plugin.WriteCommands(&out, commands()); err != nil { - t.Fatal(err) - } - var discovery plugin.CommandDiscoveryResponse - if err := plugin.NewDecoder(&out).ReadMessage(&discovery); err != nil { - t.Fatal(err) - } - if len(discovery.Commands) != 1 || discovery.Commands[0].Name != "zpan-upload" { - t.Fatalf("unexpected commands: %#v", discovery.Commands) - } - help := discovery.Commands[0].Long - if !strings.Contains(help, "RSH_PROFILE=file-manager") || strings.Contains(help, "--rsh-profile") { - t.Fatalf("upload help must use the delegated HTTP profile environment: %q", help) - } -} - -func TestRunCommandRejectsUnknownCommand(t *testing.T) { - err := runCommand("other", nil, plugin.NewCommandClient(bytes.NewReader(nil), io.Discard)) - if err == nil { - t.Fatal("expected unknown command to fail") - } -} - -func TestRunCommandDelegatesKnownCommand(t *testing.T) { - err := runCommand("zpan-upload", nil, plugin.NewCommandClient(bytes.NewReader(nil), io.Discard)) - if err == nil { - t.Fatal("expected delegated parser error") - } -} - -func TestMainStartupFlags(t *testing.T) { - t.Run("manifest", func(t *testing.T) { - data := captureStdout(t, []string{"restish-zpan", plugin.StartupFlagManifest}, main) - var manifest plugin.Manifest - if err := plugin.NewDecoder(bytes.NewReader(data)).ReadMessage(&manifest); err != nil { - t.Fatal(err) - } - if manifest.Name != "zpan" || manifest.RestishAPIVersion != 2 { - t.Fatalf("unexpected manifest: %#v", manifest) - } - }) - - t.Run("commands", func(t *testing.T) { - data := captureStdout(t, []string{"restish-zpan", plugin.StartupFlagCommands}, main) - var discovery plugin.CommandDiscoveryResponse - if err := plugin.NewDecoder(bytes.NewReader(data)).ReadMessage(&discovery); err != nil { - t.Fatal(err) - } - if len(discovery.Commands) != 1 || discovery.Commands[0].Name != "zpan-upload" { - t.Fatalf("unexpected commands: %#v", discovery.Commands) - } - }) -} - -func TestMainCommandErrorPath(t *testing.T) { - oldArgs := os.Args - oldStdin := os.Stdin - oldStdout := os.Stdout - defer func() { - os.Args = oldArgs - os.Stdin = oldStdin - os.Stdout = oldStdout - }() - - inR, inW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - outR, outW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Args = []string{"restish-zpan"} - os.Stdin = inR - os.Stdout = outW - - go func() { - defer inW.Close() - _ = plugin.WriteMessage(inW, plugin.InitMsg{Type: plugin.MsgTypeInit, Command: "unknown"}) - }() - - main() - - if err := outW.Close(); err != nil { - t.Fatal(err) - } - var stderr plugin.StderrDataMsg - dec := plugin.NewDecoder(outR) - if err := dec.ReadMessage(&stderr); err != nil { - t.Fatal(err) - } - if !bytes.Contains(stderr.Data, []byte("unknown command: unknown")) { - t.Fatalf("unexpected stderr: %q", stderr.Data) - } - var done plugin.DoneMsg - if err := dec.ReadMessage(&done); err != nil { - t.Fatal(err) - } - if done.ExitCode != 1 { - t.Fatalf("exit code = %d, want 1", done.ExitCode) - } - if err := outR.Close(); err != nil { - t.Fatal(err) - } - if err := inR.Close(); err != nil { - t.Fatal(err) - } -} - -func captureStdout(t *testing.T, args []string, fn func()) []byte { - t.Helper() - oldArgs := os.Args - oldStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Args = args - os.Stdout = w - defer func() { - os.Args = oldArgs - os.Stdout = oldStdout - }() - - fn() - - if err := w.Close(); err != nil { - t.Fatal(err) - } - data, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } - if err := r.Close(); err != nil { - t.Fatal(err) - } - return data -} diff --git a/cmd/scripts/package-restish-zpan.sh b/cmd/scripts/package-restish-zpan.sh deleted file mode 100755 index 6ece6e0a..00000000 --- a/cmd/scripts/package-restish-zpan.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -version="${1:?version is required}" -out_dir="${2:?output directory is required}" - -mkdir -p "$out_dir" -out_dir="$(cd "$out_dir" && pwd)" - -targets=( - "darwin amd64 tar.gz" - "darwin arm64 tar.gz" - "linux amd64 tar.gz" - "linux arm64 tar.gz" - "windows amd64 zip" - "windows arm64 zip" -) - -for target in "${targets[@]}"; do - ( - read -r goos goarch ext <<<"$target" - work_dir="$(mktemp -d)" - trap 'rm -rf "$work_dir"' EXIT - bin_name="restish-zpan" - if [ "$goos" = "windows" ]; then - bin_name="restish-zpan.exe" - fi - - GOOS="$goos" GOARCH="$goarch" CGO_ENABLED=0 \ - go build -trimpath -ldflags "-s -w -X main.version=${version}" \ - -o "${work_dir}/${bin_name}" ./restish-zpan - - archive="${out_dir}/restish-zpan_${goos}_${goarch}.${ext}" - if [ "$ext" = "zip" ]; then - (cd "$work_dir" && zip -q "$archive" "$bin_name") - else - tar -C "$work_dir" -czf "$archive" "$bin_name" - fi - ) -done diff --git a/docs/agent-skill.md b/docs/agent-skill.md deleted file mode 100644 index c9578825..00000000 --- a/docs/agent-skill.md +++ /dev/null @@ -1,51 +0,0 @@ -# ZPan Agent Skill - -ZPan v2.9 publishes a versioned Agent Skill in [skills/zpan](../skills/zpan). -The Skill teaches coding agents to use ZPan through Restish and the -`restish-zpan` upload plugin. - -## Install and Connect - -Install Restish v2.3 or later, confirm the ZPan origin, then connect the single -unified OpenAPI document: - -```sh -restish api connect zpan https://files.example.com/api/openapi.json --replace --yes -restish api sync zpan -``` - -Interactive agents use browser OAuth authorization code + PKCE through Restish. -CI and unattended jobs use the `ci` profile with `ZPAN_AGENT_API_KEY` from the -environment. - -## Upload Plugin - -Before installing the plugin, tell the user that Restish plugins are trusted -local executable code and ask them to approve the source: - -```sh -restish plugin install saltbo/zpan zpan -``` - -Every local upload goes through: - -```sh -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --parent root ./file.bin -``` - -The Skill does not implement upload transport logic. The plugin owns local file -streaming, storage response capture, retry, resume, abort, and checkpoint -cleanup. - -## Profiles - -- `reader`: read objects, shares, quota, and storage usage. -- `file-manager`: reader plus create, upload, move, copy, rename, and soft - delete objects. -- `publisher`: reader plus public share creation and revocation. -- `ci`: environment-backed Agent API key for unattended file-management jobs. - -The profile names are shortcuts for explicit scopes. They are not server-side -roles and routes do not authorize by preset name. - -Interactive Restish login uses browser OAuth authorization code + PKCE. diff --git a/docs/design/agent-authentication.md b/docs/design/agent-authentication.md index 82a14974..4eb4f08c 100644 --- a/docs/design/agent-authentication.md +++ b/docs/design/agent-authentication.md @@ -1,604 +1,204 @@ -# Agent Authentication and Authorization — Design +# External Agent Access — Design -> Status: Proposed (2026-07-28) -> Scope: Agent OAuth, API keys, workspace grants, protocol-neutral -> authorization, Restish profiles, future Agent Auth compatibility, revocation, -> and auditing +> Status: Implemented +> Scope: dynamic OAuth clients, external resource authorization, DPoP, resource +> discovery, consent, revocation, and direct uploads -## 1. Decision +## Decision -ZPan distinguishes delegated user access from unattended service access: +ZPan is an OAuth protected resource and authorization server. An Agent platform +such as FlareAuth discovers ZPan from its public API URL, dynamically registers +itself, asks the user for delegated access, and exchanges the resulting subject +grant for a DPoP-bound ZPan resource token. -| Actor | Authorization flow | Runtime credential | -|-------|--------------------|--------------------| -| Interactive Agent, local callback | Authorization code + PKCE | OAuth access/refresh tokens | -| CI or unattended service | Manual issuance | Workspace-scoped Agent API key | +ZPan does not ship or require: -This follows the current FlareAuth Restish v2 design: standard OpenAPI OAuth -metadata and `x-cli-config` let Restish connect, authorize, cache, refresh, and -revoke local tokens without a custom authorization script. +- a fixed first-party Agent OAuth client; +- an Agent-specific API key; +- Restish credential profiles in OpenAPI; +- a Restish upload plugin; +- a ZPan-specific Agent skill. -Standard Agent device authorization is deferred to v2.9.x. The existing -`zpan-cli` device flow remains a narrowly scoped compatibility bootstrap for -downloader registration and does not manufacture an Agent API key or a general -OAuth grant. Its device-issued bearer is normalized as a single-use downloader -registration credential and is consumed after successful downloader creation. +The integration contract is the public protocol surface: OAuth metadata, +OpenAPI, route authorization metadata, and structured API responses. -Anonymous upload and preview-and-claim are explicitly excluded. Every Agent file -operation belongs to an existing user-authorized workspace from the beginning. +## Discovery -OAuth and API keys are v2.9 credential adapters, not the file API's identity -model. Both resolve to a protocol-neutral principal, scope set, bound workspace, -and audit actor. A future Agent Auth verifier plugs into that same boundary. +Given the exact resource URL `https://zpan.example/api`, a client can discover: -## 2. Why OAuth for Interactive Agents +| Contract | Path | +|---|---| +| API, OpenAPI, and workflow discovery links | `/api` | +| OpenAPI | `/api/openapi.json` | +| Arazzo workflows | `/api/workflows.arazzo.json` | +| Protected resource metadata | `/.well-known/oauth-protected-resource/api` | +| Authorization server metadata | `/.well-known/oauth-authorization-server/api/auth` | +| Dynamic client registration | `/api/auth/oauth2/register` | -Interactive Agents act on behalf of a signed-in human. OAuth gives that -relationship first-class semantics: +Protected-resource metadata identifies the exact `/api` audience and the +authorization server. Authorization-server metadata advertises authorization +code, refresh token, JWT bearer, token exchange, dynamic registration, and +DPoP capabilities. -- short-lived access tokens -- refresh-token rotation and revocation -- explicit client identity -- explicit resource scopes -- browser consent -- authorization code + PKCE for public native clients -- no browser-cookie or raw-token copy/paste +OpenAPI remains tool-neutral. It contains no `x-cli-config`, built-in client ID, +credential environment variable, or executable helper. Agent-callable +operations publish their exact runtime requirements through `x-zpan-auth`. +`GET /api/oauth-resource-scopes` is a public scope catalog whose OpenAPI +operation carries the standard OAuth scope declaration used by external +resource registries. Keeping the business operations themselves unbound avoids +selecting a built-in Restish OAuth profile before a delegated-credential hook +can provide the resource token. Browser and administration operations retain +their normal cookie/bearer declarations. -Restish v2 natively supports authorization code + PKCE. It caches OAuth tokens -separately from HTTP responses, refreshes them, retries once after a `401`, and -supports explicit logout. +The API resource response publishes OpenAPI through an RFC 8631 `service-desc` +link and its Arazzo 1.1 description through a typed `describedby` link. The +OpenAPI document also links the Arazzo document through `externalDocs`. A +controller can therefore discover both contracts from the exact resource URL +without assuming a ZPan-specific path. -Restish v2.3 uses port `8484` and path `/callback` by default for browser -authorization-code callbacks. Restish sends `localhost` in the authorization -request; ZPan also registers the equivalent `127.0.0.1` loopback callback for -clients and tooling that distinguish loopback hostnames. +The Arazzo document defines separate prepare, re-presign, complete, and abort +workflows backed by stable OpenAPI operation IDs. Preparing an upload returns +the runtime descriptor for the direct storage transfer. This split is +intentional: an Arazzo operation target comes from its source OpenAPI server, +while a presigned storage URL is an arbitrary absolute URL generated at +runtime. The controller executes those PUT requests from the returned +descriptor, then supplies their ETags to the completion workflow. -## 3. Why API Keys Still Exist +## Dynamic Registration and Administration -CI and unattended services are different: no human is present to complete -consent or periodically reauthorize. The existing Better Auth API-key -foundation already supplies: +The OAuth provider accepts RFC 7591-style dynamic client registration with PKCE. +Each controller registers its own: -- hashed credential storage -- named and independently revocable keys -- expiry and enabled state -- rate-limit state -- resource/action permissions -- workspace scope in metadata -- owning user reference -- per-key audit attribution +- client name and URI; +- callback URI; +- grant and response types; +- token endpoint authentication method; +- JWKS or JWKS URI when JWT bearer exchange is used; +- requested scopes. -An Agent API key is therefore the pragmatic v2.9 service credential. It is -created manually and stored in a CI secret. Future workload identity federation -can replace it without changing the canonical scope and policy model. +The server assigns the client ID. No client identity or callback is hard-coded +in ZPan. -## 4. Stable Upgrade Boundary +Administrators can inspect dynamically registered applications in the existing +authentication settings. This first version does not add application approval: +registration is immediately usable, but user consent is still mandatory before +workspace access is granted. System/reference clients are not presented as +external registered applications. -ZPan separates four concepts: +## Consent and Workspace Binding -| Concept | Responsibility | -|---------|----------------| -| Credential adapter | Validate OAuth, API key, or future Agent JWT | -| Principal | Identify the authorizing user, credential actor, and bound workspace | -| Scope and policy authorization | Intersect credential scopes with current workspace authority | -| Use case | Perform the file operation without knowing the credential protocol | +Authorization code + PKCE creates a user-controlled subject grant. The consent +page resolves the registered client record and displays its real name, callback, +requested scopes, ZPan instance, selected workspace, and grant lifetime. -Conceptually, an Agent-facing principal contains: +Each consent is bound to: -```ts -type AgentPrincipal = { - kind: 'delegated-user' | 'service' | 'agent' - userId: string - orgId: string - scopes: ReadonlySet - actor: { - type: 'agent_oauth' | 'api_key' | 'agent' - id: string - } -} -``` +- the signed-in user; +- the dynamically registered client; +- exactly one workspace; +- the approved ZPan resource scopes. -The exact TypeScript representation may remain a discriminated union, but -routes must authorize scopes rather than require a concrete `kind`. -Credential-specific fields remain available for diagnostics and revocation; -they do not select business behavior. +The request cannot replace that workspace with a query or body field. Team +membership and role checks still apply. Revoking a consent removes its access +tokens, revokes its refresh tokens, and deletes the consent. The Agent Access +page lists the real client name and workspace for every current-user grant. -The versioned ZPan Agent Skill is published under `skills/zpan` and summarized -in [ZPan Agent Skill](../agent-skill.md). It consumes this authorization model -through Restish profiles instead of adding a second credential or upload -protocol. +## External Resource Token Flow -This boundary deliberately avoids two migration traps: +FlareAuth-style controllers use three credentials with separate purposes: -- File routes must not treat an OAuth bearer as an unrestricted browser user. -- Agent API keys must not become ZPan's proprietary Agent identity, - registration, signing, or capability-grant protocol. +1. A user-approved ZPan subject token represents the connected account. +2. A JWT bearer assertion identifies the Agent/controller actor and mints a + short-lived actor token. +3. OAuth token exchange combines subject and actor tokens for the exact ZPan + `/api` audience and requested scopes. -With this boundary, adopting Agent Auth later adds a verifier, persistence, -approval UI, and management UI. It does not change operation IDs, the unified -OpenAPI document, Skill/plugin workflows, workspace authorization, or file use -cases. +The exchanged access token is a JWT containing the user, workspace, +`zpan_actor`, delegated actor (`act`), audience, scopes, client ID, expiry, and +JTI. API requests use `Authorization: DPoP` plus a proof bound to the method, +URL, access token, and Agent key. ZPan verifies issuer, audience, signature, +expiry, scopes, DPoP proof, and JTI revocation. -## 5. System-Managed OAuth Client +Revoking an exchanged JWT stores its JTI until token expiry. The resource API +rejects revoked tokens. Opaque-token compatibility and fixed-client grant +assertions are intentionally not part of this path. -Create a built-in public native application such as `zpan-agent`. +## Scope Model -Properties: +Resource scopes use stable `:` names. The external Agent scope +catalog includes: -- system-managed and not editable/deletable -- public client; no client secret -- authorization code grant with PKCE -- loopback redirect URIs `http://localhost:8484/callback` and - `http://127.0.0.1:8484/callback` -- refresh-token support through `offline_access` -- Agent scopes only - -Dynamic client registration is not required in v2.9. One first-party client is -enough for the versioned ZPan Skill and Restish integration. - -The authorization server publishes discovery metadata. Better Auth OAuth -Provider 1.6.x mounts the runtime endpoints below the Better Auth base path: - -| Endpoint | Path | -|----------|------| -| Authorization | `/api/auth/oauth2/authorize` | -| Token and refresh | `/api/auth/oauth2/token` | -| Revocation | `/api/auth/oauth2/revoke` | -| Introspection | `/api/auth/oauth2/introspect` | -| UserInfo | `/api/auth/oauth2/userinfo` | -| Consent | `/api/auth/oauth2/consent` | -| Continue login flow | `/api/auth/oauth2/continue` | - -Because Better Auth is mounted at `/api/auth`, ZPan forwards the required -well-known authorization-server and OIDC metadata at root locations and also -publishes protected-resource metadata for `/api`. - -## 6. Workspace Grant - -OAuth scopes describe allowed operation classes, but a ZPan grant also needs a -resource boundary: exactly one workspace. - -The consent record binds: - -- authorization/grant ID -- user ID -- OAuth client ID -- workspace `orgId` -- approved scopes -- created, expiry, revoked, and last-used state - -Access/refresh tokens resolve to that grant. The API does not derive workspace -from the user's mutable active-organization session. - -Effective authorization is: - -```text -credential is valid -AND grant/key allows the requested action -AND request targets the bound workspace -AND authorizing user still has the required workspace role -``` - -For a team workspace, relevant requests recheck current membership and role. -Removing the user or reducing their role immediately reduces Agent access. - -For a personal workspace, authorization verifies that the organization is the -authorizing user's personal organization. The current API-key branch in -`requirePermission` lacks this personal-ownership fallback and must add it. - -Request bodies and query parameters cannot override the credential's workspace. -A mismatch is `403`, never a fallback to another active or personal workspace. - -## 7. Scope Model - -ZPan defines one canonical authorization vocabulary for scoped credentials. -OAuth grants, Agent API keys, and future Agent credentials resolve to the same -scope set. A browser cookie is a first-party, unbounded credential: it does not -need a role-to-scope mapping, but it still passes the route's declared -workspace, minimum-role, ownership, and resource policies. There is no -separately named permission vocabulary and no `Scope -> Permission` mapping. - -Scope names follow: - -```text -: -``` - -Rules: - -- lowercase ASCII only; -- plural domain resource names such as `objects`, `shares`, and `tasks`; -- a small shared action vocabulary such as `read`, `create`, `update`, and - `delete`; -- business operations rather than HTTP methods; -- no wildcard semantics or access implied by string prefixes; -- no `zpan:` prefix, because token issuer and audience already identify the - ZPan API; -- published scope meanings are stable and must never silently broaden. - -Initial Agent-grantable scopes are: - -| Scope | Intended operations | -|-------|---------------------| +| Scope | Authority | +|---|---| | `objects:read` | List, inspect, and download objects | -| `objects:create` | Create folders, upload drafts, upload-part signatures, and complete uploads | -| `objects:update` | Rename, move, and copy objects within the authorized workspace | +| `objects:create` | Create folders and direct-upload sessions | +| `objects:update` | Rename, move, and copy objects | | `objects:delete` | Soft-delete objects | -| `shares:read` | List and inspect shares | +| `shares:read` | Inspect shares | | `shares:create` | Create public shares | | `shares:delete` | Revoke shares | | `quota:read` | Inspect workspace quota | +| `storage-usage:read` | Inspect workspace storage usage | | `tasks:read` | Inspect task state | -Protocol scopes such as `openid` and `offline_access` retain their standard -OAuth/OIDC meaning. They are not ZPan route permissions. - -Every protected route declares the minimum scopes required to perform its -operation. It does not enumerate the roles, presets, credential types, broad -scopes, or Agent classes allowed to call it. For example: - -```ts -auth: { - allOf: ['objects:delete'], - workspace: 'required', -} -``` - -Scope authorization is necessary but not sufficient. Workspace membership, -resource ownership, resource state, quota, and other request-specific -constraints remain explicit policy checks. - -The consent and API-key UIs can present Reader, File manager, and Publisher -shortcuts. A shortcut expands to an explicit set of scopes; it is not itself a -scope, and routes never reference its name. Destructive and public-sharing -scopes remain separately selectable. - -No Agent-grantable scope implies admin, billing, entitlement, membership, -credential management, WebDAV, image-hosting configuration, or downloader -registration. Those protected APIs still use the same route scope mechanism but -are excluded from the Agent credential grant policy. - -## 8. Authorization Code + PKCE - -This is the default Restish flow: - -1. Skill identifies and confirms the ZPan origin and Restish API name. -2. Skill requires Restish v2. -3. `restish api connect` discovers `/api/openapi.json` and applies its - server-published OAuth binding. -4. The first safe Agent operation starts browser authorization. -5. Restish creates a PKCE verifier/challenge and listens on its loopback - callback. -6. User signs in, selects one workspace, reviews scopes, and approves or denies. -7. Restish exchanges the authorization code and caches the tokens. -8. Later commands refresh tokens without exposing them to the Agent response. - -The consent page displays the Agent client, instance hostname, workspace, -requested scopes, destructive/public side effects, and grant lifetime. - -Restish's `--rsh-no-browser` may be used when a browser cannot be opened but the -authorization-code callback can still be completed manually. - -## 9. Deferred Agent Device Authorization - -Standard Agent device authorization is a v2.9.x follow-up. It must issue tokens -for the same workspace grant and scope model as authorization code + PKCE, not a -broad Better Auth session token. The existing Better Auth device plugin remains -restricted to the legacy `zpan-cli` downloader bootstrap until that follow-up. - -## 10. Agent API-Key Issuance - -Manual API-key creation is the initial CI path: - -1. User opens Agent Access settings. -2. User selects a workspace. -3. User names the Agent or environment. -4. User selects permissions and expiry. -5. Server verifies current authority and creates an `agent` API key. -6. The plaintext key is shown once. - -New Agent keys never use `scope.mode = "user-workspaces"`. One key authorizes one -workspace. Expiry is required, defaults to 90 days, and cannot exceed one year. -Use one key per CI environment. Personal workspace owners and team -owners/admins can manage Agent keys; team editors cannot issue credentials. - -The UI lists name, workspace, permission summary, creation, expiry, last use, -and status. Revocation is immediate. Only active keys can rotate. Rotation -creates a new key and never reveals or mutates the old secret; expired and -revoked keys are terminal, so the user creates a new key instead. - -## 11. OpenAPI and Restish v2 Binding - -ZPan publishes one unified `/api/openapi.json`. It defines: - -- relative server URL for Agent API routes -- OAuth authorization-code security scheme with Agent scopes -- Bearer alternative for Agent API keys -- stable operation IDs and structured errors -- document-level Restish v2 `x-cli-config` profiles - -Conceptual configuration: - -```yaml -components: - securitySchemes: - agentOAuth2: - type: oauth2 - flows: - authorizationCode: - authorizationUrl: /api/auth/oauth2/authorize - tokenUrl: /api/auth/oauth2/token - scopes: - objects:read: Read files and folders - objects:create: Upload files and create folders - objects:update: Rename, move, and copy files and folders - objects:delete: Delete files and folders - agentApiKey: - type: http - scheme: bearer - -x-cli-config: - profiles: - default: - credentials: - agentOAuth2: - params: - client_id: zpan-agent - scopes: openid offline_access objects:read quota:read - redirect_path: /callback - file-manager: - credentials: - agentOAuth2: - params: - client_id: zpan-agent - scopes: openid offline_access objects:read objects:create objects:update objects:delete quota:read tasks:read - redirect_path: /callback -``` - -The real document also provides a Publisher shortcut. Reader is the default, so -connecting the API does not silently request write or share permission. These -profile names only expand to explicit scopes; routes never reference them. - -A separate environment-backed profile selects `agentApiKey` for CI. No Agent -device-code profile is published in v2.9. - -The OpenAPI document never contains credentials or configures an executable -credential helper. Skill instructions select a named Restish profile rather -than assuming OAuth or a particular environment-variable name. This keeps -operation workflows unchanged if a future local profile uses an Agent Auth -signer. - -All formal API operations remain visible to Restish CLI generation. Declared -scopes and dynamic policy decide whether a credential may call them. Only -browser callbacks and internal-only endpoints are hidden from CLI generation. -MCP additionally ignores authentication, administration, and credential -management operations and keeps write tools disabled by default. ZPan does not -maintain a second Agent operation allowlist. - -## 12. Restish Upload Plugin - -`restish-zpan` is a Restish v2 command plugin shipped from this repository. It -contributes `restish zpan-upload` and is installed with: - -```sh -restish plugin install saltbo/zpan zpan -``` - -The plugin uses Restish delegated HTTP for ZPan draft, part re-sign, complete, -and abort operations, preserving the selected profile, OAuth/API-key -authentication, TLS, and normalized output. With Restish v2.3 command plugins, -the host profile is selected through `RSH_PROFILE` while the plugin's matching -`--profile` selects spec validation and checkpoint identity. It streams local file sections -directly to presigned S3 URLs with bounded concurrency, retry, ETag capture, -resume checkpoints, and idempotent completion. - -The plugin never asks Restish for authentication secrets. Checkpoints contain -only safe API/profile identity, upload session and file identity, and completed -part/ETag state; they contain no token, cookie, API key, or presigned URL. The -Skill invokes this command and never implements multipart state itself. - -## 13. Route Authorization - -Both credential types enter a shared Agent authorization boundary. - -For OAuth: - -1. validate/introspect the access token; -2. require the built-in Agent client ID and the route's required scopes; -3. resolve user and bound workspace grant; -4. recheck current workspace authority. - -For API keys: - -1. verify key, expiry, revocation, rate limit, and owner status; -2. require `configId = "agent"` and the route's required scopes; -3. resolve bound workspace metadata; -4. recheck current workspace authority. - -Both then invoke the same use case with the bound `orgId` and a typed audit -actor. Routes use shared permission middleware instead of session-only or -principal-specific checks. The shared middleware accepts the internal principal -contract, so tests for protected operations do not need to know how the -principal authenticated. - -Special considerations: - -- A presigned upload URL may remain usable briefly after credential revocation - because S3 validates the signature independently. Keep presigned lifetimes - short. -- Upload completion and new part presigning always reauthorize. -- Issuing a new download URL requires object-read permission. -- Listing and task responses remain workspace-filtered and paginated. -- Share creation requires `shares:create` even when the Agent can read the - object. - -## 14. Audit and Management - -Audit records distinguish resource ownership from the actor that initiated the -operation. OAuth actions record an `agent_oauth` actor with grant/client -attribution. API-key actions retain `api_key` with the key ID as `actorRef`. -Both record the authorizing user, workspace, action, target, outcome, and safe -metadata. A future Agent Auth adapter records `agent` with its Agent ID while -retaining the delegated user as resource owner. - -Agent Access settings show two sections: - -- delegated OAuth grants, with client, workspace, scopes, last use, and revoke; -- service API keys, with name, workspace, permissions, expiry, last use, and - revoke/rotate for active keys. - -Revoking a delegated grant invalidates its refresh tokens and prevents new -access tokens. Short access-token lifetime bounds any validation-cache delay. -`restish api auth logout` clears local cached tokens; server-side revoke remains -available when a device is lost. - -Credentials are never recorded or redisplayed. - -## 15. Current Code Gaps - -- ZPan has bearer sessions and device authorization but is not yet an OAuth - authorization server with Agent resource scopes and workspace grants. -- Legacy device authorization validates only `zpan-cli` with - `downloader:register` and yields only a single-use downloader bootstrap - credential. -- `shared/api-key-templates.ts` lacks an Agent template. -- `server/http/objects.ts` rejects ordinary API-key principals. -- authenticated shares, quota, trash, and several task routes require a user - session instead of a permission. -- the current principal model and `requireAuth` helper encourage routes to - branch on identity kind; all protected routes need shared scope declarations - and a protocol-neutral authorization boundary. -- API-key authorization needs the personal-workspace ownership check. -- the unified OpenAPI document lacks operation security and CLI/MCP annotations; -- the current upload contract lacks explicit part descriptors, robust re-sign, - expiry, idempotent completion, and a Restish command plugin. - -These authorization-boundary changes require integration tests for OAuth and -API-key success, missing scope/permission, wrong workspace, wrong client, role -reduction, expiry, revocation, and personal/team spaces. - -## 16. Agent Auth Protocol Compatibility - -The [Agent Auth Protocol](https://agentauthprotocol.com/) is a strong long-term -fit because it gives every Agent a cryptographic identity, scoped capability -grants, an independent lifecycle, and per-Agent audit attribution. The -[Better Auth Agent Auth plugin](https://better-auth.com/docs/plugins/agent-auth) -also provides discovery, device/CIBA approval, short-lived signed JWTs, replay -protection, OpenAPI/MCP adapters, and lifecycle events. - -It is not the required v2.9 production path: - -- the protocol is currently `v1.0-draft`, and the plugin documentation marks - the implementation as unstable; -- Restish does not natively implement Agent Auth request signing; -- production Cloudflare Workers need distributed JTI replay storage rather than - the plugin's default in-memory cache; -- custom REST `location` handlers must validate grants and constraints in the - shared authorization layer; -- converting the full ZPan OpenAPI document into capabilities would expose too - much surface. - -The intended future adapter is: - -```text -Agent Auth JWT - -> verify signature, audience, expiry, and JTI - -> resolve delegated user and approved workspace - -> normalize capability grants to the canonical Scope set - -> create protocol-neutral principal and `agent` audit actor - -> run existing scope and policy middleware and use case -``` - -The effective permission remains: - -```text -Agent Auth capability grant -AND authorizing user's current workspace role -AND request targets the approved workspace -AND resource-specific policy allows the operation -``` - -Expected change surface: - -| Remains unchanged | Added for Agent Auth | -|-------------------|----------------------| -| Unified OpenAPI and operation IDs | Agent/host/grant/approval persistence | -| ZPan Skill and upload-plugin workflows | Agent JWT credential adapter | -| File, share, quota, and task use cases | Approval and Agent-management UI | -| Route scope requirements and workspace policies | Distributed JTI replay storage | -| Presigned direct-to-S3 upload sequence | Restish signing profile/helper | - -Agent Auth does not replace role, quota, storage, share, or ownership checks. -Autonomous/anonymous Agent registration and later claim are outside the current -product boundary; an initial integration supports delegated Agents only. - -Restish remains the operation client. Until it supports Agent Auth natively, a -future profile may use its -[external-tool authentication](https://rest.sh/docs/recipes/use-external-tool-auth/) -to invoke the official Agent Auth client or a minimal reviewed signer. This is -an authentication adapter, not a standalone ZPan CLI. The unified OpenAPI -operations and Skill workflows remain unchanged. - -Before promotion from preview to the default interactive flow, require: - -- a maintained Restish signing integration or native Agent Auth support; -- distributed JTI replay protection on Workers and an equivalent Node path; -- cross-runtime tests for registration, approval, execution, replay, revoke, - role reduction, and workspace isolation; -- an explicit Agent-grantable scope catalog rather than automatic authorization - for every operation in the unified OpenAPI document; -- acceptable upstream protocol and package stability. - -## 17. Rejected Alternatives - -### Device Approval Mints an API Key - -Rejected because device approval must eventually issue the same delegated OAuth -grant as authorization code + PKCE. Minting an API key would replace that -short-lived and refreshable lifecycle with a proprietary exchange. - -### API Key for Every Agent - -Rejected because interactive user delegation benefits from consent, short access -tokens, refresh-token revocation, and client identity. API keys remain -appropriate for CI and unattended services. - -### OAuth for CI by Pretending a User Is Present - -Rejected because unattended automation should not depend on a human refresh -grant. Use a scoped API key until workload identity federation is available. - -### Agent Auth as the Only v2.9 Credential - -Deferred because the protocol and current plugin remain unstable and Restish -needs an external signer. The compatibility boundary is included now; -production adoption can follow without making v2.9 depend on a draft protocol. - -### Browser Cookies - -Rejected because they are broad, mutable user-session credentials and unsafe to -copy into Agent environments. - -### One Credential Across All User Workspaces - -Rejected because it makes compromise impact, audit interpretation, role changes, -and revocation unnecessarily broad. - -### Anonymous Upload and Claim - -Deferred outside v2.9. File storage normally implies persistence and an -accountable quota owner. Revisit only if ZPan deliberately builds a -try-before-login artifact-delivery product. - -## 18. Future Evolution - -- Better Auth Agent Auth compatibility adapter, initially behind a feature flag -- Delegated Agent approval and per-Agent revoke/management UI -- Distributed JTI and Agent-key cache storage for Cloudflare Workers -- Workload identity federation for supported CI providers -- Dynamic client registration for trusted third-party Agent platforms -- Standard Agent device authorization using the same workspace grant and scopes -- Rich Authorization Requests if third-party clients need standardized - workspace selection in the authorization request -- HTTP Message Signatures / Web Bot Auth for additional Agent-operator - attribution, never workspace authorization +Administrative, billing, credential-management, WebDAV, downloader bootstrap, +and purge authority are not grantable through this catalog. + +OAuth is a credential adapter, not a business-logic fork. Middleware resolves a +protocol-neutral principal, bound workspace, scope set, and audit actor before +calling the same file use cases used by other authenticated clients. + +## Self-Describing Direct Upload + +File bytes continue to bypass ZPan and go directly to S3-compatible storage. +The create-object response is the upload workflow contract; an Agent does not +need a plugin or skill to infer hidden follow-up steps. + +The response includes: + +- upload ID and object draft; +- ordered part descriptors with part number, byte offset, byte length, method, + presigned URL, and required headers; +- a `workflow` object describing the upload request; +- the exact complete, re-presign, and abort operation IDs, methods, and paths; +- instructions to preserve each upload response ETag and submit + `{ partNumber, etag }` to completion. + +An Agent follows this generic sequence: + +1. Call `createObject` with file name, size, type, and workspace context. +2. Split the local file according to each returned `offset` and `length`. +3. `PUT` each byte range to its returned presigned URL and retain the response + ETag. +4. If a URL expires, call the returned re-presign operation for only the + unfinished part numbers. +5. Call the returned complete operation with all part numbers and ETags. +6. On an intentional cancellation, call the returned abort operation. + +Presigned URLs are bearer capabilities with short lifetimes. They must not be +logged, cached in checkpoints, or sent through the controller. Completion and +re-presigning re-enter ZPan authorization and workspace checks. + +## Compatibility Boundary + +The legacy `zpan-cli` device flow remains limited to downloader registration. +Ordinary human-created API keys remain available for their existing product +uses, but there is no Agent API-key template or Agent key management UI. + +Future client-registration approval can be added around dynamically registered +client records without changing resource discovery, consent, token exchange, +OpenAPI, upload responses, or file use cases. + +## Acceptance + +The integration is complete when a generic FlareAuth/Restish controller can: + +1. discover ZPan from `/api`; +2. dynamically register and appear in administrator settings; +3. create a user-visible authorization request; +4. obtain a DPoP resource token after consent; +5. discover file operations from OpenAPI and upload workflows from Arazzo; +6. upload bytes and complete the upload using the Arazzo and returned runtime + workflow data; +7. list, read, and rename the resulting object; +8. lose access after grant or JWT revocation. diff --git a/docs/restish-zpan.md b/docs/restish-zpan.md deleted file mode 100644 index 5e1748d5..00000000 --- a/docs/restish-zpan.md +++ /dev/null @@ -1,47 +0,0 @@ -# Restish ZPan Upload Plugin - -`restish-zpan` contributes the `restish zpan-upload` command. It uses Restish -profiles for ZPan API calls and streams file bytes directly from disk to -presigned storage URLs. - -The companion [ZPan Agent Skill](agent-skill.md) selects when to use generated -Restish commands and when to invoke this plugin. The Skill does not implement -multipart upload behavior itself. - -## Install - -Restish plugins are trusted local executable code. Agents must explain that -trust boundary and get explicit user approval for the `saltbo/zpan` source -before installing: - -```bash -restish plugin install saltbo/zpan zpan -``` - -## Usage - -```bash -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager ./photo.jpg -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --parent albums ./photo.jpg cover.jpg -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --resume ./large.bin -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --abort ./large.bin -``` - -The plugin validates the connected ZPan OpenAPI operations before uploading: -`createObject`, `presignObjectUploadParts`, `completeObjectUpload`, and -`abortObjectUpload`. - -Control-plane calls are delegated to Restish so host configuration, auth, TLS, -cache policy, and output formatting stay host-owned. Presigned storage PUTs use -native Go HTTP because file bytes and presigned URLs must not cross the plugin -CBOR channel. - -Local checkpoints are written with mode `0600` under the user cache directory. -They contain API/profile identity, source file identity, destination identity, -the ZPan object/session IDs, part size/count, and completed part ETags. They do -not contain credentials, cookies, presigned URLs, or file bytes. - -Restish v2.3 command plugins receive the delegated HTTP profile through -`RSH_PROFILE`; use it for the host credential selection. The plugin's -`--profile` value is separately used for spec validation and checkpoint -identity. diff --git a/docs/roadmap/v2.9.md b/docs/roadmap/v2.9.md index 3cad1390..fdcaee2e 100644 --- a/docs/roadmap/v2.9.md +++ b/docs/roadmap/v2.9.md @@ -1,409 +1,146 @@ -# v2.9 — Agent Access +# v2.9 — External Agent Access -Make ZPan operable by coding agents, scripts, and CI without maintaining a -separate ZPan CLI. v2.9 publishes a stable Agent-facing API contract and teaches -agents to use it through Restish v2 plus a ZPan Skill. +## Goal -The authentication decision follows the current FlareAuth + Restish v2 pattern: - -- **Interactive Agent:** OAuth authorization code + PKCE. -- **Unattended automation:** workspace-scoped Agent API key. - -Standard Agent device authorization is deferred to v2.9.x. The existing -`zpan-cli` device flow remains only as a compatibility bootstrap for downloader -registration. Its bearer is a single-use downloader registration credential, not -a browser session or general Agent credential. - -OAuth grants and API keys are separate because they represent different actors: -delegated user access versus a service credential. An Agent never receives a -browser cookie or an unrestricted user session. Anonymous uploads, provisional -workspaces, and claim flows are not part of v2.9. - -The file API does not depend on either credential format. Both flows resolve to -a protocol-neutral principal and the same scope and workspace-policy checks. This is -an explicit compatibility boundary for adding Agent Auth Protocol later without -redesigning the OpenAPI operations, Skill, upload plugin, or file use cases. - -The detailed model is in -[Agent Authentication and Authorization](../design/agent-authentication.md). -The published workflow package is [ZPan Agent Skill](../agent-skill.md), with -the installable Skill source under `skills/zpan`. +Make ZPan a self-describing OAuth resource that generic Agent controllers can +discover and operate without a ZPan-specific skill, Restish profile, or upload +plugin. ## Product Boundary -- **Community** gets the unified OpenAPI contract, interactive OAuth, Agent API - keys, Restish setup, the ZPan upload plugin, and the ZPan Skill. -- **Pro / Business** may raise API and automation limits, but paid tiers do not - gate ordinary authenticated file automation. -- Desktop sync clients do not use Restish as their engine. They continue to use - sync-specific APIs and OS integrations in their own projects. +ZPan owns: -## Why Restish + Skill +- OAuth protected-resource and authorization-server metadata; +- dynamic OAuth client registration; +- administrator visibility of registered applications; +- user consent and workspace-bound grants; +- JWT bearer actor authentication and OAuth token exchange; +- DPoP-bound resource tokens and revocation; +- scope-aware file APIs, Arazzo workflows, and structured direct-upload + instructions. -Restish v2 already generates commands from OpenAPI, supports OAuth and API-key -profiles, caches and refreshes OAuth tokens, emits machine-readable output, and -provides retries and pagination. Its official MCP plugin can expose an API as -tools, with write operations disabled by default. - -ZPan therefore owns: - -- one unified OpenAPI document with operation-level scope declarations -- server-published Restish v2 authentication bindings -- a `restish-zpan` command plugin for streaming and resumable multipart uploads -- a ZPan Skill that explains safe workflows and selects the right Restish surface -- the server-side authentication and authorization contract - -The plugin is a narrow Restish extension, not a standalone ZPan CLI. Restish -continues to own command parsing for generated API operations, profiles, -credential storage, authentication, and output formatting. - -Baseline Restish version: **v2.3 or later**. v2.3 is selected because it includes -the current plugin system, OAuth flows, OpenAPI credential binding, and official -MCP integration. Restish's embedding API can build a branded CLI, but that -option is deliberately not used. - -References: - -- [OpenAPI CLI integration](https://rest.sh/docs/reference/openapi-cli-integration/) -- [Authentication](https://rest.sh/docs/guides/authentication/) -- [Automation](https://rest.sh/docs/guides/automation/) -- [MCP plugin](https://rest.sh/docs/plugins/mcp/) -- [Command plugins](https://rest.sh/docs/plugins/command-plugins/) +The external controller owns Agent identity, approval of Agent access, delegated +credential injection, and tool orchestration. ZPan does not ship a fixed Agent +client or an Agent API-key product. ## Deliverables -### Protocol-Neutral Authorization Boundary +### Discovery and Dynamic Registration -Authentication adapters resolve credentials into an internal principal before -any file, share, quota, or task authorization: +- Publish the exact `/api` resource audience. +- Link `/api/openapi.json` from the resource response. +- Publish protected-resource and authorization-server metadata. +- Advertise and accept dynamic client registration with PKCE. +- Show dynamically registered applications and their metadata to + administrators. +- Do not require application approval in the first release. -```text -Browser session ────┐ -OAuth access token ─┼─> principal + actor + granted scopes ─> scope + policy check -Agent API key ──────┘ -``` +### Consent and Grants -Every protected route declares the minimum scopes required for its operation, -such as `objects:read`, `objects:create`, and `shares:create`. Routes do not -declare which credential types, roles, presets, or Agent classes may call them. -Authentication adapters normalize browser sessions, OAuth tokens, API keys, and -future Agent credentials into the same authorization context. Each adapter -supplies: +- Resolve the dynamic client name and callback at consent time. +- Bind every consent to one user, client, workspace, and explicit scope set. +- Let the user switch among accessible workspaces before approval. +- List grants with their actual registered application names. +- Revoke the selected consent plus its access and refresh token family. -- the authorizing `userId` -- the applicable workspace boundary, including one bound `orgId` for Agent - grants and Agent API keys -- the granted scopes -- a typed actor for audit attribution -- credential expiry and revocation state +### External Resource Tokens -Effective permission is always the intersection of granted scopes, the -authorizing user's current workspace role, and resource-specific rules. Adding -a future `agent-jwt` adapter must therefore require no changes to route scope -requirements, file use cases, or operation IDs. +- Accept JWT bearer assertions from registered clients with JWKS. +- Mint short-lived actor tokens. +- Exchange a user subject token and actor token for the exact ZPan API audience. +- Require DPoP proofs for token exchange and resource requests. +- Include workspace and delegated actor claims in resource JWTs. +- Support JTI-based access-token revocation. ### Unified OpenAPI -Publish only `/api/openapi.json`. Every formal operation remains available to -Restish command generation; declared scopes and dynamic policy determine -whether a credential may invoke it. Browser callbacks and internal-only -endpoints are excluded from CLI generation. MCP additionally ignores -authentication, administration, and credential-management operations and keeps -write tools disabled by default. +- Keep one OpenAPI document for browser, API, and Agent consumers. +- Publish stable operation IDs and `x-zpan-auth` resource-scope requirements. +- Keep administrative routes protected by their normal security declarations. +- Do not publish Restish profiles, client IDs, secrets, environment-variable + bindings, or executable credential helpers. +- Publish a public OAuth resource-scope catalog with standard OAuth security + declarations while keeping Agent operations bound only by `x-zpan-auth`, so + no built-in client or credential profile is selected. -Every protected operation declares its scope and workspace/role policy at the -route. The same declaration drives runtime enforcement and OpenAPI security, so -there is no second Agent operation allowlist to drift. Every machine-facing -operation needs a stable `operationId`, bounded pagination, documented -idempotency, structured errors, and suitable examples. +### Discoverable API Workflows -The document declares both allowed authentication alternatives: +- Publish an Arazzo 1.1 JSON document for prepare, re-presign, complete, and + abort upload workflows. +- Advertise it from the resource URL with a typed `describedby` Link and from + OpenAPI through `externalDocs`. +- Reference stable OpenAPI operation IDs so a controller can resolve required + scopes and invoke protected steps through its normal OpenAPI client. +- Keep the response-provided upload descriptor authoritative for presigned + storage PUTs because their absolute URLs are generated at runtime. -- OAuth 2.0 authorization code with PKCE and Agent resource scopes -- HTTP Bearer authentication for a manually issued Agent API key +### Self-Describing Direct Upload -It also publishes Restish v2 Reader, File manager, and Publisher convenience -profiles for the built-in public native client. These names expand to explicit -scope sets and are never referenced by routes. Reader is the default; broader -profiles request their scopes explicitly. No secret is embedded in the -document. +`createObject` returns everything a generic Agent needs: -The existing document currently exposes roughly 145 operation IDs without -operation security or `x-mcp-ignore` annotations. v2.9 adds those declarations -without creating `/api/openapi.agent.json`. +- upload and object identifiers; +- part number, byte offset, byte length, HTTP method, presigned URL, and required + headers for every part; +- explicit instructions to retain each response ETag; +- complete, re-presign, and abort operation IDs, methods, and paths. -### ZPan Skill +The Agent uploads bytes directly to S3-compatible storage, then completes the +draft with part numbers and ETags. Re-presigning and completion reauthorize +against ZPan. Presigned URLs and file bytes never need to transit the external +controller. -Publish a versioned Skill that: +### Authorization -1. detects the ZPan origin; -2. verifies Restish v2 and connects `/api/openapi.json` with - `--replace --yes`; -3. selects the least-privilege Reader, File manager, or Publisher profile; -4. syncs a previously connected API before use; -5. triggers browser OAuth on the first safe request when a local callback is - available; -6. verifies and, after explicit trust confirmation, installs the - `restish-zpan` plugin; -7. uses an environment-backed Agent API-key profile for CI; -8. sends every local file upload through `restish zpan-upload`; -9. confirms target workspace, destructive operations, overwrite behavior, and - public sharing; -10. returns object IDs, URLs, quota effects, and task state in a compact - machine-readable result. +The grantable resource scopes cover object read/create/update/delete, share +read/create/delete, quota read, storage-usage read, and task read. Purge, +administration, billing, credential management, WebDAV configuration, and +downloader registration remain excluded. -The Skill never asks the user to paste a bearer token. Restish owns OAuth token -storage, refresh, logout, and redacted authentication diagnostics. Skill -workflows refer to a selected ZPan Restish profile rather than assuming a -specific environment variable or credential type, so a future profile may use -an external Agent Auth signer without changing the file-operation instructions. +OAuth resolves to the same protocol-neutral principal and route policies used +by the rest of ZPan. Business use cases do not branch on a particular Agent +controller or client ID. -The repository ships the Skill as `skills/zpan` with routed references for -setup, file operations, uploads, CI, MCP, and acceptance evidence. The -user-facing setup guide is [docs/agent-skill.md](../agent-skill.md). +## Removed Compatibility Surfaces -### Restish Upload Plugin +- fixed, system-managed Agent OAuth client; +- Agent API-key template, endpoints, settings forms, and tests; +- OpenAPI `x-cli-config` profiles; +- `restish-zpan` command plugin and release artifact; +- repository-hosted ZPan Agent skill. -Ship `restish-zpan` from this repository and install it with: - -```sh -restish plugin install saltbo/zpan zpan -``` - -It contributes `restish zpan-upload`. The plugin uses Restish delegated HTTP -for ZPan draft, re-sign, complete, and abort operations, preserving the selected -profile, OAuth/API-key authentication, TLS, and output behavior. With Restish -v2.3 command plugins, the host profile is selected through `RSH_PROFILE` while -the plugin's matching `--profile` selects spec validation and checkpoint -identity. It streams -local file parts directly to presigned S3 URLs with bounded concurrency, retry, -ETag capture, resume checkpoints, and idempotent completion. File bytes and -presigned URLs never pass through the Agent context or Restish's plugin CBOR -channel. - -The first release supports single and multipart files, re-signing expired -parts, interrupted resume, and explicit abort. Checkpoints contain no -credentials or presigned URLs and are removed after success. The Skill invokes -the plugin; it does not implement the upload state machine itself. - -The official `restish-mcp` plugin is an optional transport for ordinary API -operations, not the upload implementation. Its default read-only mode is useful -for browsing. Keep the default recipe read-only: - -```sh -restish plugin install rest-sh/restish mcp -restish mcp serve zpan --operations listObjects,getObject,listShares,getUserQuota,getStorageUsage -``` - -Enable write tools only with an explicit reviewed operation allowlist. Do not -allow upload draft, part signing, completion, or abort operation IDs through -MCP. - -Keep MCP results bounded; the plugin's default result limit is 16 KiB. Object -contents continue to move through presigned URLs, never through an MCP result. - -### Interactive OAuth - -Create a system-managed public native client, for example `zpan-agent`, with: - -- authorization code + PKCE -- Restish v2.3 loopback callbacks `http://localhost:8484/callback` and - `http://127.0.0.1:8484/callback` -- refresh-token support through `offline_access` -- only Agent API scopes - -Better Auth OAuth Provider 1.6.x serves the flow below the auth base path: -`/api/auth/oauth2/authorize`, `/api/auth/oauth2/token`, -`/api/auth/oauth2/revoke`, `/api/auth/oauth2/introspect`, and -`/api/auth/oauth2/userinfo`. ZPan additionally forwards required root -well-known metadata for the `/api/auth` issuer and publishes protected-resource -metadata for `/api`. - -The default Restish profile uses authorization code + PKCE. After -`restish api connect`, the first safe Agent API request opens browser consent; -Restish caches and refreshes the resulting tokens. `--rsh-no-browser` may be -used when the callback can still be completed manually. Standard Agent device -authorization remains a v2.9.x follow-up. - -Consent binds the grant to: - -- the signed-in user -- exactly one workspace -- requested resource scopes -- the user's current workspace role -- the OAuth client and expiry/revocation state - -Access tokens are short-lived. Refresh tokens remain bounded by that grant. -Changing team membership or revoking the grant removes access independently of -the user's browser sessions. - -### Agent API Keys - -Add an `agent` API-key template for CI and other unattended automation. - -Defaults: - -- exactly one workspace -- least-privilege permissions selected by the user -- explicit name, expiry, last-used time, and revocation -- personal owners and team owners/admins manage keys; editors cannot issue credentials -- separate keys for separate Agents and environments -- no admin, billing, membership, entitlement, or credential-management access -- team membership and role rechecked at authorization boundaries -- rate and storage limits enforced server-side - -The plaintext key is returned once and stored in a CI secret or another -non-interactive secret store. - -Only active keys can be rotated. Expired and revoked keys are terminal; create -a new key when a new lifetime or credential is required. - -### Scopes and Presets - -The server has one canonical authorization vocabulary. OAuth grants, API keys, -browser-session roles, and future Agent credentials all produce a set of the -same scopes; there is no separate `Scope -> Permission` mapping. - -Scope names use the stable, lowercase `:` form. Resources are -plural domain nouns and actions come from a small shared vocabulary such as -`read`, `create`, `update`, and `delete`. Scope names describe business -authority, not HTTP methods. They do not include a redundant `zpan:` prefix: -the token issuer and audience already identify the ZPan API. - -Initial Agent-grantable scopes are: - -| Scope | Intended operations | -|-------|---------------------| -| `objects:read` | List, inspect, and download objects | -| `objects:create` | Upload files and create folders | -| `objects:update` | Rename, move, and copy objects | -| `objects:delete` | Soft-delete objects | -| `shares:read` | List and inspect shares | -| `shares:create` | Create public shares | -| `shares:delete` | Revoke shares | -| `quota:read` | Inspect workspace quota | -| `tasks:read` | Inspect task state | - -Protocol scopes such as `openid` and `offline_access` retain their standard -OAuth/OIDC meaning. They are not ZPan route permissions. - -Each protected route declares its minimum required scopes. A route declares -what authority the operation needs, not a list of broad scopes or caller types -that are allowed to invoke it. Workspace membership, resource ownership, -resource state, quota, and other dynamic constraints remain policy checks after -the scope check. - -Reader, File manager, and Publisher are UI/Restish shortcuts that expand to -explicit scope sets. They are not scopes and routes never reference preset names. -Destructive and public-sharing scopes remain separately selectable. - -## Current Gaps to Close - -- ZPan is not currently an OAuth authorization server for Agent resource - scopes. -- The legacy `zpan-cli` device flow is intentionally limited to the exact - `downloader:register` scope and downloader registration endpoint. -- Object routes currently reject ordinary API-key principals with a blanket - session-only gate. -- Authenticated share and quota routes currently require a user session. -- Protected routes need shared scope middleware that accepts a protocol-neutral - principal instead of branching on `user`, `api-key`, or another credential - kind. Every protected route must declare its minimum scopes. -- API-key templates currently cover image hosting, WebDAV, and remote download, - but not general Agent file management. -- Workspace API-key authorization needs an explicit personal-space ownership - path in addition to team membership checks. -- The unified OpenAPI document lacks operation security and CLI/MCP annotations. -- Upload conflict policy, idempotency, and multipart retry behavior need an - explicit public contract. +The existing `zpan-cli` device authorization remains only for its legacy, +single-use downloader-registration bootstrap. ## Delivery Order -1. Define the canonical scope vocabulary and scope declaration metadata. -2. Refactor all protected routes to authorize protocol-neutral principals, - declare minimum scopes, and emit typed actors rather than require a - particular principal kind. -3. Add manual Agent API keys and make scoped API access work end to end. -4. Add the system-managed public native OAuth client, Agent grants, and - authorization-code + PKCE flow. -5. Stabilize the server multipart protocol. -6. Publish unified OpenAPI security and Restish v2 credential bindings. -7. Ship and release the `restish-zpan` upload command plugin. -8. Ship the ZPan Skill and validate browser OAuth and CI profiles. -9. Add the optional, allowlisted Restish MCP recipe. - -API keys come first as the smallest way to prove the resource authorization -boundary. They do not become the interactive login protocol. - -## Non-goals for v2.9 - -- A standalone `zpan` CLI or branded Restish binary -- Standard Agent device authorization before the v2.9.x follow-up -- Anonymous upload, provisional workspace, preview-and-claim, or anonymous - permanent storage -- Desktop sync or filesystem-provider integration -- Agent access to admin, billing, entitlement, membership, or credential - management -- Giving an Agent a browser cookie or unrestricted user session -- Dynamic third-party OAuth client registration -- Making the draft Agent Auth Protocol or its current Better Auth plugin a - required production dependency -- A ZPan-specific Agent identity, key-signing, capability-grant, or claim - protocol that duplicates a future standards-based integration -- Enterprise identity protocols or custom workspace roles -- Server-proxied file bytes +1. Resource and authorization-server discovery. +2. Dynamic client registration and administrator visibility. +3. Workspace consent, grants, and revocation. +4. JWT bearer, token exchange, DPoP, and JWT revocation. +5. Tool-neutral OpenAPI authorization metadata. +6. Discoverable Arazzo upload workflows. +7. Self-describing single/multipart upload responses. +8. Remove fixed-client, Agent API-key, profile, plugin, and skill surfaces. +9. Complete local gates and real FlareAuth acceptance. ## Acceptance Criteria -- A Restish v2 client connects to `/api/openapi.json`, triggers browser - OAuth + PKCE, and reuses/refreshes cached tokens without token copy/paste. -- Consent shows and binds one workspace and the requested scopes. -- A CI job can use a manually created workspace-scoped Agent API key. -- Browser-session, OAuth, and API-key principals pass through the same scope and - workspace-policy authorization. -- Every protected route declares its minimum required scopes; no route - authorizes by credential kind, Agent type, or preset name. -- Agent-facing routes and use cases do not branch on OAuth versus API key, and - their authorization tests can supply a protocol-neutral principal directly. -- Attempts to access another workspace or an ungranted operation return `403` - and produce an audit event. -- Removing the grant/key owner's team membership or reducing their role - immediately removes the corresponding Agent access. -- Expired or revoked credentials cannot complete uploads or issue new download - URLs. -- The unified OpenAPI document derives operation security from route declarations. -- `restish zpan-upload` completes and resumes multipart uploads without exposing - file bytes, credentials, or presigned URLs to the Agent. -- Credentials never enter logs, audit metadata, analytics, share URLs, or MCP - results. +- A controller starting only from the ZPan `/api` URL discovers OpenAPI, + Arazzo, and OAuth metadata. +- Dynamic registration creates a client visible in administrator settings. +- The user sees an authorization request with the real application name, + callback, workspace, scopes, and lifetime. +- After approval, the controller obtains a DPoP resource token for `/api`. +- A generic Agent creates an upload, sends all returned byte ranges, captures + ETags, and completes it without ZPan-specific code. +- The same connection lists, reads, and renames the uploaded file. +- Grant or JWT revocation stops subsequent resource access. +- Lint, type checking, Node tests, Cloudflare tests, and applicable end-to-end + checks pass. -## User Scenarios +## Deferred -**Interactive coding Agent:** -> The Agent connects ZPan through Restish. My browser opens a consent screen -> where I approve one workspace and the requested scopes. Restish stores and -> refreshes the OAuth tokens locally. - -**CI release workflow:** -> I create a File manager API key for the release workspace, store it as a CI -> secret, and rotate it independently of my user sessions. - -## Future Work - -- Evaluate the Better Auth Agent Auth plugin as an optional compatibility layer - once its draft protocol and packages are stable enough for production. Map its - short-lived Agent JWTs and capability grants into the same principal, - workspace, and scope-and-policy boundary defined in v2.9. -- Keep Restish as the operation transport. Until Restish supports Agent Auth - natively, use its reviewed `external-tool` authentication hook with the - official Agent Auth client/signing helper; do not build a standalone ZPan CLI. -- Before enabling Agent Auth on Cloudflare Workers, provide distributed JTI - replay storage and validate revocation across Worker isolates. -- Dynamic client registration for trusted third-party Agent platforms -- Workload identity federation for CI providers to remove stored long-lived - secrets -- HTTP Message Signatures / Web Bot Auth as an additional Agent-operator - authentication signal, never as file authorization by itself -- First-party remote MCP server if Restish's local MCP transport proves - insufficient -- Standard Agent device authorization with the same workspace grant and scopes +- administrator approval or rejection of newly registered applications; +- workload identity for unattended CI; +- standard Agent device authorization; +- richer per-client policy and registration lifecycle management. diff --git a/e2e/agent-access.spec.ts b/e2e/agent-access.spec.ts index cb486d32..3710919f 100644 --- a/e2e/agent-access.spec.ts +++ b/e2e/agent-access.spec.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test' import { signUpAndGoToFiles } from './helpers' const oauthQuery = - 'client_id=zpan-agent&redirect_uri=http%3A%2F%2F127.0.0.1%3A8484%2Fcallback&response_type=code&scope=openid%20offline_access%20objects%3Aread%20shares%3Acreate%20quota%3Aread' + 'client_id=dynamic-client&redirect_uri=https%3A%2F%2Fbroker.example.com%2Fcallback&response_type=code&scope=openid%20offline_access%20objects%3Aread%20shares%3Acreate%20quota%3Aread' test.describe('Agent Access OAuth UI', () => { test('renders consent details and submits full approval @desktop', async ({ page }) => { @@ -12,13 +12,13 @@ test.describe('Agent Access OAuth UI', () => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ - clientId: 'zpan-agent', - clientName: 'ZPan Agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', instanceOrigin: 'http://localhost:5185', workspace: { id: 'org-e2e', name: 'Personal' }, scopes: ['objects:read', 'shares:create', 'quota:read'], standardScopes: ['openid', 'offline_access'], - redirectUri: 'http://127.0.0.1:8484/callback', + redirectUri: 'https://broker.example.com/callback', grantLifetime: { accessTokenSeconds: 900, refreshTokenSeconds: 2_592_000 }, }), }) @@ -30,25 +30,25 @@ test.describe('Agent Access OAuth UI', () => { expect(body).toEqual({ accept: true, oauthQuery }) await route.fulfill({ contentType: 'application/json', - body: JSON.stringify({ url: 'http://127.0.0.1:8484/callback?code=e2e-code' }), + body: JSON.stringify({ url: 'https://broker.example.com/callback?code=e2e-code' }), }) }) - await page.route('http://127.0.0.1:8484/callback?code=e2e-code', async (route) => { - await route.fulfill({ contentType: 'text/html', body: '
Returned to Restish
' }) + await page.route('https://broker.example.com/callback?code=e2e-code', async (route) => { + await route.fulfill({ contentType: 'text/html', body: '
Returned to FlareAuth
' }) }) await page.goto(`/settings/agent-access?${oauthQuery}`) - await expect(page.getByRole('heading', { name: 'Authorize ZPan Agent' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Authorize Application' })).toBeVisible() await expect(page.getByText('http://localhost:5185')).toBeVisible() - await expect(page.getByText('http://127.0.0.1:8484/callback')).toBeVisible() + await expect(page.getByText('https://broker.example.com/callback')).toBeVisible() await expect(page.getByText('Files: read objects')).toBeVisible() await expect(page.getByText('Shares: create shares')).toBeVisible() await expect(page.getByText('Quota: read workspace quota')).toBeVisible() await page.getByRole('button', { name: 'Approve Access' }).click() - await expect(page).toHaveURL(/127\.0\.0\.1:8484\/callback\?code=e2e-code/, { timeout: 10000 }) - await expect(page.getByText('Returned to Restish')).toBeVisible() + await expect(page).toHaveURL(/broker\.example\.com\/callback\?code=e2e-code/, { timeout: 10000 }) + await expect(page.getByText('Returned to FlareAuth')).toBeVisible() }) test('lists and revokes delegated grants in settings @desktop', async ({ page }) => { @@ -65,8 +65,8 @@ test.describe('Agent Access OAuth UI', () => { : [ { id: 'grant-e2e', - clientId: 'zpan-agent', - clientName: 'ZPan Agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', userId: 'user-e2e', orgId: 'org-e2e', workspaceName: 'Personal', @@ -88,7 +88,7 @@ test.describe('Agent Access OAuth UI', () => { await page.goto('/settings/agent-access') await expect(page.getByText('Delegated OAuth Grants')).toBeVisible() - await expect(page.getByRole('cell', { name: 'ZPan Agent' })).toBeVisible() + await expect(page.getByRole('cell', { name: 'FlareAuth' })).toBeVisible() await expect(page.getByText('Shares: create shares')).toBeVisible() const revokeButtons = page.getByRole('button', { name: 'Revoke' }) @@ -105,13 +105,13 @@ test.describe('Agent Access OAuth UI', () => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ - clientId: 'zpan-agent', - clientName: 'ZPan Agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', instanceOrigin: 'http://localhost:5185', workspace: { id: 'org-e2e', name: 'Personal' }, scopes: ['objects:read', 'shares:create', 'quota:read'], standardScopes: ['openid', 'offline_access'], - redirectUri: 'http://127.0.0.1:8484/callback', + redirectUri: 'https://broker.example.com/callback', grantLifetime: { accessTokenSeconds: 900, refreshTokenSeconds: 2_592_000 }, }), }) @@ -124,8 +124,8 @@ test.describe('Agent Access OAuth UI', () => { items: [ { id: 'grant-mobile', - clientId: 'zpan-agent', - clientName: 'ZPan Agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', userId: 'user-e2e', orgId: 'org-e2e', workspaceName: 'Personal', @@ -140,7 +140,7 @@ test.describe('Agent Access OAuth UI', () => { }) await page.goto(`/settings/agent-access?${oauthQuery}`) - await expect(page.getByRole('heading', { name: 'Authorize ZPan Agent' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Authorize Application' })).toBeVisible() await expect(page.getByRole('button', { name: 'Approve Access' })).toBeVisible() await expect(page.getByText('Files: read objects')).toBeVisible() await expect(page.getByText('Shares: create shares')).toBeVisible() @@ -150,7 +150,7 @@ test.describe('Agent Access OAuth UI', () => { await page.goto('/settings/agent-access') await expect(page.getByText('Delegated OAuth Grants')).toBeVisible() - await expect(page.getByRole('cell', { name: 'ZPan Agent' })).toBeVisible() + await expect(page.getByRole('cell', { name: 'FlareAuth' })).toBeVisible() const grantsTableContainer = page.locator('[data-slot="table-container"]').last() await expect(grantsTableContainer).toBeVisible() await expect diff --git a/migrations/0083_external-resource-oauth.sql b/migrations/0083_external-resource-oauth.sql new file mode 100644 index 00000000..4adc5de2 --- /dev/null +++ b/migrations/0083_external-resource-oauth.sql @@ -0,0 +1,66 @@ +CREATE TABLE `jwks` ( + `id` text PRIMARY KEY NOT NULL, + `public_key` text NOT NULL, + `private_key` text NOT NULL, + `alg` text, + `crv` text, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `expires_at` integer +); +--> statement-breakpoint +CREATE TABLE `oauthClientAssertion` ( + `id` text PRIMARY KEY NOT NULL, + `expires_at` integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE `oauthClientResource` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `resource_id` text NOT NULL, + `metadata` text, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`client_id`) REFERENCES `oauthClient`(`client_id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`resource_id`) REFERENCES `oauthResource`(`identifier`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE INDEX `oauthClientResource_client_id_idx` ON `oauthClientResource` (`client_id`);--> statement-breakpoint +CREATE INDEX `oauthClientResource_resource_id_idx` ON `oauthClientResource` (`resource_id`);--> statement-breakpoint +CREATE TABLE `oauthResource` ( + `id` text PRIMARY KEY NOT NULL, + `identifier` text NOT NULL, + `name` text NOT NULL, + `access_token_ttl` integer, + `refresh_token_ttl` integer, + `signing_algorithm` text, + `signing_key_id` text, + `allowed_scopes` text, + `custom_claims` text, + `dpop_bound_access_tokens_required` integer DEFAULT false, + `disabled` integer DEFAULT false, + `policy_version` integer DEFAULT 1, + `metadata` text, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + `updated_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauthResource_identifier_unique` ON `oauthResource` (`identifier`);--> statement-breakpoint +CREATE INDEX `oauthResource_identifier_idx` ON `oauthResource` (`identifier`);--> statement-breakpoint +ALTER TABLE `oauthAccessToken` ADD `authorization_code_id` text;--> statement-breakpoint +ALTER TABLE `oauthAccessToken` ADD `resources` text;--> statement-breakpoint +ALTER TABLE `oauthAccessToken` ADD `requested_user_info_claims` text;--> statement-breakpoint +ALTER TABLE `oauthAccessToken` ADD `revoked` integer;--> statement-breakpoint +ALTER TABLE `oauthAccessToken` ADD `confirmation` text;--> statement-breakpoint +ALTER TABLE `oauthClient` ADD `backchannel_logout_uri` text;--> statement-breakpoint +ALTER TABLE `oauthClient` ADD `backchannel_logout_session_required` integer;--> statement-breakpoint +ALTER TABLE `oauthClient` ADD `jwks` text;--> statement-breakpoint +ALTER TABLE `oauthClient` ADD `jwks_uri` text;--> statement-breakpoint +ALTER TABLE `oauthClient` ADD `dpop_bound_access_tokens` integer DEFAULT false;--> statement-breakpoint +ALTER TABLE `oauthConsent` ADD `resources` text;--> statement-breakpoint +ALTER TABLE `oauthConsent` ADD `requested_user_info_claims` text;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `authorization_code_id` text;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `resources` text;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `requested_user_info_claims` text;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `rotated_at` integer;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `rotation_replay_response` text;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `rotation_replay_expires_at` integer;--> statement-breakpoint +ALTER TABLE `oauthRefreshToken` ADD `confirmation` text; \ No newline at end of file diff --git a/migrations/0084_better-auth-account-issuer.sql b/migrations/0084_better-auth-account-issuer.sql new file mode 100644 index 00000000..68b78fd8 --- /dev/null +++ b/migrations/0084_better-auth-account-issuer.sql @@ -0,0 +1,2 @@ +ALTER TABLE `account` ADD `issuer` text DEFAULT '' NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX `account_issuer_providerAccountId_unique` ON `account` (`issuer`,`account_id`); \ No newline at end of file diff --git a/migrations/0085_oauth-jwt-revocation.sql b/migrations/0085_oauth-jwt-revocation.sql new file mode 100644 index 00000000..cc393c33 --- /dev/null +++ b/migrations/0085_oauth-jwt-revocation.sql @@ -0,0 +1,8 @@ +CREATE TABLE `oauthJwtRevocation` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL +); +--> statement-breakpoint +CREATE INDEX `oauthJwtRevocation_expires_at_idx` ON `oauthJwtRevocation` (`expires_at`); \ No newline at end of file diff --git a/migrations/meta/0083_snapshot.json b/migrations/meta/0083_snapshot.json new file mode 100644 index 00000000..85e1b426 --- /dev/null +++ b/migrations/meta/0083_snapshot.json @@ -0,0 +1,6046 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "d1a5f1ce-6ecd-4998-9cda-035fda58138a", + "prevId": "5bb69def-4eeb-4aaf-8540-1fe87aa31d34", + "tables": { + "announcements": { + "name": "announcements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "announcements_status_priority_idx": { + "name": "announcements_status_priority_idx", + "columns": [ + "status", + "priority" + ], + "isUnique": false + }, + "announcements_published_idx": { + "name": "announcements_published_idx", + "columns": [ + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_name": { + "name": "target_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_ref": { + "name": "actor_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "audit_events_org_created_idx": { + "name": "audit_events_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_created_idx": { + "name": "audit_events_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_action_created_idx": { + "name": "audit_events_action_created_idx", + "columns": [ + "action", + "created_at" + ], + "isUnique": false + }, + "audit_events_target_created_idx": { + "name": "audit_events_target_created_idx", + "columns": [ + "target_type", + "target_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_created_idx": { + "name": "audit_events_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "background_jobs": { + "name": "background_jobs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_path": { + "name": "target_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_bytes": { + "name": "input_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_bytes": { + "name": "output_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processed_bytes": { + "name": "processed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "current_filename": { + "name": "current_filename", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_metadata": { + "name": "result_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retryable": { + "name": "retryable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cancelable": { + "name": "cancelable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "retried_from_job_id": { + "name": "retried_from_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "background_jobs_org_created_idx": { + "name": "background_jobs_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_status_idx": { + "name": "background_jobs_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "background_jobs_org_type_idx": { + "name": "background_jobs_org_type_idx", + "columns": [ + "org_id", + "type" + ], + "isUnique": false + }, + "background_jobs_created_idx": { + "name": "background_jobs_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_page_idx": { + "name": "background_jobs_org_page_idx", + "columns": [ + "org_id", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cloud_traffic_reports": { + "name": "cloud_traffic_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "cloud_traffic_reports_event_uniq": { + "name": "cloud_traffic_reports_event_uniq", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "cloud_traffic_reports_org_period_idx": { + "name": "cloud_traffic_reports_org_period_idx", + "columns": [ + "org_id", + "period" + ], + "isUnique": false + }, + "cloud_traffic_reports_status_idx": { + "name": "cloud_traffic_reports_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "cloud_traffic_reports_retry_idx": { + "name": "cloud_traffic_reports_retry_idx", + "columns": [ + "status", + "next_retry_at", + "created_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_issued_idx": { + "name": "cloud_traffic_reports_issued_idx", + "columns": [ + "issued_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_updated_idx": { + "name": "cloud_traffic_reports_updated_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "download_tasks": { + "name": "download_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_uri": { + "name": "source_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "assigned_downloader_id": { + "name": "assigned_downloader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "billing_authorized_bytes": { + "name": "billing_authorized_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_bytes": { + "name": "billing_charged_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_credits": { + "name": "billing_charged_credits", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_object_id": { + "name": "result_object_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "events": { + "name": "events", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "resolve_started_at": { + "name": "resolve_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolve_completed_at": { + "name": "resolve_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_completed_at": { + "name": "download_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_started_at": { + "name": "ingest_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_completed_at": { + "name": "ingest_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_started_at": { + "name": "seeding_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_stopped_at": { + "name": "seeding_stopped_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "download_tasks_org_created_idx": { + "name": "download_tasks_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_status_idx": { + "name": "download_tasks_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "download_tasks_org_category_idx": { + "name": "download_tasks_org_category_idx", + "columns": [ + "org_id", + "category" + ], + "isUnique": false + }, + "download_tasks_org_tags_idx": { + "name": "download_tasks_org_tags_idx", + "columns": [ + "org_id", + "tags" + ], + "isUnique": false + }, + "download_tasks_downloader_idx": { + "name": "download_tasks_downloader_idx", + "columns": [ + "assigned_downloader_id", + "status" + ], + "isUnique": false + }, + "download_tasks_created_idx": { + "name": "download_tasks_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "download_tasks_finished_idx": { + "name": "download_tasks_finished_idx", + "columns": [ + "finished_at" + ], + "isUnique": false + }, + "download_tasks_org_deleted_created_idx": { + "name": "download_tasks_org_deleted_created_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_page_idx": { + "name": "download_tasks_org_page_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + }, + "download_tasks_downloader_page_idx": { + "name": "download_tasks_downloader_page_idx", + "columns": [ + "assigned_downloader_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloaders": { + "name": "downloaders", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_jti": { + "name": "token_jti", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "engine": { + "name": "engine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'http'" + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "max_concurrent_tasks": { + "name": "max_concurrent_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "current_tasks": { + "name": "current_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "download_bps": { + "name": "download_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "upload_bps": { + "name": "upload_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "free_disk_bytes": { + "name": "free_disk_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remote_download_credit_billing_enabled": { + "name": "remote_download_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_download_credit_unit_bytes": { + "name": "remote_download_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "remote_download_credit_per_unit": { + "name": "remote_download_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "downloaders_token_jti_unique": { + "name": "downloaders_token_jti_unique", + "columns": [ + "token_jti" + ], + "isUnique": true + }, + "downloaders_status_idx": { + "name": "downloaders_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "downloaders_enabled_idx": { + "name": "downloaders_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + }, + "downloaders_created_idx": { + "name": "downloaders_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hosting_configs": { + "name": "image_hosting_configs", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_provider": { + "name": "domain_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_hostname_id": { + "name": "provider_hostname_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_status": { + "name": "domain_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_error": { + "name": "domain_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_last_checked_at": { + "name": "domain_last_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_verified_at": { + "name": "domain_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referer_allowlist": { + "name": "referer_allowlist", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hosting_configs_custom_domain_unique": { + "name": "image_hosting_configs_custom_domain_unique", + "columns": [ + "custom_domain" + ], + "isUnique": true + } + }, + "foreignKeys": { + "image_hosting_configs_org_id_organization_id_fk": { + "name": "image_hosting_configs_org_id_organization_id_fk", + "tableFrom": "image_hosting_configs", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hostings": { + "name": "image_hostings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hostings_token_unique": { + "name": "image_hostings_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "image_hostings_org_path_uniq": { + "name": "image_hostings_org_path_uniq", + "columns": [ + "org_id", + "path" + ], + "isUnique": true, + "where": "\"image_hostings\".\"purged_at\" IS NULL" + }, + "image_hostings_org_created_idx": { + "name": "image_hostings_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "image_hostings_page_idx": { + "name": "image_hostings_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "created_at", + "id" + ], + "isUnique": false + }, + "image_hostings_token_idx": { + "name": "image_hostings_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "image_hostings_org_id_organization_id_fk": { + "name": "image_hostings_org_id_organization_id_fk", + "tableFrom": "image_hostings", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "image_hostings_storage_id_storages_id_fk": { + "name": "image_hostings_storage_id_storages_id_fk", + "tableFrom": "image_hostings", + "tableTo": "storages", + "columnsFrom": [ + "storage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invite_codes": { + "name": "invite_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_by": { + "name": "used_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_codes_code_unique": { + "name": "invite_codes_code_unique", + "columns": [ + "code" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "license_bindings": { + "name": "license_bindings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cloud_binding_id": { + "name": "cloud_binding_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_store_id": { + "name": "cloud_store_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_id": { + "name": "cloud_account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_email": { + "name": "cloud_account_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate": { + "name": "cached_certificate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate_expires_at": { + "name": "cached_certificate_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bound_at": { + "name": "bound_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disconnected_at": { + "name": "disconnected_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_at": { + "name": "last_refresh_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_error": { + "name": "last_refresh_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "license_bindings_active_uniq": { + "name": "license_bindings_active_uniq", + "columns": [ + "status" + ], + "isUnique": true, + "where": "status = 'active'" + }, + "license_bindings_cloud_binding_idx": { + "name": "license_bindings_cloud_binding_idx", + "columns": [ + "cloud_binding_id" + ], + "isUnique": false + }, + "license_bindings_instance_idx": { + "name": "license_bindings_instance_idx", + "columns": [ + "instance_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "matters": { + "name": "matters", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "dirtype": { + "name": "dirtype", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "parent": { + "name": "parent", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "object": { + "name": "object", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "trashed_at": { + "name": "trashed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "matters_alias_unique": { + "name": "matters_alias_unique", + "columns": [ + "alias" + ], + "isUnique": true + }, + "matters_status_dir_created_idx": { + "name": "matters_status_dir_created_idx", + "columns": [ + "status", + "dirtype", + "created_at" + ], + "isUnique": false + }, + "matters_webdav_path_idx": { + "name": "matters_webdav_path_idx", + "columns": [ + "org_id", + "parent", + "name", + "status", + "trashed_at", + "purged_at" + ], + "isUnique": false + }, + "matters_webdav_children_idx": { + "name": "matters_webdav_children_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "name" + ], + "isUnique": false + }, + "matters_list_page_idx": { + "name": "matters_list_page_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "created_at", + "id" + ], + "isUnique": false + }, + "matters_trash_page_idx": { + "name": "matters_trash_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "trashed_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "ref_type": { + "name": "ref_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notifications_user_created_idx": { + "name": "notifications_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + "user_id", + "read_at" + ], + "isUnique": false + }, + "notifications_user_page_idx": { + "name": "notifications_user_page_idx", + "columns": [ + "user_id", + "read_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "object_upload_sessions": { + "name": "object_upload_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "object_id": { + "name": "object_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "upload_id": { + "name": "upload_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "on_conflict": { + "name": "on_conflict", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fail'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "object_upload_sessions_object_idx": { + "name": "object_upload_sessions_object_idx", + "columns": [ + "org_id", + "object_id" + ], + "isUnique": false + }, + "object_upload_sessions_expires_idx": { + "name": "object_upload_sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quota_entitlements": { + "name": "org_quota_entitlements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entitlement_type": { + "name": "entitlement_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'grant'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "starts_at": { + "name": "starts_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "org_quota_entitlements_org_resource_idx": { + "name": "org_quota_entitlements_org_resource_idx", + "columns": [ + "org_id", + "resource_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_org_type_idx": { + "name": "org_quota_entitlements_org_type_idx", + "columns": [ + "org_id", + "resource_type", + "entitlement_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_active_plan_uniq": { + "name": "org_quota_entitlements_active_plan_uniq", + "columns": [ + "org_id", + "resource_type", + "entitlement_type" + ], + "isUnique": true, + "where": "status = 'active' AND entitlement_type = 'plan' AND source <> 'free_plan'" + }, + "org_quota_entitlements_source_resource_uniq": { + "name": "org_quota_entitlements_source_resource_uniq", + "columns": [ + "source", + "source_id", + "resource_type" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quotas": { + "name": "org_quotas", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quota": { + "name": "quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_quota": { + "name": "traffic_quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_used": { + "name": "traffic_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_period": { + "name": "traffic_period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1970-01'" + } + }, + "indexes": { + "org_quotas_org_uniq": { + "name": "org_quotas_org_uniq", + "columns": [ + "org_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "remote_download_usage_reports": { + "name": "remote_download_usage_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "downloader_id": { + "name": "downloader_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_index": { + "name": "unit_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "remote_download_usage_reports_event_id_unique": { + "name": "remote_download_usage_reports_event_id_unique", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "remote_download_usage_task_unit_uniq": { + "name": "remote_download_usage_task_unit_uniq", + "columns": [ + "task_id", + "unit_index" + ], + "isUnique": true + }, + "remote_download_usage_org_idx": { + "name": "remote_download_usage_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + }, + "remote_download_usage_status_idx": { + "name": "remote_download_usage_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "remote_download_usage_created_idx": { + "name": "remote_download_usage_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "resource_changes": { + "name": "resource_changes", + "columns": { + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "change_type": { + "name": "change_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "resource_changes_scope_sequence_idx": { + "name": "resource_changes_scope_sequence_idx", + "columns": [ + "scope_type", + "scope_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_resource_sequence_idx": { + "name": "resource_changes_resource_sequence_idx", + "columns": [ + "resource_type", + "resource_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_occurred_idx": { + "name": "resource_changes_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "share_recipients": { + "name": "share_recipients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "share_id": { + "name": "share_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "share_recipients_share_id_idx": { + "name": "share_recipients_share_id_idx", + "columns": [ + "share_id" + ], + "isUnique": false + }, + "share_recipients_user_id_idx": { + "name": "share_recipients_user_id_idx", + "columns": [ + "recipient_user_id" + ], + "isUnique": false + }, + "share_recipients_email_idx": { + "name": "share_recipients_email_idx", + "columns": [ + "recipient_email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shares": { + "name": "shares", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "matter_id": { + "name": "matter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_limit": { + "name": "download_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "downloads": { + "name": "downloads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "shares_token_unique": { + "name": "shares_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "shares_creator_status_created_idx": { + "name": "shares_creator_status_created_idx", + "columns": [ + "creator_id", + "status", + "created_at", + "id" + ], + "isUnique": false + }, + "shares_creator_private_created_idx": { + "name": "shares_creator_private_created_idx", + "columns": [ + "creator_id", + "private", + "created_at" + ], + "isUnique": false + }, + "shares_created_idx": { + "name": "shares_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "site_invitations": { + "name": "site_invitations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_by": { + "name": "accepted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "site_invitations_token_unique": { + "name": "site_invitations_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "site_invitations_email_idx": { + "name": "site_invitations_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "site_invitations_created_idx": { + "name": "site_invitations_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "site_invitations_expires_idx": { + "name": "site_invitations_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "stats_rollups_hourly": { + "name": "stats_rollups_hourly", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bucket_start": { + "name": "bucket_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "metric_key": { + "name": "metric_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dimension_key": { + "name": "dimension_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "dimension_value": { + "name": "dimension_value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "unique_count": { + "name": "unique_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "stats_rollups_hourly_bucket_metric_dim_uniq": { + "name": "stats_rollups_hourly_bucket_metric_dim_uniq", + "columns": [ + "bucket_start", + "org_id", + "metric_key", + "dimension_key", + "dimension_value" + ], + "isUnique": true + }, + "stats_rollups_hourly_metric_bucket_idx": { + "name": "stats_rollups_hourly_metric_bucket_idx", + "columns": [ + "metric_key", + "bucket_start" + ], + "isUnique": false + }, + "stats_rollups_hourly_dimension_bucket_idx": { + "name": "stats_rollups_hourly_dimension_bucket_idx", + "columns": [ + "metric_key", + "dimension_key", + "bucket_start" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_breakdowns": { + "name": "storage_usage_breakdowns", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_breakdowns_org_category_uniq": { + "name": "storage_usage_breakdowns_org_category_uniq", + "columns": [ + "org_id", + "category" + ], + "isUnique": true + }, + "storage_usage_breakdowns_org_idx": { + "name": "storage_usage_breakdowns_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_ledger": { + "name": "storage_usage_ledger", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delta_bytes": { + "name": "delta_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_ledger_event_key_unique": { + "name": "storage_usage_ledger_event_key_unique", + "columns": [ + "event_key" + ], + "isUnique": true + }, + "storage_usage_ledger_occurred_idx": { + "name": "storage_usage_ledger_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_org_occurred_idx": { + "name": "storage_usage_ledger_org_occurred_idx", + "columns": [ + "org_id", + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_storage_occurred_idx": { + "name": "storage_usage_ledger_storage_occurred_idx", + "columns": [ + "storage_id", + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storages": { + "name": "storages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "access_key": { + "name": "access_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "secret_key": { + "name": "secret_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "custom_host": { + "name": "custom_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "capacity": { + "name": "capacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "egress_credit_billing_enabled": { + "name": "egress_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "egress_credit_unit_bytes": { + "name": "egress_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "egress_credit_per_unit": { + "name": "egress_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "force_path_style": { + "name": "force_path_style", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_checked_at": { + "name": "status_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_options": { + "name": "system_options", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_invite_links": { + "name": "team_invite_links", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "team_invite_links_token_unique": { + "name": "team_invite_links_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_dead_properties": { + "name": "webdav_dead_properties", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_dead_properties_resource_prop_uniq": { + "name": "webdav_dead_properties_resource_prop_uniq", + "columns": [ + "org_id", + "resource_path", + "namespace", + "name" + ], + "isUnique": true + }, + "webdav_dead_properties_resource_idx": { + "name": "webdav_dead_properties_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_locks": { + "name": "webdav_locks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "depth": { + "name": "depth", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'infinity'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_locks_token_unique": { + "name": "webdav_locks_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "webdav_locks_resource_idx": { + "name": "webdav_locks_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + }, + "webdav_locks_expires_idx": { + "name": "webdav_locks_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook_events": { + "name": "webhook_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cloud'" + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'order.quota_changed'" + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webhook_events_source_event_uniq": { + "name": "webhook_events_source_event_uniq", + "columns": [ + "source", + "event_id" + ], + "isUnique": true + }, + "webhook_events_source_created_idx": { + "name": "webhook_events_source_created_idx", + "columns": [ + "source", + "created_at" + ], + "isUnique": false + }, + "webhook_events_status_idx": { + "name": "webhook_events_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "webhook_events_processed_idx": { + "name": "webhook_events_processed_idx", + "columns": [ + "processed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "apikey": { + "name": "apikey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_request": { + "name": "last_request", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "apikey_config_id_idx": { + "name": "apikey_config_id_idx", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "apikey_reference_id_idx": { + "name": "apikey_reference_id_idx", + "columns": [ + "reference_id" + ], + "isUnique": false + }, + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": [ + "key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "deviceCode": { + "name": "deviceCode", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "deviceCode_device_code_idx": { + "name": "deviceCode_device_code_idx", + "columns": [ + "device_code" + ], + "isUnique": false + }, + "deviceCode_user_code_idx": { + "name": "deviceCode_user_code_idx", + "columns": [ + "user_code" + ], + "isUnique": false + }, + "deviceCode_status_idx": { + "name": "deviceCode_status_idx", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloader_bootstrap_credentials": { + "name": "downloader_bootstrap_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "downloader_bootstrap_credentials_token_hash_unique": { + "name": "downloader_bootstrap_credentials_token_hash_unique", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "downloader_bootstrap_token_hash_idx": { + "name": "downloader_bootstrap_token_hash_idx", + "columns": [ + "token_hash" + ], + "isUnique": false + }, + "downloader_bootstrap_user_idx": { + "name": "downloader_bootstrap_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "downloader_bootstrap_consumed_idx": { + "name": "downloader_bootstrap_consumed_idx", + "columns": [ + "consumed_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "downloader_bootstrap_credentials_user_id_user_id_fk": { + "name": "downloader_bootstrap_credentials_user_id_user_id_fk", + "tableFrom": "downloader_bootstrap_credentials", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "crv": { + "name": "crv", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "member": { + "name": "member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthAccessToken": { + "name": "oauthAccessToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthAccessToken_token_unique": { + "name": "oauthAccessToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthAccessToken_client_id_idx": { + "name": "oauthAccessToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthAccessToken_session_id_idx": { + "name": "oauthAccessToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthAccessToken_user_id_idx": { + "name": "oauthAccessToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthAccessToken_refresh_id_idx": { + "name": "oauthAccessToken_refresh_id_idx", + "columns": [ + "refresh_id" + ], + "isUnique": false + }, + "oauthAccessToken_token_idx": { + "name": "oauthAccessToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthAccessToken_client_id_oauthClient_client_id_fk": { + "name": "oauthAccessToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_session_id_session_id_fk": { + "name": "oauthAccessToken_session_id_session_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthAccessToken_user_id_user_id_fk": { + "name": "oauthAccessToken_user_id_user_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk": { + "name": "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthRefreshToken", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClient": { + "name": "oauthClient", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contacts": { + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens": { + "name": "dpop_bound_access_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "oauthClient_client_id_unique": { + "name": "oauthClient_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + }, + "oauthClient_client_id_idx": { + "name": "oauthClient_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClient_user_id_idx": { + "name": "oauthClient_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClient_user_id_user_id_fk": { + "name": "oauthClient_user_id_user_id_fk", + "tableFrom": "oauthClient", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientAssertion": { + "name": "oauthClientAssertion", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientResource": { + "name": "oauthClientResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthClientResource_client_id_idx": { + "name": "oauthClientResource_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClientResource_resource_id_idx": { + "name": "oauthClientResource_resource_id_idx", + "columns": [ + "resource_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClientResource_client_id_oauthClient_client_id_fk": { + "name": "oauthClientResource_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthClientResource_resource_id_oauthResource_identifier_fk": { + "name": "oauthClientResource_resource_id_oauthResource_identifier_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthResource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "identifier" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthConsent": { + "name": "oauthConsent", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthConsent_client_id_idx": { + "name": "oauthConsent_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthConsent_user_id_idx": { + "name": "oauthConsent_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthConsent_client_id_oauthClient_client_id_fk": { + "name": "oauthConsent_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthConsent_user_id_user_id_fk": { + "name": "oauthConsent_user_id_user_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthRefreshToken": { + "name": "oauthRefreshToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_response": { + "name": "rotation_replay_response", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_expires_at": { + "name": "rotation_replay_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_time": { + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthRefreshToken_token_unique": { + "name": "oauthRefreshToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthRefreshToken_client_id_idx": { + "name": "oauthRefreshToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthRefreshToken_session_id_idx": { + "name": "oauthRefreshToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthRefreshToken_user_id_idx": { + "name": "oauthRefreshToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthRefreshToken_token_idx": { + "name": "oauthRefreshToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthRefreshToken_client_id_oauthClient_client_id_fk": { + "name": "oauthRefreshToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthRefreshToken_session_id_session_id_fk": { + "name": "oauthRefreshToken_session_id_session_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthRefreshToken_user_id_user_id_fk": { + "name": "oauthRefreshToken_user_id_user_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthResource": { + "name": "oauthResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_ttl": { + "name": "access_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_ttl": { + "name": "refresh_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_algorithm": { + "name": "signing_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_key_id": { + "name": "signing_key_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_scopes": { + "name": "allowed_scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_claims": { + "name": "custom_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens_required": { + "name": "dpop_bound_access_tokens_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "policy_version": { + "name": "policy_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthResource_identifier_unique": { + "name": "oauthResource_identifier_unique", + "columns": [ + "identifier" + ], + "isUnique": true + }, + "oauthResource_identifier_idx": { + "name": "oauthResource_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization": { + "name": "organization", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "session_created_idx": { + "name": "session_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned": { + "name": "banned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_created_idx": { + "name": "user_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "user_lastActiveAt_idx": { + "name": "user_lastActiveAt_idx", + "columns": [ + "last_active_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "matters_webdav_children_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + }, + "matters_list_page_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/0084_snapshot.json b/migrations/meta/0084_snapshot.json new file mode 100644 index 00000000..dfbbd321 --- /dev/null +++ b/migrations/meta/0084_snapshot.json @@ -0,0 +1,6062 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "58a6328a-feb3-43bd-a119-e1c7637041b4", + "prevId": "d1a5f1ce-6ecd-4998-9cda-035fda58138a", + "tables": { + "announcements": { + "name": "announcements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "announcements_status_priority_idx": { + "name": "announcements_status_priority_idx", + "columns": [ + "status", + "priority" + ], + "isUnique": false + }, + "announcements_published_idx": { + "name": "announcements_published_idx", + "columns": [ + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_name": { + "name": "target_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_ref": { + "name": "actor_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "audit_events_org_created_idx": { + "name": "audit_events_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_created_idx": { + "name": "audit_events_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_action_created_idx": { + "name": "audit_events_action_created_idx", + "columns": [ + "action", + "created_at" + ], + "isUnique": false + }, + "audit_events_target_created_idx": { + "name": "audit_events_target_created_idx", + "columns": [ + "target_type", + "target_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_created_idx": { + "name": "audit_events_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "background_jobs": { + "name": "background_jobs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_path": { + "name": "target_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_bytes": { + "name": "input_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_bytes": { + "name": "output_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processed_bytes": { + "name": "processed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "current_filename": { + "name": "current_filename", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_metadata": { + "name": "result_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retryable": { + "name": "retryable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cancelable": { + "name": "cancelable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "retried_from_job_id": { + "name": "retried_from_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "background_jobs_org_created_idx": { + "name": "background_jobs_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_status_idx": { + "name": "background_jobs_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "background_jobs_org_type_idx": { + "name": "background_jobs_org_type_idx", + "columns": [ + "org_id", + "type" + ], + "isUnique": false + }, + "background_jobs_created_idx": { + "name": "background_jobs_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_page_idx": { + "name": "background_jobs_org_page_idx", + "columns": [ + "org_id", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cloud_traffic_reports": { + "name": "cloud_traffic_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "cloud_traffic_reports_event_uniq": { + "name": "cloud_traffic_reports_event_uniq", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "cloud_traffic_reports_org_period_idx": { + "name": "cloud_traffic_reports_org_period_idx", + "columns": [ + "org_id", + "period" + ], + "isUnique": false + }, + "cloud_traffic_reports_status_idx": { + "name": "cloud_traffic_reports_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "cloud_traffic_reports_retry_idx": { + "name": "cloud_traffic_reports_retry_idx", + "columns": [ + "status", + "next_retry_at", + "created_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_issued_idx": { + "name": "cloud_traffic_reports_issued_idx", + "columns": [ + "issued_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_updated_idx": { + "name": "cloud_traffic_reports_updated_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "download_tasks": { + "name": "download_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_uri": { + "name": "source_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "assigned_downloader_id": { + "name": "assigned_downloader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "billing_authorized_bytes": { + "name": "billing_authorized_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_bytes": { + "name": "billing_charged_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_credits": { + "name": "billing_charged_credits", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_object_id": { + "name": "result_object_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "events": { + "name": "events", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "resolve_started_at": { + "name": "resolve_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolve_completed_at": { + "name": "resolve_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_completed_at": { + "name": "download_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_started_at": { + "name": "ingest_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_completed_at": { + "name": "ingest_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_started_at": { + "name": "seeding_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_stopped_at": { + "name": "seeding_stopped_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "download_tasks_org_created_idx": { + "name": "download_tasks_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_status_idx": { + "name": "download_tasks_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "download_tasks_org_category_idx": { + "name": "download_tasks_org_category_idx", + "columns": [ + "org_id", + "category" + ], + "isUnique": false + }, + "download_tasks_org_tags_idx": { + "name": "download_tasks_org_tags_idx", + "columns": [ + "org_id", + "tags" + ], + "isUnique": false + }, + "download_tasks_downloader_idx": { + "name": "download_tasks_downloader_idx", + "columns": [ + "assigned_downloader_id", + "status" + ], + "isUnique": false + }, + "download_tasks_created_idx": { + "name": "download_tasks_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "download_tasks_finished_idx": { + "name": "download_tasks_finished_idx", + "columns": [ + "finished_at" + ], + "isUnique": false + }, + "download_tasks_org_deleted_created_idx": { + "name": "download_tasks_org_deleted_created_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_page_idx": { + "name": "download_tasks_org_page_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + }, + "download_tasks_downloader_page_idx": { + "name": "download_tasks_downloader_page_idx", + "columns": [ + "assigned_downloader_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloaders": { + "name": "downloaders", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_jti": { + "name": "token_jti", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "engine": { + "name": "engine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'http'" + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "max_concurrent_tasks": { + "name": "max_concurrent_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "current_tasks": { + "name": "current_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "download_bps": { + "name": "download_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "upload_bps": { + "name": "upload_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "free_disk_bytes": { + "name": "free_disk_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remote_download_credit_billing_enabled": { + "name": "remote_download_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_download_credit_unit_bytes": { + "name": "remote_download_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "remote_download_credit_per_unit": { + "name": "remote_download_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "downloaders_token_jti_unique": { + "name": "downloaders_token_jti_unique", + "columns": [ + "token_jti" + ], + "isUnique": true + }, + "downloaders_status_idx": { + "name": "downloaders_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "downloaders_enabled_idx": { + "name": "downloaders_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + }, + "downloaders_created_idx": { + "name": "downloaders_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hosting_configs": { + "name": "image_hosting_configs", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_provider": { + "name": "domain_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_hostname_id": { + "name": "provider_hostname_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_status": { + "name": "domain_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_error": { + "name": "domain_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_last_checked_at": { + "name": "domain_last_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_verified_at": { + "name": "domain_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referer_allowlist": { + "name": "referer_allowlist", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hosting_configs_custom_domain_unique": { + "name": "image_hosting_configs_custom_domain_unique", + "columns": [ + "custom_domain" + ], + "isUnique": true + } + }, + "foreignKeys": { + "image_hosting_configs_org_id_organization_id_fk": { + "name": "image_hosting_configs_org_id_organization_id_fk", + "tableFrom": "image_hosting_configs", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hostings": { + "name": "image_hostings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hostings_token_unique": { + "name": "image_hostings_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "image_hostings_org_path_uniq": { + "name": "image_hostings_org_path_uniq", + "columns": [ + "org_id", + "path" + ], + "isUnique": true, + "where": "\"image_hostings\".\"purged_at\" IS NULL" + }, + "image_hostings_org_created_idx": { + "name": "image_hostings_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "image_hostings_page_idx": { + "name": "image_hostings_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "created_at", + "id" + ], + "isUnique": false + }, + "image_hostings_token_idx": { + "name": "image_hostings_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "image_hostings_org_id_organization_id_fk": { + "name": "image_hostings_org_id_organization_id_fk", + "tableFrom": "image_hostings", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "image_hostings_storage_id_storages_id_fk": { + "name": "image_hostings_storage_id_storages_id_fk", + "tableFrom": "image_hostings", + "tableTo": "storages", + "columnsFrom": [ + "storage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invite_codes": { + "name": "invite_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_by": { + "name": "used_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_codes_code_unique": { + "name": "invite_codes_code_unique", + "columns": [ + "code" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "license_bindings": { + "name": "license_bindings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cloud_binding_id": { + "name": "cloud_binding_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_store_id": { + "name": "cloud_store_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_id": { + "name": "cloud_account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_email": { + "name": "cloud_account_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate": { + "name": "cached_certificate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate_expires_at": { + "name": "cached_certificate_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bound_at": { + "name": "bound_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disconnected_at": { + "name": "disconnected_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_at": { + "name": "last_refresh_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_error": { + "name": "last_refresh_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "license_bindings_active_uniq": { + "name": "license_bindings_active_uniq", + "columns": [ + "status" + ], + "isUnique": true, + "where": "status = 'active'" + }, + "license_bindings_cloud_binding_idx": { + "name": "license_bindings_cloud_binding_idx", + "columns": [ + "cloud_binding_id" + ], + "isUnique": false + }, + "license_bindings_instance_idx": { + "name": "license_bindings_instance_idx", + "columns": [ + "instance_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "matters": { + "name": "matters", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "dirtype": { + "name": "dirtype", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "parent": { + "name": "parent", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "object": { + "name": "object", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "trashed_at": { + "name": "trashed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "matters_alias_unique": { + "name": "matters_alias_unique", + "columns": [ + "alias" + ], + "isUnique": true + }, + "matters_status_dir_created_idx": { + "name": "matters_status_dir_created_idx", + "columns": [ + "status", + "dirtype", + "created_at" + ], + "isUnique": false + }, + "matters_webdav_path_idx": { + "name": "matters_webdav_path_idx", + "columns": [ + "org_id", + "parent", + "name", + "status", + "trashed_at", + "purged_at" + ], + "isUnique": false + }, + "matters_webdav_children_idx": { + "name": "matters_webdav_children_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "name" + ], + "isUnique": false + }, + "matters_list_page_idx": { + "name": "matters_list_page_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "created_at", + "id" + ], + "isUnique": false + }, + "matters_trash_page_idx": { + "name": "matters_trash_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "trashed_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "ref_type": { + "name": "ref_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notifications_user_created_idx": { + "name": "notifications_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + "user_id", + "read_at" + ], + "isUnique": false + }, + "notifications_user_page_idx": { + "name": "notifications_user_page_idx", + "columns": [ + "user_id", + "read_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "object_upload_sessions": { + "name": "object_upload_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "object_id": { + "name": "object_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "upload_id": { + "name": "upload_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "on_conflict": { + "name": "on_conflict", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fail'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "object_upload_sessions_object_idx": { + "name": "object_upload_sessions_object_idx", + "columns": [ + "org_id", + "object_id" + ], + "isUnique": false + }, + "object_upload_sessions_expires_idx": { + "name": "object_upload_sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quota_entitlements": { + "name": "org_quota_entitlements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entitlement_type": { + "name": "entitlement_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'grant'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "starts_at": { + "name": "starts_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "org_quota_entitlements_org_resource_idx": { + "name": "org_quota_entitlements_org_resource_idx", + "columns": [ + "org_id", + "resource_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_org_type_idx": { + "name": "org_quota_entitlements_org_type_idx", + "columns": [ + "org_id", + "resource_type", + "entitlement_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_active_plan_uniq": { + "name": "org_quota_entitlements_active_plan_uniq", + "columns": [ + "org_id", + "resource_type", + "entitlement_type" + ], + "isUnique": true, + "where": "status = 'active' AND entitlement_type = 'plan' AND source <> 'free_plan'" + }, + "org_quota_entitlements_source_resource_uniq": { + "name": "org_quota_entitlements_source_resource_uniq", + "columns": [ + "source", + "source_id", + "resource_type" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quotas": { + "name": "org_quotas", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quota": { + "name": "quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_quota": { + "name": "traffic_quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_used": { + "name": "traffic_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_period": { + "name": "traffic_period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1970-01'" + } + }, + "indexes": { + "org_quotas_org_uniq": { + "name": "org_quotas_org_uniq", + "columns": [ + "org_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "remote_download_usage_reports": { + "name": "remote_download_usage_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "downloader_id": { + "name": "downloader_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_index": { + "name": "unit_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "remote_download_usage_reports_event_id_unique": { + "name": "remote_download_usage_reports_event_id_unique", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "remote_download_usage_task_unit_uniq": { + "name": "remote_download_usage_task_unit_uniq", + "columns": [ + "task_id", + "unit_index" + ], + "isUnique": true + }, + "remote_download_usage_org_idx": { + "name": "remote_download_usage_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + }, + "remote_download_usage_status_idx": { + "name": "remote_download_usage_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "remote_download_usage_created_idx": { + "name": "remote_download_usage_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "resource_changes": { + "name": "resource_changes", + "columns": { + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "change_type": { + "name": "change_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "resource_changes_scope_sequence_idx": { + "name": "resource_changes_scope_sequence_idx", + "columns": [ + "scope_type", + "scope_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_resource_sequence_idx": { + "name": "resource_changes_resource_sequence_idx", + "columns": [ + "resource_type", + "resource_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_occurred_idx": { + "name": "resource_changes_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "share_recipients": { + "name": "share_recipients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "share_id": { + "name": "share_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "share_recipients_share_id_idx": { + "name": "share_recipients_share_id_idx", + "columns": [ + "share_id" + ], + "isUnique": false + }, + "share_recipients_user_id_idx": { + "name": "share_recipients_user_id_idx", + "columns": [ + "recipient_user_id" + ], + "isUnique": false + }, + "share_recipients_email_idx": { + "name": "share_recipients_email_idx", + "columns": [ + "recipient_email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shares": { + "name": "shares", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "matter_id": { + "name": "matter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_limit": { + "name": "download_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "downloads": { + "name": "downloads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "shares_token_unique": { + "name": "shares_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "shares_creator_status_created_idx": { + "name": "shares_creator_status_created_idx", + "columns": [ + "creator_id", + "status", + "created_at", + "id" + ], + "isUnique": false + }, + "shares_creator_private_created_idx": { + "name": "shares_creator_private_created_idx", + "columns": [ + "creator_id", + "private", + "created_at" + ], + "isUnique": false + }, + "shares_created_idx": { + "name": "shares_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "site_invitations": { + "name": "site_invitations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_by": { + "name": "accepted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "site_invitations_token_unique": { + "name": "site_invitations_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "site_invitations_email_idx": { + "name": "site_invitations_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "site_invitations_created_idx": { + "name": "site_invitations_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "site_invitations_expires_idx": { + "name": "site_invitations_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "stats_rollups_hourly": { + "name": "stats_rollups_hourly", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bucket_start": { + "name": "bucket_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "metric_key": { + "name": "metric_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dimension_key": { + "name": "dimension_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "dimension_value": { + "name": "dimension_value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "unique_count": { + "name": "unique_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "stats_rollups_hourly_bucket_metric_dim_uniq": { + "name": "stats_rollups_hourly_bucket_metric_dim_uniq", + "columns": [ + "bucket_start", + "org_id", + "metric_key", + "dimension_key", + "dimension_value" + ], + "isUnique": true + }, + "stats_rollups_hourly_metric_bucket_idx": { + "name": "stats_rollups_hourly_metric_bucket_idx", + "columns": [ + "metric_key", + "bucket_start" + ], + "isUnique": false + }, + "stats_rollups_hourly_dimension_bucket_idx": { + "name": "stats_rollups_hourly_dimension_bucket_idx", + "columns": [ + "metric_key", + "dimension_key", + "bucket_start" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_breakdowns": { + "name": "storage_usage_breakdowns", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_breakdowns_org_category_uniq": { + "name": "storage_usage_breakdowns_org_category_uniq", + "columns": [ + "org_id", + "category" + ], + "isUnique": true + }, + "storage_usage_breakdowns_org_idx": { + "name": "storage_usage_breakdowns_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_ledger": { + "name": "storage_usage_ledger", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delta_bytes": { + "name": "delta_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_ledger_event_key_unique": { + "name": "storage_usage_ledger_event_key_unique", + "columns": [ + "event_key" + ], + "isUnique": true + }, + "storage_usage_ledger_occurred_idx": { + "name": "storage_usage_ledger_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_org_occurred_idx": { + "name": "storage_usage_ledger_org_occurred_idx", + "columns": [ + "org_id", + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_storage_occurred_idx": { + "name": "storage_usage_ledger_storage_occurred_idx", + "columns": [ + "storage_id", + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storages": { + "name": "storages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "access_key": { + "name": "access_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "secret_key": { + "name": "secret_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "custom_host": { + "name": "custom_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "capacity": { + "name": "capacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "egress_credit_billing_enabled": { + "name": "egress_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "egress_credit_unit_bytes": { + "name": "egress_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "egress_credit_per_unit": { + "name": "egress_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "force_path_style": { + "name": "force_path_style", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_checked_at": { + "name": "status_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_options": { + "name": "system_options", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_invite_links": { + "name": "team_invite_links", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "team_invite_links_token_unique": { + "name": "team_invite_links_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_dead_properties": { + "name": "webdav_dead_properties", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_dead_properties_resource_prop_uniq": { + "name": "webdav_dead_properties_resource_prop_uniq", + "columns": [ + "org_id", + "resource_path", + "namespace", + "name" + ], + "isUnique": true + }, + "webdav_dead_properties_resource_idx": { + "name": "webdav_dead_properties_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_locks": { + "name": "webdav_locks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "depth": { + "name": "depth", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'infinity'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_locks_token_unique": { + "name": "webdav_locks_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "webdav_locks_resource_idx": { + "name": "webdav_locks_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + }, + "webdav_locks_expires_idx": { + "name": "webdav_locks_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook_events": { + "name": "webhook_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cloud'" + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'order.quota_changed'" + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webhook_events_source_event_uniq": { + "name": "webhook_events_source_event_uniq", + "columns": [ + "source", + "event_id" + ], + "isUnique": true + }, + "webhook_events_source_created_idx": { + "name": "webhook_events_source_created_idx", + "columns": [ + "source", + "created_at" + ], + "isUnique": false + }, + "webhook_events_status_idx": { + "name": "webhook_events_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "webhook_events_processed_idx": { + "name": "webhook_events_processed_idx", + "columns": [ + "processed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "account_issuer_providerAccountId_unique": { + "name": "account_issuer_providerAccountId_unique", + "columns": [ + "issuer", + "account_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "apikey": { + "name": "apikey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_request": { + "name": "last_request", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "apikey_config_id_idx": { + "name": "apikey_config_id_idx", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "apikey_reference_id_idx": { + "name": "apikey_reference_id_idx", + "columns": [ + "reference_id" + ], + "isUnique": false + }, + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": [ + "key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "deviceCode": { + "name": "deviceCode", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "deviceCode_device_code_idx": { + "name": "deviceCode_device_code_idx", + "columns": [ + "device_code" + ], + "isUnique": false + }, + "deviceCode_user_code_idx": { + "name": "deviceCode_user_code_idx", + "columns": [ + "user_code" + ], + "isUnique": false + }, + "deviceCode_status_idx": { + "name": "deviceCode_status_idx", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloader_bootstrap_credentials": { + "name": "downloader_bootstrap_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "downloader_bootstrap_credentials_token_hash_unique": { + "name": "downloader_bootstrap_credentials_token_hash_unique", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "downloader_bootstrap_token_hash_idx": { + "name": "downloader_bootstrap_token_hash_idx", + "columns": [ + "token_hash" + ], + "isUnique": false + }, + "downloader_bootstrap_user_idx": { + "name": "downloader_bootstrap_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "downloader_bootstrap_consumed_idx": { + "name": "downloader_bootstrap_consumed_idx", + "columns": [ + "consumed_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "downloader_bootstrap_credentials_user_id_user_id_fk": { + "name": "downloader_bootstrap_credentials_user_id_user_id_fk", + "tableFrom": "downloader_bootstrap_credentials", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "crv": { + "name": "crv", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "member": { + "name": "member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthAccessToken": { + "name": "oauthAccessToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthAccessToken_token_unique": { + "name": "oauthAccessToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthAccessToken_client_id_idx": { + "name": "oauthAccessToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthAccessToken_session_id_idx": { + "name": "oauthAccessToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthAccessToken_user_id_idx": { + "name": "oauthAccessToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthAccessToken_refresh_id_idx": { + "name": "oauthAccessToken_refresh_id_idx", + "columns": [ + "refresh_id" + ], + "isUnique": false + }, + "oauthAccessToken_token_idx": { + "name": "oauthAccessToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthAccessToken_client_id_oauthClient_client_id_fk": { + "name": "oauthAccessToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_session_id_session_id_fk": { + "name": "oauthAccessToken_session_id_session_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthAccessToken_user_id_user_id_fk": { + "name": "oauthAccessToken_user_id_user_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk": { + "name": "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthRefreshToken", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClient": { + "name": "oauthClient", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contacts": { + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens": { + "name": "dpop_bound_access_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "oauthClient_client_id_unique": { + "name": "oauthClient_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + }, + "oauthClient_client_id_idx": { + "name": "oauthClient_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClient_user_id_idx": { + "name": "oauthClient_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClient_user_id_user_id_fk": { + "name": "oauthClient_user_id_user_id_fk", + "tableFrom": "oauthClient", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientAssertion": { + "name": "oauthClientAssertion", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientResource": { + "name": "oauthClientResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthClientResource_client_id_idx": { + "name": "oauthClientResource_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClientResource_resource_id_idx": { + "name": "oauthClientResource_resource_id_idx", + "columns": [ + "resource_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClientResource_client_id_oauthClient_client_id_fk": { + "name": "oauthClientResource_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthClientResource_resource_id_oauthResource_identifier_fk": { + "name": "oauthClientResource_resource_id_oauthResource_identifier_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthResource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "identifier" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthConsent": { + "name": "oauthConsent", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthConsent_client_id_idx": { + "name": "oauthConsent_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthConsent_user_id_idx": { + "name": "oauthConsent_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthConsent_client_id_oauthClient_client_id_fk": { + "name": "oauthConsent_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthConsent_user_id_user_id_fk": { + "name": "oauthConsent_user_id_user_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthRefreshToken": { + "name": "oauthRefreshToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_response": { + "name": "rotation_replay_response", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_expires_at": { + "name": "rotation_replay_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_time": { + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthRefreshToken_token_unique": { + "name": "oauthRefreshToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthRefreshToken_client_id_idx": { + "name": "oauthRefreshToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthRefreshToken_session_id_idx": { + "name": "oauthRefreshToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthRefreshToken_user_id_idx": { + "name": "oauthRefreshToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthRefreshToken_token_idx": { + "name": "oauthRefreshToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthRefreshToken_client_id_oauthClient_client_id_fk": { + "name": "oauthRefreshToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthRefreshToken_session_id_session_id_fk": { + "name": "oauthRefreshToken_session_id_session_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthRefreshToken_user_id_user_id_fk": { + "name": "oauthRefreshToken_user_id_user_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthResource": { + "name": "oauthResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_ttl": { + "name": "access_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_ttl": { + "name": "refresh_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_algorithm": { + "name": "signing_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_key_id": { + "name": "signing_key_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_scopes": { + "name": "allowed_scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_claims": { + "name": "custom_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens_required": { + "name": "dpop_bound_access_tokens_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "policy_version": { + "name": "policy_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthResource_identifier_unique": { + "name": "oauthResource_identifier_unique", + "columns": [ + "identifier" + ], + "isUnique": true + }, + "oauthResource_identifier_idx": { + "name": "oauthResource_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization": { + "name": "organization", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "session_created_idx": { + "name": "session_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned": { + "name": "banned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_created_idx": { + "name": "user_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "user_lastActiveAt_idx": { + "name": "user_lastActiveAt_idx", + "columns": [ + "last_active_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "matters_webdav_children_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + }, + "matters_list_page_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/0085_snapshot.json b/migrations/meta/0085_snapshot.json new file mode 100644 index 00000000..28ceac5b --- /dev/null +++ b/migrations/meta/0085_snapshot.json @@ -0,0 +1,6109 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1ac145c8-4c7c-4212-95be-aa0e33c64ce9", + "prevId": "58a6328a-feb3-43bd-a119-e1c7637041b4", + "tables": { + "announcements": { + "name": "announcements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "announcements_status_priority_idx": { + "name": "announcements_status_priority_idx", + "columns": [ + "status", + "priority" + ], + "isUnique": false + }, + "announcements_published_idx": { + "name": "announcements_published_idx", + "columns": [ + "published_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_events": { + "name": "audit_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_name": { + "name": "target_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actor_ref": { + "name": "actor_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "audit_events_org_created_idx": { + "name": "audit_events_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_user_created_idx": { + "name": "audit_events_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_action_created_idx": { + "name": "audit_events_action_created_idx", + "columns": [ + "action", + "created_at" + ], + "isUnique": false + }, + "audit_events_target_created_idx": { + "name": "audit_events_target_created_idx", + "columns": [ + "target_type", + "target_id", + "created_at" + ], + "isUnique": false + }, + "audit_events_created_idx": { + "name": "audit_events_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "background_jobs": { + "name": "background_jobs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_path": { + "name": "target_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "input_bytes": { + "name": "input_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "output_bytes": { + "name": "output_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "processed_bytes": { + "name": "processed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "current_filename": { + "name": "current_filename", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_metadata": { + "name": "result_metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retryable": { + "name": "retryable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "cancelable": { + "name": "cancelable", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "retried_from_job_id": { + "name": "retried_from_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "background_jobs_org_created_idx": { + "name": "background_jobs_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_status_idx": { + "name": "background_jobs_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "background_jobs_org_type_idx": { + "name": "background_jobs_org_type_idx", + "columns": [ + "org_id", + "type" + ], + "isUnique": false + }, + "background_jobs_created_idx": { + "name": "background_jobs_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "background_jobs_org_page_idx": { + "name": "background_jobs_org_page_idx", + "columns": [ + "org_id", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "cloud_traffic_reports": { + "name": "cloud_traffic_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "period": { + "name": "period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "cloud_traffic_reports_event_uniq": { + "name": "cloud_traffic_reports_event_uniq", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "cloud_traffic_reports_org_period_idx": { + "name": "cloud_traffic_reports_org_period_idx", + "columns": [ + "org_id", + "period" + ], + "isUnique": false + }, + "cloud_traffic_reports_status_idx": { + "name": "cloud_traffic_reports_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "cloud_traffic_reports_retry_idx": { + "name": "cloud_traffic_reports_retry_idx", + "columns": [ + "status", + "next_retry_at", + "created_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_issued_idx": { + "name": "cloud_traffic_reports_issued_idx", + "columns": [ + "issued_at" + ], + "isUnique": false + }, + "cloud_traffic_reports_updated_idx": { + "name": "cloud_traffic_reports_updated_idx", + "columns": [ + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "download_tasks": { + "name": "download_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_uri": { + "name": "source_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "target_folder": { + "name": "target_folder", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "assigned_downloader_id": { + "name": "assigned_downloader_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "billing_authorized_bytes": { + "name": "billing_authorized_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_bytes": { + "name": "billing_charged_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_charged_credits": { + "name": "billing_charged_credits", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "billing_status": { + "name": "billing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "result_object_id": { + "name": "result_object_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "runtime": { + "name": "runtime", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "events": { + "name": "events", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "resolve_started_at": { + "name": "resolve_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolve_completed_at": { + "name": "resolve_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_completed_at": { + "name": "download_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_started_at": { + "name": "ingest_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ingest_completed_at": { + "name": "ingest_completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_started_at": { + "name": "seeding_started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "seeding_stopped_at": { + "name": "seeding_stopped_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "finished_at": { + "name": "finished_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "download_tasks_org_created_idx": { + "name": "download_tasks_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_status_idx": { + "name": "download_tasks_org_status_idx", + "columns": [ + "org_id", + "status" + ], + "isUnique": false + }, + "download_tasks_org_category_idx": { + "name": "download_tasks_org_category_idx", + "columns": [ + "org_id", + "category" + ], + "isUnique": false + }, + "download_tasks_org_tags_idx": { + "name": "download_tasks_org_tags_idx", + "columns": [ + "org_id", + "tags" + ], + "isUnique": false + }, + "download_tasks_downloader_idx": { + "name": "download_tasks_downloader_idx", + "columns": [ + "assigned_downloader_id", + "status" + ], + "isUnique": false + }, + "download_tasks_created_idx": { + "name": "download_tasks_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "download_tasks_finished_idx": { + "name": "download_tasks_finished_idx", + "columns": [ + "finished_at" + ], + "isUnique": false + }, + "download_tasks_org_deleted_created_idx": { + "name": "download_tasks_org_deleted_created_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at" + ], + "isUnique": false + }, + "download_tasks_org_page_idx": { + "name": "download_tasks_org_page_idx", + "columns": [ + "org_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + }, + "download_tasks_downloader_page_idx": { + "name": "download_tasks_downloader_page_idx", + "columns": [ + "assigned_downloader_id", + "deleted_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloaders": { + "name": "downloaders", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_jti": { + "name": "token_jti", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "arch": { + "name": "arch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "engine": { + "name": "engine", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'http'" + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "max_concurrent_tasks": { + "name": "max_concurrent_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "current_tasks": { + "name": "current_tasks", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "download_bps": { + "name": "download_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "upload_bps": { + "name": "upload_bps", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "free_disk_bytes": { + "name": "free_disk_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remote_download_credit_billing_enabled": { + "name": "remote_download_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "remote_download_credit_unit_bytes": { + "name": "remote_download_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "remote_download_credit_per_unit": { + "name": "remote_download_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_heartbeat_at": { + "name": "last_heartbeat_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "downloaders_token_jti_unique": { + "name": "downloaders_token_jti_unique", + "columns": [ + "token_jti" + ], + "isUnique": true + }, + "downloaders_status_idx": { + "name": "downloaders_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "downloaders_enabled_idx": { + "name": "downloaders_enabled_idx", + "columns": [ + "enabled" + ], + "isUnique": false + }, + "downloaders_created_idx": { + "name": "downloaders_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hosting_configs": { + "name": "image_hosting_configs", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_provider": { + "name": "domain_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "provider_hostname_id": { + "name": "provider_hostname_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_status": { + "name": "domain_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_error": { + "name": "domain_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_last_checked_at": { + "name": "domain_last_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_verified_at": { + "name": "domain_verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "referer_allowlist": { + "name": "referer_allowlist", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hosting_configs_custom_domain_unique": { + "name": "image_hosting_configs_custom_domain_unique", + "columns": [ + "custom_domain" + ], + "isUnique": true + } + }, + "foreignKeys": { + "image_hosting_configs_org_id_organization_id_fk": { + "name": "image_hosting_configs_org_id_organization_id_fk", + "tableFrom": "image_hosting_configs", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "image_hostings": { + "name": "image_hostings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime": { + "name": "mime", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "image_hostings_token_unique": { + "name": "image_hostings_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "image_hostings_org_path_uniq": { + "name": "image_hostings_org_path_uniq", + "columns": [ + "org_id", + "path" + ], + "isUnique": true, + "where": "\"image_hostings\".\"purged_at\" IS NULL" + }, + "image_hostings_org_created_idx": { + "name": "image_hostings_org_created_idx", + "columns": [ + "org_id", + "created_at" + ], + "isUnique": false + }, + "image_hostings_page_idx": { + "name": "image_hostings_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "created_at", + "id" + ], + "isUnique": false + }, + "image_hostings_token_idx": { + "name": "image_hostings_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "image_hostings_org_id_organization_id_fk": { + "name": "image_hostings_org_id_organization_id_fk", + "tableFrom": "image_hostings", + "tableTo": "organization", + "columnsFrom": [ + "org_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "image_hostings_storage_id_storages_id_fk": { + "name": "image_hostings_storage_id_storages_id_fk", + "tableFrom": "image_hostings", + "tableTo": "storages", + "columnsFrom": [ + "storage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invite_codes": { + "name": "invite_codes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used_by": { + "name": "used_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "used_at": { + "name": "used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invite_codes_code_unique": { + "name": "invite_codes_code_unique", + "columns": [ + "code" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "license_bindings": { + "name": "license_bindings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "cloud_binding_id": { + "name": "cloud_binding_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_store_id": { + "name": "cloud_store_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_id": { + "name": "cloud_account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cloud_account_email": { + "name": "cloud_account_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate": { + "name": "cached_certificate", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cached_certificate_expires_at": { + "name": "cached_certificate_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bound_at": { + "name": "bound_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "disconnected_at": { + "name": "disconnected_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_at": { + "name": "last_refresh_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refresh_error": { + "name": "last_refresh_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "license_bindings_active_uniq": { + "name": "license_bindings_active_uniq", + "columns": [ + "status" + ], + "isUnique": true, + "where": "status = 'active'" + }, + "license_bindings_cloud_binding_idx": { + "name": "license_bindings_cloud_binding_idx", + "columns": [ + "cloud_binding_id" + ], + "isUnique": false + }, + "license_bindings_instance_idx": { + "name": "license_bindings_instance_idx", + "columns": [ + "instance_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "matters": { + "name": "matters", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alias": { + "name": "alias", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "dirtype": { + "name": "dirtype", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "parent": { + "name": "parent", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "object": { + "name": "object", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "trashed_at": { + "name": "trashed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "purged_at": { + "name": "purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "matters_alias_unique": { + "name": "matters_alias_unique", + "columns": [ + "alias" + ], + "isUnique": true + }, + "matters_status_dir_created_idx": { + "name": "matters_status_dir_created_idx", + "columns": [ + "status", + "dirtype", + "created_at" + ], + "isUnique": false + }, + "matters_webdav_path_idx": { + "name": "matters_webdav_path_idx", + "columns": [ + "org_id", + "parent", + "name", + "status", + "trashed_at", + "purged_at" + ], + "isUnique": false + }, + "matters_webdav_children_idx": { + "name": "matters_webdav_children_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "name" + ], + "isUnique": false + }, + "matters_list_page_idx": { + "name": "matters_list_page_idx", + "columns": [ + "org_id", + "parent", + "status", + "trashed_at", + "purged_at", + "\"dirtype\" desc", + "created_at", + "id" + ], + "isUnique": false + }, + "matters_trash_page_idx": { + "name": "matters_trash_page_idx", + "columns": [ + "org_id", + "status", + "purged_at", + "trashed_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications": { + "name": "notifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "ref_type": { + "name": "ref_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "read_at": { + "name": "read_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notifications_user_created_idx": { + "name": "notifications_user_created_idx", + "columns": [ + "user_id", + "created_at" + ], + "isUnique": false + }, + "notifications_user_read_idx": { + "name": "notifications_user_read_idx", + "columns": [ + "user_id", + "read_at" + ], + "isUnique": false + }, + "notifications_user_page_idx": { + "name": "notifications_user_page_idx", + "columns": [ + "user_id", + "read_at", + "created_at", + "id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "object_upload_sessions": { + "name": "object_upload_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "object_id": { + "name": "object_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "upload_id": { + "name": "upload_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "on_conflict": { + "name": "on_conflict", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'fail'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "object_upload_sessions_object_idx": { + "name": "object_upload_sessions_object_idx", + "columns": [ + "org_id", + "object_id" + ], + "isUnique": false + }, + "object_upload_sessions_expires_idx": { + "name": "object_upload_sessions_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quota_entitlements": { + "name": "org_quota_entitlements", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entitlement_type": { + "name": "entitlement_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'grant'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "starts_at": { + "name": "starts_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "org_quota_entitlements_org_resource_idx": { + "name": "org_quota_entitlements_org_resource_idx", + "columns": [ + "org_id", + "resource_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_org_type_idx": { + "name": "org_quota_entitlements_org_type_idx", + "columns": [ + "org_id", + "resource_type", + "entitlement_type", + "status" + ], + "isUnique": false + }, + "org_quota_entitlements_active_plan_uniq": { + "name": "org_quota_entitlements_active_plan_uniq", + "columns": [ + "org_id", + "resource_type", + "entitlement_type" + ], + "isUnique": true, + "where": "status = 'active' AND entitlement_type = 'plan' AND source <> 'free_plan'" + }, + "org_quota_entitlements_source_resource_uniq": { + "name": "org_quota_entitlements_source_resource_uniq", + "columns": [ + "source", + "source_id", + "resource_type" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "org_quotas": { + "name": "org_quotas", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quota": { + "name": "quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_quota": { + "name": "traffic_quota", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_used": { + "name": "traffic_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "traffic_period": { + "name": "traffic_period", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'1970-01'" + } + }, + "indexes": { + "org_quotas_org_uniq": { + "name": "org_quotas_org_uniq", + "columns": [ + "org_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "remote_download_usage_reports": { + "name": "remote_download_usage_reports", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "downloader_id": { + "name": "downloader_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_index": { + "name": "unit_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unit_bytes": { + "name": "unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "credits_per_unit": { + "name": "credits_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "remote_download_usage_reports_event_id_unique": { + "name": "remote_download_usage_reports_event_id_unique", + "columns": [ + "event_id" + ], + "isUnique": true + }, + "remote_download_usage_task_unit_uniq": { + "name": "remote_download_usage_task_unit_uniq", + "columns": [ + "task_id", + "unit_index" + ], + "isUnique": true + }, + "remote_download_usage_org_idx": { + "name": "remote_download_usage_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + }, + "remote_download_usage_status_idx": { + "name": "remote_download_usage_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "remote_download_usage_created_idx": { + "name": "remote_download_usage_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "resource_changes": { + "name": "resource_changes", + "columns": { + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "change_type": { + "name": "change_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "resource_changes_scope_sequence_idx": { + "name": "resource_changes_scope_sequence_idx", + "columns": [ + "scope_type", + "scope_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_resource_sequence_idx": { + "name": "resource_changes_resource_sequence_idx", + "columns": [ + "resource_type", + "resource_id", + "sequence" + ], + "isUnique": false + }, + "resource_changes_occurred_idx": { + "name": "resource_changes_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "share_recipients": { + "name": "share_recipients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "share_id": { + "name": "share_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recipient_email": { + "name": "recipient_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "share_recipients_share_id_idx": { + "name": "share_recipients_share_id_idx", + "columns": [ + "share_id" + ], + "isUnique": false + }, + "share_recipients_user_id_idx": { + "name": "share_recipients_user_id_idx", + "columns": [ + "recipient_user_id" + ], + "isUnique": false + }, + "share_recipients_email_idx": { + "name": "share_recipients_email_idx", + "columns": [ + "recipient_email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "shares": { + "name": "shares", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "matter_id": { + "name": "matter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "download_limit": { + "name": "download_limit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "downloads": { + "name": "downloads", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "private": { + "name": "private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "shares_token_unique": { + "name": "shares_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "shares_creator_status_created_idx": { + "name": "shares_creator_status_created_idx", + "columns": [ + "creator_id", + "status", + "created_at", + "id" + ], + "isUnique": false + }, + "shares_creator_private_created_idx": { + "name": "shares_creator_private_created_idx", + "columns": [ + "creator_id", + "private", + "created_at" + ], + "isUnique": false + }, + "shares_created_idx": { + "name": "shares_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "site_invitations": { + "name": "site_invitations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "accepted_by": { + "name": "accepted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "site_invitations_token_unique": { + "name": "site_invitations_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "site_invitations_email_idx": { + "name": "site_invitations_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "site_invitations_created_idx": { + "name": "site_invitations_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "site_invitations_expires_idx": { + "name": "site_invitations_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "stats_rollups_hourly": { + "name": "stats_rollups_hourly", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "bucket_start": { + "name": "bucket_start", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "metric_key": { + "name": "metric_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dimension_key": { + "name": "dimension_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "dimension_value": { + "name": "dimension_value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "unique_count": { + "name": "unique_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "stats_rollups_hourly_bucket_metric_dim_uniq": { + "name": "stats_rollups_hourly_bucket_metric_dim_uniq", + "columns": [ + "bucket_start", + "org_id", + "metric_key", + "dimension_key", + "dimension_value" + ], + "isUnique": true + }, + "stats_rollups_hourly_metric_bucket_idx": { + "name": "stats_rollups_hourly_metric_bucket_idx", + "columns": [ + "metric_key", + "bucket_start" + ], + "isUnique": false + }, + "stats_rollups_hourly_dimension_bucket_idx": { + "name": "stats_rollups_hourly_dimension_bucket_idx", + "columns": [ + "metric_key", + "dimension_key", + "bucket_start" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_breakdowns": { + "name": "storage_usage_breakdowns", + "columns": { + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bytes": { + "name": "bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "file_count": { + "name": "file_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_breakdowns_org_category_uniq": { + "name": "storage_usage_breakdowns_org_category_uniq", + "columns": [ + "org_id", + "category" + ], + "isUnique": true + }, + "storage_usage_breakdowns_org_idx": { + "name": "storage_usage_breakdowns_org_idx", + "columns": [ + "org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storage_usage_ledger": { + "name": "storage_usage_ledger", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "storage_id": { + "name": "storage_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delta_bytes": { + "name": "delta_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "storage_usage_ledger_event_key_unique": { + "name": "storage_usage_ledger_event_key_unique", + "columns": [ + "event_key" + ], + "isUnique": true + }, + "storage_usage_ledger_occurred_idx": { + "name": "storage_usage_ledger_occurred_idx", + "columns": [ + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_org_occurred_idx": { + "name": "storage_usage_ledger_org_occurred_idx", + "columns": [ + "org_id", + "occurred_at" + ], + "isUnique": false + }, + "storage_usage_ledger_storage_occurred_idx": { + "name": "storage_usage_ledger_storage_occurred_idx", + "columns": [ + "storage_id", + "occurred_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "storages": { + "name": "storages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'auto'" + }, + "access_key": { + "name": "access_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "secret_key": { + "name": "secret_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "custom_host": { + "name": "custom_host", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "capacity": { + "name": "capacity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "egress_credit_billing_enabled": { + "name": "egress_credit_billing_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "egress_credit_unit_bytes": { + "name": "egress_credit_unit_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 104857600 + }, + "egress_credit_per_unit": { + "name": "egress_credit_per_unit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "force_path_style": { + "name": "force_path_style", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'unknown'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_checked_at": { + "name": "status_checked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "system_options": { + "name": "system_options", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_invite_links": { + "name": "team_invite_links", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "team_invite_links_token_unique": { + "name": "team_invite_links_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_dead_properties": { + "name": "webdav_dead_properties", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_dead_properties_resource_prop_uniq": { + "name": "webdav_dead_properties_resource_prop_uniq", + "columns": [ + "org_id", + "resource_path", + "namespace", + "name" + ], + "isUnique": true + }, + "webdav_dead_properties_resource_idx": { + "name": "webdav_dead_properties_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webdav_locks": { + "name": "webdav_locks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "org_id": { + "name": "org_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_path": { + "name": "resource_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "depth": { + "name": "depth", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'infinity'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "webdav_locks_token_unique": { + "name": "webdav_locks_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "webdav_locks_resource_idx": { + "name": "webdav_locks_resource_idx", + "columns": [ + "org_id", + "resource_path" + ], + "isUnique": false + }, + "webdav_locks_expires_idx": { + "name": "webdav_locks_expires_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook_events": { + "name": "webhook_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'cloud'" + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'order.quota_changed'" + }, + "payload_hash": { + "name": "payload_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "processed_at": { + "name": "processed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "webhook_events_source_event_uniq": { + "name": "webhook_events_source_event_uniq", + "columns": [ + "source", + "event_id" + ], + "isUnique": true + }, + "webhook_events_source_created_idx": { + "name": "webhook_events_source_created_idx", + "columns": [ + "source", + "created_at" + ], + "isUnique": false + }, + "webhook_events_status_idx": { + "name": "webhook_events_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "webhook_events_processed_idx": { + "name": "webhook_events_processed_idx", + "columns": [ + "processed_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "account_issuer_providerAccountId_unique": { + "name": "account_issuer_providerAccountId_unique", + "columns": [ + "issuer", + "account_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "apikey": { + "name": "apikey", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_request": { + "name": "last_request", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "apikey_config_id_idx": { + "name": "apikey_config_id_idx", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "apikey_reference_id_idx": { + "name": "apikey_reference_id_idx", + "columns": [ + "reference_id" + ], + "isUnique": false + }, + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": [ + "key" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "deviceCode": { + "name": "deviceCode", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_polled_at": { + "name": "last_polled_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "polling_interval": { + "name": "polling_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "deviceCode_device_code_idx": { + "name": "deviceCode_device_code_idx", + "columns": [ + "device_code" + ], + "isUnique": false + }, + "deviceCode_user_code_idx": { + "name": "deviceCode_user_code_idx", + "columns": [ + "user_code" + ], + "isUnique": false + }, + "deviceCode_status_idx": { + "name": "deviceCode_status_idx", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "downloader_bootstrap_credentials": { + "name": "downloader_bootstrap_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "device_code": { + "name": "device_code", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "downloader_bootstrap_credentials_token_hash_unique": { + "name": "downloader_bootstrap_credentials_token_hash_unique", + "columns": [ + "token_hash" + ], + "isUnique": true + }, + "downloader_bootstrap_token_hash_idx": { + "name": "downloader_bootstrap_token_hash_idx", + "columns": [ + "token_hash" + ], + "isUnique": false + }, + "downloader_bootstrap_user_idx": { + "name": "downloader_bootstrap_user_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "downloader_bootstrap_consumed_idx": { + "name": "downloader_bootstrap_consumed_idx", + "columns": [ + "consumed_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "downloader_bootstrap_credentials_user_id_user_id_fk": { + "name": "downloader_bootstrap_credentials_user_id_user_id_fk", + "tableFrom": "downloader_bootstrap_credentials", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "alg": { + "name": "alg", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "crv": { + "name": "crv", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "member": { + "name": "member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthAccessToken": { + "name": "oauthAccessToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthAccessToken_token_unique": { + "name": "oauthAccessToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthAccessToken_client_id_idx": { + "name": "oauthAccessToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthAccessToken_session_id_idx": { + "name": "oauthAccessToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthAccessToken_user_id_idx": { + "name": "oauthAccessToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthAccessToken_refresh_id_idx": { + "name": "oauthAccessToken_refresh_id_idx", + "columns": [ + "refresh_id" + ], + "isUnique": false + }, + "oauthAccessToken_token_idx": { + "name": "oauthAccessToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthAccessToken_client_id_oauthClient_client_id_fk": { + "name": "oauthAccessToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_session_id_session_id_fk": { + "name": "oauthAccessToken_session_id_session_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthAccessToken_user_id_user_id_fk": { + "name": "oauthAccessToken_user_id_user_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk": { + "name": "oauthAccessToken_refresh_id_oauthRefreshToken_id_fk", + "tableFrom": "oauthAccessToken", + "tableTo": "oauthRefreshToken", + "columnsFrom": [ + "refresh_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClient": { + "name": "oauthClient", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contacts": { + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_uri": { + "name": "backchannel_logout_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "backchannel_logout_session_required": { + "name": "backchannel_logout_session_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks": { + "name": "jwks", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "jwks_uri": { + "name": "jwks_uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens": { + "name": "dpop_bound_access_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "oauthClient_client_id_unique": { + "name": "oauthClient_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + }, + "oauthClient_client_id_idx": { + "name": "oauthClient_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClient_user_id_idx": { + "name": "oauthClient_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClient_user_id_user_id_fk": { + "name": "oauthClient_user_id_user_id_fk", + "tableFrom": "oauthClient", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientAssertion": { + "name": "oauthClientAssertion", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthClientResource": { + "name": "oauthClientResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthClientResource_client_id_idx": { + "name": "oauthClientResource_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthClientResource_resource_id_idx": { + "name": "oauthClientResource_resource_id_idx", + "columns": [ + "resource_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthClientResource_client_id_oauthClient_client_id_fk": { + "name": "oauthClientResource_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthClientResource_resource_id_oauthResource_identifier_fk": { + "name": "oauthClientResource_resource_id_oauthResource_identifier_fk", + "tableFrom": "oauthClientResource", + "tableTo": "oauthResource", + "columnsFrom": [ + "resource_id" + ], + "columnsTo": [ + "identifier" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthConsent": { + "name": "oauthConsent", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthConsent_client_id_idx": { + "name": "oauthConsent_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthConsent_user_id_idx": { + "name": "oauthConsent_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthConsent_client_id_oauthClient_client_id_fk": { + "name": "oauthConsent_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthConsent_user_id_user_id_fk": { + "name": "oauthConsent_user_id_user_id_fk", + "tableFrom": "oauthConsent", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthJwtRevocation": { + "name": "oauthJwtRevocation", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthJwtRevocation_expires_at_idx": { + "name": "oauthJwtRevocation_expires_at_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthRefreshToken": { + "name": "oauthRefreshToken", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorization_code_id": { + "name": "authorization_code_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resources": { + "name": "resources", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "requested_user_info_claims": { + "name": "requested_user_info_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_response": { + "name": "rotation_replay_response", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotation_replay_expires_at": { + "name": "rotation_replay_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_time": { + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirmation": { + "name": "confirmation", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauthRefreshToken_token_unique": { + "name": "oauthRefreshToken_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "oauthRefreshToken_client_id_idx": { + "name": "oauthRefreshToken_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauthRefreshToken_session_id_idx": { + "name": "oauthRefreshToken_session_id_idx", + "columns": [ + "session_id" + ], + "isUnique": false + }, + "oauthRefreshToken_user_id_idx": { + "name": "oauthRefreshToken_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauthRefreshToken_token_idx": { + "name": "oauthRefreshToken_token_idx", + "columns": [ + "token" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauthRefreshToken_client_id_oauthClient_client_id_fk": { + "name": "oauthRefreshToken_client_id_oauthClient_client_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "oauthClient", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauthRefreshToken_session_id_session_id_fk": { + "name": "oauthRefreshToken_session_id_session_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauthRefreshToken_user_id_user_id_fk": { + "name": "oauthRefreshToken_user_id_user_id_fk", + "tableFrom": "oauthRefreshToken", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauthResource": { + "name": "oauthResource", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_ttl": { + "name": "access_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_ttl": { + "name": "refresh_token_ttl", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_algorithm": { + "name": "signing_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "signing_key_id": { + "name": "signing_key_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_scopes": { + "name": "allowed_scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "custom_claims": { + "name": "custom_claims", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dpop_bound_access_tokens_required": { + "name": "dpop_bound_access_tokens_required", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "policy_version": { + "name": "policy_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "oauthResource_identifier_unique": { + "name": "oauthResource_identifier_unique", + "columns": [ + "identifier" + ], + "isUnique": true + }, + "oauthResource_identifier_idx": { + "name": "oauthResource_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization": { + "name": "organization", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "session_created_idx": { + "name": "session_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "banned": { + "name": "banned", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_active_at": { + "name": "last_active_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + }, + "user_created_idx": { + "name": "user_created_idx", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "user_lastActiveAt_idx": { + "name": "user_lastActiveAt_idx", + "columns": [ + "last_active_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "matters_webdav_children_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + }, + "matters_list_page_idx": { + "columns": { + "\"dirtype\" desc": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 1ba4e335..210f4454 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -575,6 +575,27 @@ "when": 1785351402721, "tag": "0082_agent_oauth_consent_last_used_at", "breakpoints": true + }, + { + "idx": 83, + "version": "6", + "when": 1785388050472, + "tag": "0083_external-resource-oauth", + "breakpoints": true + }, + { + "idx": 84, + "version": "6", + "when": 1785388374965, + "tag": "0084_better-auth-account-issuer", + "breakpoints": true + }, + { + "idx": 85, + "version": "6", + "when": 1785388778835, + "tag": "0085_oauth-jwt-revocation", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 7a0d16bf..d2bbf0fa 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "lint:arch": "depcruise server/ shared/ --config .dependency-cruiser.cjs", "lint:http": "tsx scripts/lint-http-boundary.ts", "lint:spec": "node scripts/lint-spec.mjs", - "lint:zpan-skill": "node scripts/lint-zpan-skill.mjs", "prepare": "husky", "format": "biome format --write .", "e2e": "playwright test", @@ -57,8 +56,8 @@ "@aws-sdk/client-s3": "^3.1022.0", "@aws-sdk/s3-request-presigner": "^3.1022.0", "@azure/functions": "^4.12.0", - "@better-auth/api-key": "^1.6.14", - "@better-auth/oauth-provider": "1.6.14", + "@better-auth/api-key": "1.7.0-rc.2", + "@better-auth/oauth-provider": "1.7.0-rc.2", "@better-captcha/react": "^0.7.0", "@dnd-kit/core": "^6.3.1", "@hono/node-server": "^2.0.10", @@ -73,7 +72,7 @@ "@uiw/react-markdown-preview": "5.2.0", "@uiw/react-md-editor": "^4.1.0", "@vidstack/react": "^1.12.13", - "better-auth": "^1.6.14", + "better-auth": "1.7.0-rc.2", "better-sqlite3": "^12.10.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -84,6 +83,7 @@ "hono": "^4.12.27", "i18next": "^26.0.3", "i18next-browser-languagedetector": "^8.2.1", + "jose": "6.2.3", "lucide-react": "^0.577.0", "nanoid": "^5.1.11", "next-themes": "^0.4.6", @@ -168,13 +168,13 @@ }, "pnpm": { "overrides": { - "@better-auth/core": "1.6.14", - "@better-auth/drizzle-adapter": "1.6.14", - "@better-auth/kysely-adapter": "1.6.14", - "@better-auth/memory-adapter": "1.6.14", - "@better-auth/mongo-adapter": "1.6.14", - "@better-auth/prisma-adapter": "1.6.14", - "@better-auth/telemetry": "1.6.14", + "@better-auth/core": "1.7.0-rc.2", + "@better-auth/drizzle-adapter": "1.7.0-rc.2", + "@better-auth/kysely-adapter": "1.7.0-rc.2", + "@better-auth/memory-adapter": "1.7.0-rc.2", + "@better-auth/mongo-adapter": "1.7.0-rc.2", + "@better-auth/prisma-adapter": "1.7.0-rc.2", + "@better-auth/telemetry": "1.7.0-rc.2", "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7af97a8b..4bf9ad8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,13 +5,13 @@ settings: excludeLinksFromLockfile: false overrides: - '@better-auth/core': 1.6.14 - '@better-auth/drizzle-adapter': 1.6.14 - '@better-auth/kysely-adapter': 1.6.14 - '@better-auth/memory-adapter': 1.6.14 - '@better-auth/mongo-adapter': 1.6.14 - '@better-auth/prisma-adapter': 1.6.14 - '@better-auth/telemetry': 1.6.14 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/drizzle-adapter': 1.7.0-rc.2 + '@better-auth/kysely-adapter': 1.7.0-rc.2 + '@better-auth/memory-adapter': 1.7.0-rc.2 + '@better-auth/mongo-adapter': 1.7.0-rc.2 + '@better-auth/prisma-adapter': 1.7.0-rc.2 + '@better-auth/telemetry': 1.7.0-rc.2 '@vitest/expect': 4.1.4 '@vitest/mocker': 4.1.4 '@vitest/pretty-format': 4.1.4 @@ -36,11 +36,11 @@ importers: specifier: ^4.12.0 version: 4.12.0 '@better-auth/api-key': - specifier: ^1.6.14 - version: 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(better-auth@1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.5(zod@4.4.3)) + specifier: 1.7.0-rc.2 + version: 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(better-auth@1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.7(zod@4.4.3)) '@better-auth/oauth-provider': - specifier: 1.6.14 - version: 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-auth@1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.5(zod@4.4.3)) + specifier: 1.7.0-rc.2 + version: 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.7(zod@4.4.3)) '@better-captcha/react': specifier: ^0.7.0 version: 0.7.0(react@19.2.5)(typescript@5.9.3) @@ -84,8 +84,8 @@ importers: specifier: ^1.12.13 version: 1.12.13(@types/react@19.2.14)(react@19.2.5) better-auth: - specifier: ^1.6.14 - version: 1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) + specifier: 1.7.0-rc.2 + version: 1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) better-sqlite3: specifier: ^12.10.0 version: 12.10.0 @@ -116,6 +116,9 @@ importers: i18next-browser-languagedetector: specifier: ^8.2.1 version: 8.2.1 + jose: + specifier: 6.2.3 + version: 6.2.3 lucide-react: specifier: ^0.577.0 version: 0.577.0(react@19.2.5) @@ -568,22 +571,22 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@better-auth/api-key@1.6.14': - resolution: {integrity: sha512-iMLRcjpGyegI5yy375ZIw83HZGSZe6TwjtCKWdFTYy1PQ0bUcD0H61uKcvO82Co4jJmjakI3POR6lDy5W1OOew==} + '@better-auth/api-key@1.7.0-rc.2': + resolution: {integrity: sha512-7fyxJhOKKWQ+TtLTvCjxfZU+blE+jvwe4rzunz1RfOafuWEukE2ofRQ8uOMG4ZUYG6SO+tVKxsFDnbJZj2Da3g==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 - better-auth: ^1.6.14 - better-call: 1.3.5 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 + better-auth: ^1.7.0-rc.2 + better-call: 1.3.7 - '@better-auth/core@1.6.14': - resolution: {integrity: sha512-12cA7tnR4Wyb3nLpPmeq/Id7QNB+4OhjbzuX7sIhqglgXGjyT5iiNpe2lx/8FF532sHC450Yx1850salCYbkzw==} + '@better-auth/core@1.7.0-rc.2': + resolution: {integrity: sha512-NreNGg68j4qUVVYTcC1DtvRTwSJdCavH5igrMyTO5ghZxnzL4G539uRIzOZmJ64MLzOyOwzWH+JHqpVaj0ZRxw==} peerDependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 '@cloudflare/workers-types': '>=4' '@opentelemetry/api': ^1.9.0 - better-call: 1.3.5 + better-call: 1.3.7 jose: ^6.1.0 kysely: ^0.28.5 || ^0.29.0 nanostores: ^1.0.1 @@ -593,56 +596,56 @@ packages: '@opentelemetry/api': optional: true - '@better-auth/drizzle-adapter@1.6.14': - resolution: {integrity: sha512-lYs1jDudriKYMXNcLFLAvEvOEKbeKBFdDciG4H8qZhV+3+yghGC3f/H5qtgTDc8mGBPV+2tEvVgYqReurOSmNw==} + '@better-auth/drizzle-adapter@1.7.0-rc.2': + resolution: {integrity: sha512-o6HCC8PCyvg1/BQaNWvJM7kO8svXWvuM++APj7ah+iEfFWcp0yklNQWLijDLu+PAaoKHwNMgDpmclDruirHdPA==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 - drizzle-orm: ^0.45.2 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 + drizzle-orm: ^0.45.2 || >=1.0.0-rc.1 <2.0.0 peerDependenciesMeta: drizzle-orm: optional: true - '@better-auth/kysely-adapter@1.6.14': - resolution: {integrity: sha512-A2+381gYADuZpgd98XQ39bnxLzbT03wnnDmSQIXp7XcE3hF093mGMk6rxlAhENVHH7JL2B0Tv2la2o6n+6ppyQ==} + '@better-auth/kysely-adapter@1.7.0-rc.2': + resolution: {integrity: sha512-g65JeOOseffsqHJXOM0/+SdPvojXzFPejVEFKuUatkdfXcw/l0zEiEkH38Ag5SWFaqfvog/wRPY/MK8PC/ODvg==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 kysely: ^0.28.17 || ^0.29.0 peerDependenciesMeta: kysely: optional: true - '@better-auth/memory-adapter@1.6.14': - resolution: {integrity: sha512-frtBTozi8qsBlypxp33dkiIZT2IOMvix3oh2qTTcBkK11ISsRSTUUadl7DbwXri2AEoooShsH6PSAput920J3Q==} + '@better-auth/memory-adapter@1.7.0-rc.2': + resolution: {integrity: sha512-ACP69pbSDnIYYcx/KEtRXpFmte6q0Adh3028pRP5aDydkmbcCc7cFiwnRMQuI/MY7aBfW0wefEbcEwOse61Hcg==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.14': - resolution: {integrity: sha512-meaZx712k9c0Cl6urwYZRNa3mAy3/leaYiSNt+hVaCOEPlgTDxzmYMNACvTTYXgh4eCpDVf5G7ZMEYBtejKQdw==} + '@better-auth/mongo-adapter@1.7.0-rc.2': + resolution: {integrity: sha512-/QeC23KheruIamhu4XIqtPLcvupoDXmSpPB9QvmVqQcb5oVjAILJo6kIbZtq/JYbaHjrIO7mpdH6404hyv7weg==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 mongodb: ^6.0.0 || ^7.0.0 peerDependenciesMeta: mongodb: optional: true - '@better-auth/oauth-provider@1.6.14': - resolution: {integrity: sha512-JL5UNKayERwRbYyZL7DsjOMtMjPWiOVnzUwztIuDNuYK5JZC2Pfm/16MdkEtic8+8YCv9qGK7dph/TTU5fdlKA==} + '@better-auth/oauth-provider@1.7.0-rc.2': + resolution: {integrity: sha512-fc3jCYwS/PaQyErOPqIUplqK456zhrmNWGnJPhDEF68merXBQN1OodUTzicZ3skFDpAv6MY3m5vk4D1Gz3R/oA==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 - better-auth: ^1.6.14 - better-call: 1.3.5 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + better-auth: ^1.7.0-rc.2 + better-call: 1.3.7 - '@better-auth/prisma-adapter@1.6.14': - resolution: {integrity: sha512-9b9wSqhCthMmOYo0QdX+N/cOv+fNck/JE5CZQuuWwEJl5QeoYhCZesXjts5VfLAPMIf6vKw3QNBrn0SVMXXi2Q==} + '@better-auth/prisma-adapter@1.7.0-rc.2': + resolution: {integrity: sha512-OFRJbg44ha2zD5lpXIKfoGBEwPe58YBhwIgKlfRuHpsZSpknaXOTvFIH2da8q8SK5L5mjYQ9vSsnX0oOf8gNDA==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 peerDependenciesMeta: @@ -651,15 +654,15 @@ packages: prisma: optional: true - '@better-auth/telemetry@1.6.14': - resolution: {integrity: sha512-ALi3cEx5eyrFY+TeAdhc1uq8FqJyGvzgvIo7GQZOqGqLZxHY9nte44WN++jBFGJJbsW3e4cgLj8dQK291s6wWQ==} + '@better-auth/telemetry@1.7.0-rc.2': + resolution: {integrity: sha512-sSZ+/FkG/axBjXVeF01LT+NQjT23TLwRwpdkcI8FJBWINNUCYhuxgdi05dv70MeX8iocfMhKxQ4EPgvt/eW/kQ==} peerDependencies: - '@better-auth/core': 1.6.14 - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/core': 1.7.0-rc.2 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 - '@better-auth/utils@0.4.1': - resolution: {integrity: sha512-SZBPRPF3z0nBvE5ygOkxae35wnnXPRShmqFo78S+qslLeFoPu/pMgnXAuNKFMMybac3tiLaVg1e3MQW5MC+1iA==} + '@better-auth/utils@0.4.2': + resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} '@better-captcha/core@0.7.0': resolution: {integrity: sha512-fOTHeBbhf7WzstFp67eeaKyvrnH+nvPfsy1fyz+cj8scn16pgOxuwHONdnr23dXEpfa9DngKJEq/4TNGvX8Mkw==} @@ -670,8 +673,8 @@ packages: react: ^18 || ^19 typescript: ^5.0.0 - '@better-fetch/fetch@1.1.21': - resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + '@better-fetch/fetch@1.3.1': + resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} '@biomejs/biome@2.4.11': resolution: {integrity: sha512-nWxHX8tf3Opb/qRgZpBbsTOqOodkbrkJ7S+JxJAruxOReaDPPmPuLBAGQ8vigyUgo0QBB+oQltNEAvalLcjggA==} @@ -3535,8 +3538,8 @@ packages: bcp-47-match@2.0.3: resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} - better-auth@1.6.14: - resolution: {integrity: sha512-c0/DvTQGDpgfj1knekCpQrg6PSWGDtfAtP7Ou6FkAhoE3RNnnIxLB5qKj6tRg53a1xsq93G6T68cNxrUZ7ZVmw==} + better-auth@1.7.0-rc.2: + resolution: {integrity: sha512-5KZrqbAsoQA8q1edmufaoF/CBbMjGb/BoPqyMTzXFyDeXNhk8pXO2xJkiDDeZcSGtyhUKXiDnD7hxh4sJVgYZw==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -3544,7 +3547,7 @@ packages: '@tanstack/react-start': ^1.0.0 '@tanstack/solid-start': ^1.0.0 better-sqlite3: ^12.0.0 - drizzle-kit: '>=0.31.4' + drizzle-kit: '>=0.31.4 || >=1.0.0-beta.1' drizzle-orm: ^0.45.2 mongodb: ^6.0.0 || ^7.0.0 mysql2: ^3.0.0 @@ -3597,8 +3600,8 @@ packages: vue: optional: true - better-call@1.3.5: - resolution: {integrity: sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA==} + better-call@1.3.7: + resolution: {integrity: sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w==} peerDependencies: zod: ^4.0.0 peerDependenciesMeta: @@ -6417,21 +6420,21 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/api-key@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(better-auth@1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.5(zod@4.4.3))': + '@better-auth/api-key@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(better-auth@1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.7(zod@4.4.3))': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 - better-auth: 1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) - better-call: 1.3.5(zod@4.4.3) + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + better-auth: 1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) + better-call: 1.3.7(zod@4.4.3) zod: 4.4.3 - '@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)': + '@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0)': dependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 '@opentelemetry/semantic-conventions': 1.41.1 '@standard-schema/spec': 1.1.0 - better-call: 1.3.5(zod@4.4.3) + better-call: 1.3.7(zod@4.4.3) jose: 6.2.3 kysely: 0.28.17 nanostores: 1.3.0 @@ -6440,52 +6443,52 @@ snapshots: '@cloudflare/workers-types': 4.20260606.1 '@opentelemetry/api': 1.9.1 - '@better-auth/drizzle-adapter@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))': + '@better-auth/drizzle-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 optionalDependencies: drizzle-orm: 0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17) - '@better-auth/kysely-adapter@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.28.17)': + '@better-auth/kysely-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.28.17)': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 optionalDependencies: kysely: 0.28.17 - '@better-auth/memory-adapter@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/memory-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/mongo-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 - '@better-auth/oauth-provider@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(better-auth@1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.5(zod@4.4.3))': + '@better-auth/oauth-provider@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-auth@1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4))(better-call@1.3.7(zod@4.4.3))': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 - better-auth: 1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) - better-call: 1.3.5(zod@4.4.3) + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 + better-auth: 1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4) + better-call: 1.3.7(zod@4.4.3) jose: 6.2.3 zod: 4.4.3 - '@better-auth/prisma-adapter@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)': + '@better-auth/prisma-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 - '@better-auth/telemetry@1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)': + '@better-auth/telemetry@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 - '@better-auth/utils@0.4.1': + '@better-auth/utils@0.4.2': dependencies: '@noble/hashes': 2.2.0 @@ -6497,7 +6500,7 @@ snapshots: react: 19.2.5 typescript: 5.9.3 - '@better-fetch/fetch@1.1.21': {} + '@better-fetch/fetch@1.3.1': {} '@biomejs/biome@2.4.11': optionalDependencies: @@ -8965,20 +8968,20 @@ snapshots: bcp-47-match@2.0.3: {} - better-auth@1.6.14(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4): + better-auth@1.7.0-rc.2(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-sqlite3@12.10.0)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vitest@4.1.4): dependencies: - '@better-auth/core': 1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) - '@better-auth/drizzle-adapter': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17)) - '@better-auth/kysely-adapter': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(kysely@0.28.17) - '@better-auth/memory-adapter': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/mongo-adapter': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/prisma-adapter': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1) - '@better-auth/telemetry': 1.6.14(@better-auth/core@1.6.14(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.5(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.1)(@better-fetch/fetch@1.1.21) - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0) + '@better-auth/drizzle-adapter': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260606.1)(@libsql/client@0.17.2)(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.10.0)(kysely@0.28.17)) + '@better-auth/kysely-adapter': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(kysely@0.28.17) + '@better-auth/memory-adapter': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2) + '@better-auth/telemetry': 1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@cloudflare/workers-types@4.20260606.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.3)(kysely@0.28.17)(nanostores@1.3.0))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.2.0 '@noble/hashes': 2.2.0 - better-call: 1.3.5(zod@4.4.3) + better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 jose: 6.2.3 kysely: 0.28.17 @@ -8995,10 +8998,10 @@ snapshots: - '@cloudflare/workers-types' - '@opentelemetry/api' - better-call@1.3.5(zod@4.4.3): + better-call@1.3.7(zod@4.4.3): dependencies: - '@better-auth/utils': 0.4.1 - '@better-fetch/fetch': 1.1.21 + '@better-auth/utils': 0.4.2 + '@better-fetch/fetch': 1.3.1 rou3: 0.7.12 set-cookie-parser: 3.1.0 optionalDependencies: diff --git a/scripts/db-reset.ts b/scripts/db-reset.ts index 459d9377..a1d9ce8d 100644 --- a/scripts/db-reset.ts +++ b/scripts/db-reset.ts @@ -67,14 +67,14 @@ if (githubClientId && githubClientSecret) { } // create storage -const storageRes = await app.request('/api/admin/storages', { +const storageRes = await app.request('/api/site/storages', { method: 'POST', headers: { 'Content-Type': 'application/json', Cookie: cookies }, body: JSON.stringify(storageConfig), }) if (!storageRes.ok) throw new Error(`create storage failed: ${storageRes.status} ${await storageRes.text()}`) -const storage = (await storageRes.json()) as { id: string; title: string } -console.log(`created storage: ${storage.title} (${storage.id})`) +const storage = (await storageRes.json()) as { id: string; bucket: string } +console.log(`created storage: ${storage.bucket} (${storage.id})`) console.log('\ndone!') @@ -92,7 +92,7 @@ function resetNode(): Platform { migrate(db, { migrationsFolder: './migrations' }) console.log('database migrated') - return { db, getEnv: (key) => process.env[key] } + return { db, getEnv: (key) => process.env[key], getBinding: () => undefined } } function resetD1(): Platform { @@ -111,7 +111,7 @@ function resetD1(): Platform { const sqlite = new Database(dbFile) const db = drizzle(sqlite, { schema: { ...schema, ...authSchema } }) - return { db, getEnv: (key) => process.env[key] } + return { db, getEnv: (key) => process.env[key], getBinding: () => undefined } } function findD1SqliteFile(): string { diff --git a/scripts/lint-zpan-skill.mjs b/scripts/lint-zpan-skill.mjs deleted file mode 100644 index ea86f01e..00000000 --- a/scripts/lint-zpan-skill.mjs +++ /dev/null @@ -1,203 +0,0 @@ -#!/usr/bin/env node - -import { readdirSync, readFileSync, statSync } from 'node:fs' -import { join, relative } from 'node:path' - -const ROOT = process.cwd() -const SKILL_DIR = join(ROOT, 'skills', 'zpan') - -function walkMarkdown(dir) { - const files = [] - for (const name of readdirSync(dir)) { - const full = join(dir, name) - const stat = statSync(full) - if (stat.isDirectory()) files.push(...walkMarkdown(full)) - else if (name.endsWith('.md')) files.push(full) - } - return files.sort() -} - -const files = walkMarkdown(SKILL_DIR) -const documents = files.map((file) => ({ - file, - rel: relative(ROOT, file), - text: readFileSync(file, 'utf8'), -})) -const corpus = documents.map((doc) => doc.text).join('\n\n') -const normalizedCorpus = corpus.toLowerCase() -const failures = [] - -function requireMatch(label, pattern) { - if (!pattern.test(corpus)) failures.push(`missing required contract: ${label}`) -} - -function requireText(label, text) { - if (!normalizedCorpus.includes(text.toLowerCase())) { - failures.push(`missing required contract: ${label}`) - } -} - -function forbidMatch(label, pattern) { - for (const doc of documents) { - for (const match of doc.text.matchAll(pattern)) { - const line = doc.text.slice(0, match.index).split('\n').length - failures.push(`forbidden contract text: ${label} (${doc.rel}:${line})`) - } - } -} - -function forbidUnsafeLine(label, pattern) { - const safePrefix = /\b(do not|don't|never|must not|not|no)\b/i - for (const doc of documents) { - const lines = doc.text.split('\n') - lines.forEach((lineText, index) => { - if (pattern.test(lineText) && !safePrefix.test(lineText)) { - failures.push(`unsafe contract guidance: ${label} (${doc.rel}:${index + 1})`) - } - }) - } -} - -function commandLines() { - return corpus - .split('\n') - .map((line) => line.trim()) - .filter((line) => line.startsWith('restish ') || /^RSH_PROFILE=\S+\s+restish\b/.test(line)) -} - -function requireCommandLine(label, pattern) { - if (!commandLines().some((line) => pattern.test(line))) { - failures.push(`missing executable command example: ${label}`) - } -} - -function forbidCommandLine(label, pattern) { - for (const doc of documents) { - const lines = doc.text.split('\n') - lines.forEach((lineText, index) => { - const line = lineText.trim() - if (/^(?:RSH_PROFILE=\S+\s+)?restish /.test(line) && pattern.test(line)) { - failures.push(`forbidden executable command: ${label} (${doc.rel}:${index + 1})`) - } - }) - } -} - -function validateSkillFrontmatter() { - const skill = documents.find((doc) => doc.rel === 'skills/zpan/SKILL.md') - if (!skill) { - failures.push('missing skills/zpan/SKILL.md') - return - } - const match = skill.text.match(/^---\n([\s\S]*?)\n---\n/) - if (!match) { - failures.push('missing Skill YAML frontmatter') - return - } - const keys = [...match[1].matchAll(/^([A-Za-z0-9_-]+):/gm)].map((entry) => entry[1]) - const extras = keys.filter((key) => key !== 'name' && key !== 'description') - if (extras.length > 0) { - failures.push(`unsupported Skill frontmatter key(s): ${extras.join(', ')}`) - } -} - -validateSkillFrontmatter() - -requireMatch('Restish v2.3 or later', /Restish v2\.3(?:\+| or later)/i) -requireText('connect exactly /api/openapi.json', '/api/openapi.json') -requireText('plugin install command', 'restish plugin install saltbo/zpan zpan') -requireText('upload command surface', 'restish zpan-upload') - -for (const command of [ - 'list-objects', - 'get-object', - 'create-object', - 'update-object', - 'copy-object', - 'transfer-object', - 'delete-object', - 'list-shares', - 'create-share', - 'revoke-share', - 'get-user-quota', - 'get-storage-usage', - 'list-download-tasks', - 'get-download-task', - 'list-download-task-events', -]) { - requireCommandLine(`restish zpan ${command}`, new RegExp(`\\brestish\\s+(?:--rsh-profile\\s+\\S+\\s+)?zpan\\s+${command}\\b`)) -} - -for (const operationId of ['createObject', 'presignObjectUploadParts', 'completeObjectUpload', 'abortObjectUpload']) { - requireText(`upload plugin validates ${operationId}`, operationId) -} - -requireCommandLine('list pagination uses --page-size', /\bzpan\s+list-objects\b.*\s--page-size\s+\d+/) -requireCommandLine('share pagination uses --page-size', /\bzpan\s+list-shares\b.*\s--page-size\s+\d+/) -requireCommandLine('task pagination uses --page-size', /\bzpan\s+list-download-tasks\b.*\s--page-size\s+\d+/) -requireCommandLine('create-object uses positional body input', /\bzpan\s+create-object\s+'[^']*\bname:/) -requireCommandLine('update-object uses positional body input', /\bzpan\s+update-object\s+\S+\s+'[^']*\bname:/) -requireCommandLine('copy-object uses positional body input', /\bzpan\s+copy-object\s+\S+\s+'[^']*\bparent:/) -requireCommandLine('transfer-object uses positional body input', /\bzpan\s+transfer-object\s+\S+\s+'[^']*\btargetOrgId:/) -requireCommandLine('create-share uses positional body input', /\bzpan\s+create-share\s+'[^']*\bmatterId:/) -requireCommandLine('revoke-share uses positional body input', /\bzpan\s+revoke-share\s+\S+\s+'[^']*\bstatus:\s*revoked/) -requireCommandLine('upload passes Restish and plugin profiles', /\bRSH_PROFILE=(\S+)\s+restish\s+zpan-upload\b.*\s--api\s+zpan\b.*\s--profile\s+\1\b/) - -requireText('reader profile', '`reader`') -requireText('file-manager profile', '`file-manager`') -requireText('publisher profile', '`publisher`') -requireText('ci profile', '`ci`') -requireMatch('least-privilege profile selection', /(least-privilege|narrowest) profile/i) -requireText('objects read scope', 'objects:read') -requireText('objects write scopes', 'objects:create') -requireText('share publishing scopes', 'shares:create') -requireText('environment-backed Agent API key', 'Environment-backed') - -requireMatch('OAuth authorization code with PKCE', /OAuth authorization code \+ PKCE|authorization code\s*\+\s*PKCE/i) -requireMatch('CI Agent API key guidance', /CI[\s\S]{0,240}Agent API key|Agent API key[\s\S]{0,240}CI/i) - -requireMatch('confirm target workspace', /confirm[\s\S]{0,120}workspace/i) -requireMatch('confirm conflict policy', /confirm[\s\S]{0,160}(conflict|overwrite|replace)/i) -requireMatch('confirm destructive delete', /confirm[\s\S]{0,160}(destructive|soft delete|delet)/i) -requireMatch('confirm permanent purge', /confirm[\s\S]{0,160}(purge|permanent)/i) -requireMatch('confirm public sharing', /confirm[\s\S]{0,160}public share/i) -requireMatch( - 'confirm plugin executable trust', - /(?:confirm|ask)[\s\S]{0,200}(trusted local executable|executable Restish plugin|plugin trust)/i, -) - -forbidMatch('agent OpenAPI document', /\/api\/openapi\.agent\.json/gi) -forbidMatch('standalone zpan file CLI', /standalone\s+`?zpan`?\s+file CLI/gi) - -const openApiDocs = [...corpus.matchAll(/\/api\/openapi(?:\.[a-z0-9-]+)?\.json/gi)].map((match) => match[0]) -for (const doc of openApiDocs) { - if (doc !== '/api/openapi.json') { - failures.push(`OpenAPI document must be exactly /api/openapi.json, found ${doc}`) - } -} - -forbidUnsafeLine('bearer-token paste flow', /\b(paste|copy\/paste|copy paste)\b.*\bbearer token\b/i) -forbidUnsafeLine('Agent device login as v2.9 flow', /\b(device authorization|device login|device flow)\b.*\bv2\.9\b/i) -forbidUnsafeLine('Skill-handled multipart orchestration', /\b(Skill|agent)\b.*\b(orchestrate|handle|implement)\b.*\bmultipart\b/i) -forbidUnsafeLine('Skill-handled ETag retry loop', /\b(Skill|agent)\b.*\b(ETag|ETags)\b.*\b(retry|retries|loop|loops)\b/i) -forbidUnsafeLine('presigned URL exposure', /\b(expose|return|print|show)\b.*\bpresigned URLs?\b/i) -forbidMatch('silent plugin install approval', /restish\s+plugin\s+install\s+saltbo\/zpan\s+zpan[^\n]*--yes/gi) -forbidMatch('old Restish list limit flag', /\brestish\s+(?:--rsh-profile\s+\S+\s+)?zpan\s+(?:list-objects|list-shares|list-download-tasks)\b[^\n]*\s--limit\b/gi) -forbidMatch('camelCase Restish command example', /\brestish\s+(?:--rsh-profile\s+\S+\s+)?zpan\s+(?:listObjects|getObject|createObject|updateObject|copyObject|transferObject|deleteObject|purgeTrashObject|listShares|createShare|revokeShare|getUserQuota|getStorageUsage|listDownloadTasks|getDownloadTask|listDownloadTaskEvents)\b/gi) -forbidMatch('profile template purge command', /\brestish\s+--rsh-profile\s+(?:reader|file-manager|publisher|ci)\s+zpan\s+purge-trash-object\b/gi) -forbidMatch('invented operator profile', /\brestish\s+--rsh-profile\s+operator\b/gi) -forbidCommandLine('upload without plugin profile', /\b(?:RSH_PROFILE=\S+\s+)?restish\s+(?:--rsh-profile\s+\S+\s+)?zpan-upload\b(?!.*\s--profile\s+\S+)/i) -forbidMatch('upload with ineffective host profile flag', /\brestish\s+--rsh-profile\s+\S+\s+zpan-upload\b/gi) -forbidCommandLine('upload without delegated profile environment', /^restish\s+zpan-upload\b/i) -forbidMatch( - 'MCP upload control-plane allowlist', - /restish\s+mcp\s+serve[\s\S]*?--operations[^\n]*(createObject|create-object|presignObjectUploadParts|presign-object-upload-parts|completeObjectUpload|complete-object-upload|abortObjectUpload|abort-object-upload)/gi, -) - -if (failures.length > 0) { - console.error(`ZPan Skill static contract failed with ${failures.length} finding(s):`) - for (const failure of failures) console.error(`- ${failure}`) - process.exit(1) -} - -console.log(`ZPan Skill static contract passed (${documents.length} markdown files checked)`) diff --git a/server/adapters/repos/agent-oauth.test.ts b/server/adapters/repos/agent-oauth.test.ts index d6d6d94f..e8deb9c7 100644 --- a/server/adapters/repos/agent-oauth.test.ts +++ b/server/adapters/repos/agent-oauth.test.ts @@ -1,125 +1,41 @@ -import { createHash } from 'node:crypto' -import { AGENT_OAUTH_CLIENT_ID } from '@shared/agent-oauth' import { AuthorizationScope } from '@shared/authorization' -import { eq, isNull } from 'drizzle-orm' +import { isNull } from 'drizzle-orm' import { describe, expect, it } from 'vitest' import * as authSchema from '../../db/auth-schema' import { createTestApp } from '../../test/setup' import { createAgentOAuthGateway } from './agent-oauth' +const CLIENT_ID = 'dynamic-client' + describe('Agent OAuth gateway', () => { - it('provisions the system public native client', async () => { + it('finds and lists dynamically registered applications', async () => { const { db } = await createTestApp() - const [client] = await db - .select() - .from(authSchema.oauthClient) - .where(eq(authSchema.oauthClient.clientId, AGENT_OAUTH_CLIENT_ID)) + await insertClient(db, CLIENT_ID, 'FlareAuth') + await insertClient(db, 'retired-system-client', 'Retired', 'system') - expect(client).toMatchObject({ - clientId: AGENT_OAUTH_CLIENT_ID, - tokenEndpointAuthMethod: 'none', - public: true, - type: 'native', - requirePKCE: true, + await expect(createAgentOAuthGateway().findClient(db, CLIENT_ID)).resolves.toMatchObject({ + clientId: CLIENT_ID, + clientName: 'FlareAuth', disabled: false, + redirectUris: ['https://flareauth.example/callback'], + responseTypes: ['code'], }) - expect(JSON.parse(client.redirectUris)).toEqual([ - 'http://localhost:8484/callback', - 'http://127.0.0.1:8484/callback', + await expect(createAgentOAuthGateway().listRegisteredApplications(db)).resolves.toEqual([ + expect.objectContaining({ clientId: CLIENT_ID, name: 'FlareAuth' }), ]) - expect(JSON.parse(client.grantTypes ?? '[]')).toEqual(['authorization_code', 'refresh_token']) + await expect(createAgentOAuthGateway().findClient(db, 'retired-system-client')).resolves.toBeNull() }) - it('verifies access tokens only while consent is live and scoped to the workspace', async () => { - const { db } = await createTestApp() - const userId = 'oauth-user' - const orgId = 'oauth-org' - await insertUserAndOrg(db, userId, orgId) - await db.insert(authSchema.oauthConsent).values({ - id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date(), - updatedAt: new Date(), - }) - await db.insert(authSchema.oauthAccessToken).values({ - id: 'access-1', - token: hashStoredToken('opaque-token'), - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date(), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }) - - const token = await createAgentOAuthGateway().verifyAccessToken(db, 'opaque-token') - - expect(token).toEqual({ - grantId: 'grant-1', - userId, - orgId, - clientId: AGENT_OAUTH_CLIENT_ID, - scopes: [AuthorizationScope.OBJECTS_READ], - }) - - await db.delete(authSchema.oauthConsent).where(eq(authSchema.oauthConsent.id, 'grant-1')) - await expect(createAgentOAuthGateway().verifyAccessToken(db, 'opaque-token')).resolves.toBeNull() - }) - - it('requires the managed client, workspace, and granted scopes before minting claims', async () => { - const { db } = await createTestApp() - const userId = 'oauth-user' - const orgId = 'oauth-org' - await insertUserAndOrg(db, userId, orgId) - await db.insert(authSchema.oauthConsent).values({ - id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date(), - updatedAt: new Date(), - }) - - await expect( - createAgentOAuthGateway().assertLiveGrant(db, { - userId, - clientId: AGENT_OAUTH_CLIENT_ID, - scopes: [AuthorizationScope.OBJECTS_READ], - }), - ).rejects.toThrow('agent_oauth_workspace_required') - - await expect( - createAgentOAuthGateway().assertLiveGrant(db, { - userId, - clientId: 'other-client', - orgId, - scopes: [AuthorizationScope.OBJECTS_READ], - }), - ).rejects.toThrow('agent_oauth_client_denied') - - await expect( - createAgentOAuthGateway().assertLiveGrant(db, { - userId, - clientId: AGENT_OAUTH_CLIENT_ID, - orgId, - scopes: [AuthorizationScope.OBJECTS_READ, AuthorizationScope.QUOTA_READ], - }), - ).rejects.toThrow('agent_oauth_scope_denied') - }) - - it('lists only workspace-bound grants for the managed client', async () => { + it('lists workspace-bound grants with their registered application names', async () => { const { db } = await createTestApp() + await insertClient(db, CLIENT_ID, 'FlareAuth') const userId = 'oauth-user' const orgId = 'oauth-org' await insertUserAndOrg(db, userId, orgId) await db.insert(authSchema.oauthConsent).values([ { id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId, referenceId: orgId, scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), @@ -129,42 +45,20 @@ describe('Agent OAuth gateway', () => { }, { id: 'grant-without-workspace', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId, referenceId: null, scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date('2026-07-29T12:02:00.000Z'), - lastUsedAt: null, - updatedAt: new Date('2026-07-29T12:03:00.000Z'), - }, - ]) - await db.insert(authSchema.oauthAccessToken).values([ - { - id: 'access-older', - token: 'hashed-access-older', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date('2026-07-29T12:05:00.000Z'), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }, - { - id: 'access-newer', - token: 'hashed-access-newer', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date('2026-07-29T12:10:00.000Z'), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), + createdAt: new Date(), + updatedAt: new Date(), }, ]) await expect(createAgentOAuthGateway().listGrants(db, userId)).resolves.toEqual([ { id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, + clientName: 'FlareAuth', userId, orgId, scopes: [AuthorizationScope.OBJECTS_READ], @@ -174,138 +68,35 @@ describe('Agent OAuth gateway', () => { ]) }) - it('records actual delegated grant use without treating token issuance as use', async () => { + it('revokes the selected dynamic-client grant family only', async () => { const { db } = await createTestApp() - const gateway = createAgentOAuthGateway() + await insertClient(db, CLIENT_ID, 'FlareAuth') const userId = 'oauth-user' const orgId = 'oauth-org' await insertUserAndOrg(db, userId, orgId) await db.insert(authSchema.oauthConsent).values({ id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date('2026-07-29T12:00:00.000Z'), - updatedAt: new Date('2026-07-29T12:01:00.000Z'), - }) - await db.insert(authSchema.oauthAccessToken).values({ - id: 'access-1', - token: 'hashed-access', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: orgId, - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date('2026-07-29T12:10:00.000Z'), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }) - - await expect(gateway.listGrants(db, userId)).resolves.toMatchObject([{ id: 'grant-1', lastUsedAt: null }]) - - await gateway.recordGrantUse(db, { - grantId: 'grant-1', - userId, - orgId, - now: new Date('2026-07-29T12:30:00.000Z'), - }) - - await expect(gateway.listGrants(db, userId)).resolves.toMatchObject([ - { id: 'grant-1', lastUsedAt: '2026-07-29T12:30:00.000Z' }, - ]) - }) - - it('revokes only the managed client grant for the selected workspace', async () => { - const { db } = await createTestApp() - const userId = 'oauth-user' - const orgId = 'oauth-org' - await insertUserAndOrg(db, userId, orgId) - await db.insert(authSchema.organization).values({ id: 'oauth-org-2', name: 'OAuth Org 2', slug: 'oauth-org-2' }) - await db - .insert(authSchema.member) - .values({ id: 'oauth-org-2-member', organizationId: 'oauth-org-2', userId, role: 'owner' }) - await db.insert(authSchema.oauthClient).values({ - id: 'other-client', - clientId: 'other-client', - clientSecret: null, - disabled: false, - skipConsent: false, - enableEndSession: false, - subjectType: 'public', - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - name: 'Other Client', - redirectUris: JSON.stringify(['http://localhost/callback']), - tokenEndpointAuthMethod: 'none', - grantTypes: JSON.stringify(['authorization_code']), - responseTypes: JSON.stringify(['code']), - public: true, - type: 'native', - requirePKCE: true, - }) - await db.insert(authSchema.oauthConsent).values({ - id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId, referenceId: orgId, scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), createdAt: new Date(), updatedAt: new Date(), }) - await db.insert(authSchema.oauthConsent).values([ - { - id: 'grant-2', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: 'oauth-org-2', - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: 'other-grant', - clientId: 'other-client', - userId, - referenceId: orgId, - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - createdAt: new Date(), - updatedAt: new Date(), - }, - ]) await db.insert(authSchema.oauthRefreshToken).values({ id: 'refresh-1', token: 'hashed-refresh', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId, referenceId: orgId, expiresAt: new Date(Date.now() + 60_000), createdAt: new Date(), scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), }) - await db.insert(authSchema.oauthRefreshToken).values([ - { - id: 'refresh-2', - token: 'hashed-refresh-2', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: 'oauth-org-2', - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date(), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }, - { - id: 'other-refresh', - token: 'hashed-other-refresh', - clientId: 'other-client', - userId, - referenceId: orgId, - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date(), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }, - ]) await db.insert(authSchema.oauthAccessToken).values({ id: 'access-1', token: 'hashed-access', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId, referenceId: orgId, refreshId: 'refresh-1', @@ -313,59 +104,66 @@ describe('Agent OAuth gateway', () => { createdAt: new Date(), scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), }) - await db.insert(authSchema.oauthAccessToken).values([ - { - id: 'access-2', - token: 'hashed-access-2', - clientId: AGENT_OAUTH_CLIENT_ID, - userId, - referenceId: 'oauth-org-2', - refreshId: 'refresh-2', - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date(), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }, - { - id: 'other-access', - token: 'hashed-other-access', - clientId: 'other-client', - userId, - referenceId: orgId, - refreshId: 'other-refresh', - expiresAt: new Date(Date.now() + 60_000), - createdAt: new Date(), - scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), - }, - ]) - const revoked = await createAgentOAuthGateway().revokeGrant(db, { - userId, - grantId: 'grant-1', - now: new Date('2026-07-29T12:00:00.000Z'), - }) - - expect(revoked).toBe(true) - expect((await db.select().from(authSchema.oauthConsent)).map((row) => row.id).sort()).toEqual([ - 'grant-2', - 'other-grant', - ]) - expect((await db.select().from(authSchema.oauthAccessToken)).map((row) => row.id).sort()).toEqual([ - 'access-2', - 'other-access', - ]) - const [refresh] = await db - .select() - .from(authSchema.oauthRefreshToken) - .where(eq(authSchema.oauthRefreshToken.id, 'refresh-1')) - expect(refresh.revoked?.toISOString()).toBe('2026-07-29T12:00:00.000Z') + await expect( + createAgentOAuthGateway().revokeGrant(db, { + userId, + grantId: 'grant-1', + now: new Date('2026-07-29T12:30:00.000Z'), + }), + ).resolves.toBe(true) + expect(await db.select().from(authSchema.oauthConsent)).toHaveLength(0) + expect(await db.select().from(authSchema.oauthAccessToken)).toHaveLength(0) const liveRefreshes = await db .select() .from(authSchema.oauthRefreshToken) .where(isNull(authSchema.oauthRefreshToken.revoked)) - expect(liveRefreshes.map((row) => row.id).sort()).toEqual(['other-refresh', 'refresh-2']) + expect(liveRefreshes).toHaveLength(0) + }) + + it('records and detects JWT access-token revocation by jti', async () => { + const { db } = await createTestApp() + const payload = Buffer.from( + JSON.stringify({ jti: 'token-1', client_id: CLIENT_ID, exp: Math.floor(Date.now() / 1000) + 60 }), + ).toString('base64url') + const token = `e30.${payload}.signature` + const gateway = createAgentOAuthGateway() + + await gateway.revokeJwtAccessToken(db, token) + + await expect(gateway.isJwtAccessTokenRevoked(db, 'token-1')).resolves.toBe(true) + await expect(gateway.isJwtAccessTokenRevoked(db, 'unknown')).resolves.toBe(false) }) }) +async function insertClient( + db: Awaited>['db'], + clientId: string, + name: string, + referenceId?: string, +) { + await db.insert(authSchema.oauthClient).values({ + id: clientId, + clientId, + clientSecret: null, + disabled: false, + skipConsent: false, + enableEndSession: false, + subjectType: 'public', + scopes: JSON.stringify([AuthorizationScope.OBJECTS_READ]), + name, + uri: 'https://flareauth.example', + redirectUris: JSON.stringify(['https://flareauth.example/callback']), + tokenEndpointAuthMethod: 'none', + grantTypes: JSON.stringify(['authorization_code', 'refresh_token']), + responseTypes: JSON.stringify(['code']), + public: true, + type: 'native', + requirePKCE: true, + referenceId, + }) +} + async function insertUserAndOrg(db: Awaited>['db'], userId: string, orgId: string) { await db.insert(authSchema.user).values({ id: userId, @@ -376,7 +174,3 @@ async function insertUserAndOrg(db: Awaited>['d await db.insert(authSchema.organization).values({ id: orgId, name: 'OAuth Org', slug: orgId }) await db.insert(authSchema.member).values({ id: `${orgId}-member`, organizationId: orgId, userId, role: 'owner' }) } - -function hashStoredToken(token: string): string { - return createHash('sha256').update(token).digest('base64url') -} diff --git a/server/adapters/repos/agent-oauth.ts b/server/adapters/repos/agent-oauth.ts index f3016619..25f27f07 100644 --- a/server/adapters/repos/agent-oauth.ts +++ b/server/adapters/repos/agent-oauth.ts @@ -1,119 +1,94 @@ -import { createHash } from 'node:crypto' -import { - AGENT_OAUTH_CLIENT_ID, - AGENT_OAUTH_CLIENT_NAME, - AGENT_OAUTH_SCOPES, - RESTISH_OAUTH_REDIRECT_URIS, -} from '@shared/agent-oauth' import { type AuthorizationScope, isAuthorizationScope } from '@shared/authorization' import { and, eq, gt, inArray, isNull } from 'drizzle-orm' -import { oauthAccessToken, oauthClient, oauthConsent, oauthRefreshToken, user as userTable } from '../../db/auth-schema' +import { decodeJwt } from 'jose' +import { + oauthAccessToken, + oauthClient, + oauthConsent, + oauthJwtRevocation, + oauthRefreshToken, +} from '../../db/auth-schema' import { executeWriteTransaction } from '../../db/transaction' -import type { Database } from '../../platform/interface' -import type { AgentOAuthGateway, AgentOAuthGrant } from '../../usecases/ports' +import type { AgentOAuthClient, AgentOAuthGateway } from '../../usecases/ports' export function createAgentOAuthGateway(): AgentOAuthGateway { return { - async ensureSystemClient(db) { - const now = new Date() - const row = { - id: AGENT_OAUTH_CLIENT_ID, - clientId: AGENT_OAUTH_CLIENT_ID, - clientSecret: null, - disabled: false, - skipConsent: false, - enableEndSession: false, - subjectType: 'public', - scopes: JSON.stringify([...AGENT_OAUTH_SCOPES]), - userId: null, - createdAt: now, - updatedAt: now, - name: AGENT_OAUTH_CLIENT_NAME, - uri: null, - icon: null, - contacts: null, - tos: null, - policy: null, - softwareId: 'zpan-agent', - softwareVersion: null, - softwareStatement: null, - redirectUris: JSON.stringify([...RESTISH_OAUTH_REDIRECT_URIS]), - postLogoutRedirectUris: null, - tokenEndpointAuthMethod: 'none', - grantTypes: JSON.stringify(['authorization_code', 'refresh_token']), - responseTypes: JSON.stringify(['code']), - public: true, - type: 'native', - requirePKCE: true, - referenceId: 'system', - metadata: JSON.stringify({ systemManaged: true }), - } - await db - .insert(oauthClient) - .values(row) - .onConflictDoUpdate({ - target: oauthClient.clientId, - set: { - disabled: false, - scopes: row.scopes, - updatedAt: now, - redirectUris: row.redirectUris, - tokenEndpointAuthMethod: row.tokenEndpointAuthMethod, - grantTypes: row.grantTypes, - responseTypes: row.responseTypes, - public: true, - type: row.type, - requirePKCE: true, - metadata: row.metadata, - }, + async findClient(db, clientId) { + const [row] = await db + .select({ + clientId: oauthClient.clientId, + name: oauthClient.name, + disabled: oauthClient.disabled, + redirectUris: oauthClient.redirectUris, + responseTypes: oauthClient.responseTypes, + scopes: oauthClient.scopes, + referenceId: oauthClient.referenceId, }) + .from(oauthClient) + .where(eq(oauthClient.clientId, clientId)) + .limit(1) + if (!row || row.referenceId === 'system') return null + return { + clientId: row.clientId, + clientName: row.name || row.clientId, + disabled: row.disabled === true, + redirectUris: parseStringArray(row.redirectUris), + responseTypes: parseStringArray(row.responseTypes), + scopes: parseStringArray(row.scopes), + } satisfies AgentOAuthClient }, - async assertLiveGrant(db, input) { - const orgId = input.orgId - if (!orgId) throw new Error('agent_oauth_workspace_required') - if (input.clientId !== AGENT_OAUTH_CLIENT_ID) throw new Error('agent_oauth_client_denied') - const requestedScopes = input.scopes.filter(isAuthorizationScope) - const consent = await findConsent(db, input.userId, input.clientId, orgId) - if (!consent) throw new Error('agent_oauth_grant_revoked') - const grantedScopes = parseScopes(consent.scopes).filter(isAuthorizationScope) - if (!requestedScopes.every((scope) => grantedScopes.includes(scope))) throw new Error('agent_oauth_scope_denied') - }, - - async verifyAccessToken(db, token) { + async listRegisteredApplications(db) { const rows = await db .select({ - userId: oauthAccessToken.userId, - clientId: oauthAccessToken.clientId, - orgId: oauthAccessToken.referenceId, - scopes: oauthAccessToken.scopes, + clientId: oauthClient.clientId, + name: oauthClient.name, + uri: oauthClient.uri, + redirectUris: oauthClient.redirectUris, + grantTypes: oauthClient.grantTypes, + scopes: oauthClient.scopes, + disabled: oauthClient.disabled, + createdAt: oauthClient.createdAt, + referenceId: oauthClient.referenceId, }) - .from(oauthAccessToken) - .innerJoin(userTable, eq(userTable.id, oauthAccessToken.userId)) - .innerJoin(oauthClient, eq(oauthClient.clientId, oauthAccessToken.clientId)) - .where( - and( - eq(oauthAccessToken.token, hashStoredToken(token)), - eq(oauthAccessToken.clientId, AGENT_OAUTH_CLIENT_ID), - gt(oauthAccessToken.expiresAt, new Date()), - eq(oauthClient.disabled, false), - eq(userTable.banned, false), - ), - ) - .limit(1) - const result = rows[0] - if (!result?.userId || !result.orgId) return null - const scopes = parseScopes(result.scopes).filter(isAuthorizationScope) - const consent = await findConsent(db, result.userId, result.clientId, result.orgId) - if (!consent) return null - const grantedScopes = parseScopes(consent.scopes).filter(isAuthorizationScope) - return { - grantId: consent.id, - userId: result.userId, - orgId: result.orgId, - clientId: result.clientId, - scopes: scopes.filter((scope) => grantedScopes.includes(scope)), + .from(oauthClient) + return rows + .filter((row) => row.referenceId !== 'system') + .map((row) => ({ + clientId: row.clientId, + name: row.name || row.clientId, + uri: row.uri, + redirectUris: parseStringArray(row.redirectUris), + grantTypes: parseStringArray(row.grantTypes), + scopes: parseStringArray(row.scopes), + disabled: row.disabled === true, + createdAt: toIso(row.createdAt), + })) + }, + + async revokeJwtAccessToken(db, token) { + const payload = decodeJwt(token) + if (typeof payload.jti !== 'string' || typeof payload.client_id !== 'string' || typeof payload.exp !== 'number') { + throw new Error('invalid_oauth_jwt_revocation') } + await db + .insert(oauthJwtRevocation) + .values({ + id: payload.jti, + clientId: payload.client_id, + expiresAt: new Date(payload.exp * 1000), + createdAt: new Date(), + }) + .onConflictDoNothing({ target: oauthJwtRevocation.id }) + }, + + async isJwtAccessTokenRevoked(db, tokenId) { + const [row] = await db + .select({ id: oauthJwtRevocation.id }) + .from(oauthJwtRevocation) + .where(and(eq(oauthJwtRevocation.id, tokenId), gt(oauthJwtRevocation.expiresAt, new Date()))) + .limit(1) + return Boolean(row) }, async listGrants(db, userId) { @@ -121,6 +96,7 @@ export function createAgentOAuthGateway(): AgentOAuthGateway { .select({ id: oauthConsent.id, clientId: oauthConsent.clientId, + clientName: oauthClient.name, userId: oauthConsent.userId, orgId: oauthConsent.referenceId, scopes: oauthConsent.scopes, @@ -128,16 +104,18 @@ export function createAgentOAuthGateway(): AgentOAuthGateway { lastUsedAt: oauthConsent.lastUsedAt, }) .from(oauthConsent) - .where(and(eq(oauthConsent.userId, userId), eq(oauthConsent.clientId, AGENT_OAUTH_CLIENT_ID))) - return rows.flatMap((row): AgentOAuthGrant[] => { + .innerJoin(oauthClient, eq(oauthClient.clientId, oauthConsent.clientId)) + .where(eq(oauthConsent.userId, userId)) + return rows.flatMap((row) => { if (!row.userId || !row.orgId) return [] return [ { id: row.id, clientId: row.clientId, + clientName: row.clientName || row.clientId, userId: row.userId, orgId: row.orgId, - scopes: parseScopes(row.scopes).filter(isAuthorizationScope), + scopes: parseScopes(row.scopes), createdAt: toIso(row.createdAt), lastUsedAt: row.lastUsedAt ? toIso(row.lastUsedAt) : null, }, @@ -145,22 +123,8 @@ export function createAgentOAuthGateway(): AgentOAuthGateway { }) }, - async recordGrantUse(db, input) { - await db - .update(oauthConsent) - .set({ lastUsedAt: input.now }) - .where( - and( - eq(oauthConsent.id, input.grantId), - eq(oauthConsent.userId, input.userId), - eq(oauthConsent.referenceId, input.orgId), - eq(oauthConsent.clientId, AGENT_OAUTH_CLIENT_ID), - ), - ) - }, - async revokeGrant(db, input) { - const grants = await db + const [grant] = await db .select({ id: oauthConsent.id, clientId: oauthConsent.clientId, @@ -168,15 +132,8 @@ export function createAgentOAuthGateway(): AgentOAuthGateway { referenceId: oauthConsent.referenceId, }) .from(oauthConsent) - .where( - and( - eq(oauthConsent.id, input.grantId), - eq(oauthConsent.userId, input.userId), - eq(oauthConsent.clientId, AGENT_OAUTH_CLIENT_ID), - ), - ) + .where(and(eq(oauthConsent.id, input.grantId), eq(oauthConsent.userId, input.userId))) .limit(1) - const grant = grants[0] if (!grant?.userId || !grant.referenceId) return false const refreshRows = await db .select({ id: oauthRefreshToken.id }) @@ -210,23 +167,6 @@ export function createAgentOAuthGateway(): AgentOAuthGateway { } } -async function findConsent(db: Database, userId: string, clientId: string, orgId: string) { - const rows = await db - .select({ id: oauthConsent.id, scopes: oauthConsent.scopes }) - .from(oauthConsent) - .innerJoin(userTable, eq(userTable.id, oauthConsent.userId)) - .where( - and( - eq(oauthConsent.userId, userId), - eq(oauthConsent.clientId, clientId), - eq(oauthConsent.referenceId, orgId), - eq(userTable.banned, false), - ), - ) - .limit(1) - return rows[0] ?? null -} - function parseScopes(value: string | string[] | null): AuthorizationScope[] { if (Array.isArray(value)) return value.filter(isAuthorizationScope) if (!value) return [] @@ -236,12 +176,15 @@ function parseScopes(value: string | string[] | null): AuthorizationScope[] { : [] } -function toIso(value: Date | number | string): string { - const date = new Date(value) - if (Number.isNaN(date.getTime())) throw new Error('invalid_agent_oauth_date') - return date.toISOString() +function parseStringArray(value: string | string[] | null): string[] { + if (Array.isArray(value)) return value.filter((item): item is string => typeof item === 'string') + if (!value) return [] + const parsed = JSON.parse(value) as unknown + return Array.isArray(parsed) ? parsed.filter((item): item is string => typeof item === 'string') : [] } -function hashStoredToken(token: string): string { - return createHash('sha256').update(token).digest('base64url') +function toIso(value: Date | number | string): string { + const date = new Date(value) + if (Number.isNaN(date.getTime())) throw new Error('invalid_oauth_date') + return date.toISOString() } diff --git a/server/adapters/repos/api-key-scopes.ts b/server/adapters/repos/api-key-scopes.ts index c9330dec..42ee3ddd 100644 --- a/server/adapters/repos/api-key-scopes.ts +++ b/server/adapters/repos/api-key-scopes.ts @@ -3,7 +3,7 @@ import { inArray } from 'drizzle-orm' import { apikey } from '../../db/auth-schema' import type { Database } from '../../platform/interface' -const WORKSPACE_TEMPLATES = [ApiKeyTemplate.IHOST, ApiKeyTemplate.REMOTE_DOWNLOAD, ApiKeyTemplate.AGENT] +const WORKSPACE_TEMPLATES = [ApiKeyTemplate.IHOST, ApiKeyTemplate.REMOTE_DOWNLOAD] export function scopeForApiKey(configId: string, metadata: unknown): ApiKeyScope | null { const scope = parseApiKeyScope(metadata) diff --git a/server/adapters/repos/api-keys.ts b/server/adapters/repos/api-keys.ts index 618bc81e..6021d520 100644 --- a/server/adapters/repos/api-keys.ts +++ b/server/adapters/repos/api-keys.ts @@ -1,31 +1,16 @@ import { defaultKeyHasher } from '@better-auth/api-key' import { - AGENT_GRANTABLE_API_KEY_SCOPES, API_KEY_TEMPLATES, type ApiKeyPermissions, - ApiKeyTemplate, type ApiKeyTemplate as ApiKeyTemplateId, - apiKeyMetadata, } from '@shared/api-key-templates' -import { - type AuthorizationScope, - authorizationScope, - hasAuthorizationScope, - permissionScopes, - scopePermissions, -} from '@shared/authorization' -import type { AgentApiKey, AgentGrantableScope } from '@shared/schemas' -import { and, desc, eq } from 'drizzle-orm' -import { nanoid } from 'nanoid' -import { apikey, organization } from '../../db/auth-schema' -import { executeWriteTransaction } from '../../db/transaction' +import { authorizationScope, hasAuthorizationScope } from '@shared/authorization' +import { eq } from 'drizzle-orm' +import { apikey } from '../../db/auth-schema' import type { Database } from '../../platform/interface' import { type ApiKeyAuth, type ApiKeyGateway, ApiKeyRateLimitError, type VerifiedApiKey } from '../../usecases/ports' import { scopeForApiKey } from './api-key-scopes' -const AGENT_API_KEY_PREFIX = 'zpan_agent_' -const AGENT_GRANTABLE_SCOPE_SET = new Set(AGENT_GRANTABLE_API_KEY_SCOPES) - type VerifyApiKeyResult = { valid: boolean error: { message: string; code: string; details?: { tryAgainIn?: number } } | null @@ -72,155 +57,9 @@ export function createApiKeyGateway(): ApiKeyGateway { hasApiKeyScope(permissions: ApiKeyPermissions | null | undefined, scope) { return hasAuthorizationScope(permissions, scope) }, - - async listAgentApiKeys(db, userId, orgId, now) { - const rows = await listAgentRows(db, userId, orgId) - return rows.map((row) => toAgentApiKeyDTO(row, now)) - }, - - async getAgentApiKey(db, userId, orgId, keyId, now) { - const row = await getAgentRow(db, userId, orgId, keyId) - return row ? toAgentApiKeyDTO(row, now) : null - }, - - async issueAgentApiKey(db, input) { - const now = new Date() - const id = crypto.randomUUID() - const key = `${AGENT_API_KEY_PREFIX}${nanoid(48)}` - const hashedKey = await defaultKeyHasher(key) - const insert = db.insert(apikey).values({ - id, - configId: ApiKeyTemplate.AGENT, - name: input.name, - start: key.slice(0, AGENT_API_KEY_PREFIX.length + 6), - referenceId: input.userId, - prefix: AGENT_API_KEY_PREFIX, - key: hashedKey, - enabled: true, - rateLimitEnabled: true, - rateLimitTimeWindow: 60_000, - rateLimitMax: 600, - requestCount: 0, - expiresAt: input.expiresAt, - createdAt: now, - updatedAt: now, - permissions: JSON.stringify(scopePermissions(input.scopes)), - metadata: JSON.stringify(apiKeyMetadata({ mode: 'workspace', orgId: input.orgId })), - }) - const revoke = input.revokeKeyId - ? db.update(apikey).set({ enabled: false, updatedAt: now }).where(eq(apikey.id, input.revokeKeyId)) - : null - await executeWriteTransaction(db, revoke ? [insert, revoke] : [insert]) - const row = await getAgentRow(db, input.userId, input.orgId, id) - if (!row) throw new Error('agent_api_key_create_failed') - return { key, item: toAgentApiKeyDTO(row, now) } - }, - - async revokeAgentApiKey(db, keyId) { - await db.update(apikey).set({ enabled: false, updatedAt: new Date() }).where(eq(apikey.id, keyId)) - }, } } -type AgentApiKeyRow = { - id: string - name: string | null - permissions: string | null - metadata: string | null - enabled: boolean - createdAt: Date | number | string - expiresAt: Date | number | string | null - lastRequest: Date | number | string | null - workspaceName: string | null -} - -async function listAgentRows(db: Database, userId: string, orgId: string): Promise { - const rows = await db - .select({ - id: apikey.id, - name: apikey.name, - permissions: apikey.permissions, - metadata: apikey.metadata, - enabled: apikey.enabled, - createdAt: apikey.createdAt, - expiresAt: apikey.expiresAt, - lastRequest: apikey.lastRequest, - workspaceName: organization.name, - }) - .from(apikey) - .leftJoin(organization, eq(organization.id, orgId)) - .where(and(eq(apikey.configId, ApiKeyTemplate.AGENT), eq(apikey.referenceId, userId))) - .orderBy(desc(apikey.createdAt)) - return rows.filter((row) => parseWorkspaceMetadata(row.metadata)?.orgId === orgId) -} - -async function getAgentRow(db: Database, userId: string, orgId: string, keyId: string): Promise { - const rows = await db - .select({ - id: apikey.id, - name: apikey.name, - permissions: apikey.permissions, - metadata: apikey.metadata, - enabled: apikey.enabled, - createdAt: apikey.createdAt, - expiresAt: apikey.expiresAt, - lastRequest: apikey.lastRequest, - workspaceName: organization.name, - }) - .from(apikey) - .leftJoin(organization, eq(organization.id, orgId)) - .where(and(eq(apikey.id, keyId), eq(apikey.configId, ApiKeyTemplate.AGENT), eq(apikey.referenceId, userId))) - .limit(1) - const row = rows[0] - return row && parseWorkspaceMetadata(row.metadata)?.orgId === orgId ? row : null -} - -function toAgentApiKeyDTO(row: AgentApiKeyRow, now: Date): AgentApiKey { - const scope = parseWorkspaceMetadata(row.metadata) - if (!scope) throw new Error('agent_api_key_workspace_scope_missing') - const expiresAt = requireDate(row.expiresAt, 'agent_api_key_expiry_missing') - return { - id: row.id, - name: row.name ?? row.id, - orgId: scope.orgId, - workspaceName: row.workspaceName, - scopes: parseStoredScopes(row.permissions), - createdAt: toIso(row.createdAt), - expiresAt: expiresAt.toISOString(), - lastUsedAt: row.lastRequest ? toIso(row.lastRequest) : null, - status: !row.enabled ? 'revoked' : expiresAt <= now ? 'expired' : 'active', - } -} - -function parseWorkspaceMetadata(value: string | null): { orgId: string } | null { - if (!value) return null - const parsed = JSON.parse(value) as { scope?: { mode?: unknown; orgId?: unknown } } - return parsed.scope?.mode === 'workspace' && typeof parsed.scope.orgId === 'string' - ? { orgId: parsed.scope.orgId } - : null -} - -function parseStoredScopes(value: string | null): AgentGrantableScope[] { - if (!value) return [] - const permissions = JSON.parse(value) as ApiKeyPermissions - return permissionScopes(permissions).filter((scope): scope is AgentGrantableScope => - AGENT_GRANTABLE_SCOPE_SET.has(scope), - ) -} - -function requireDate(value: Date | number | string | null, message: string): Date { - if (value === null) throw new Error(message) - const date = new Date(value) - if (Number.isNaN(date.getTime())) throw new Error(message) - return date -} - -function toIso(value: Date | number | string): string { - const date = new Date(value) - if (Number.isNaN(date.getTime())) throw new Error('invalid_agent_api_key_date') - return date.toISOString() -} - async function normalizeVerifiedApiKey(key: NonNullable): Promise { const scope = scopeForApiKey(key.configId, key.metadata) if (!scope) return null diff --git a/server/app.ts b/server/app.ts index efc3600c..d6485c8a 100644 --- a/server/app.ts +++ b/server/app.ts @@ -1,9 +1,7 @@ import { release as osRelease } from 'node:os' import { OpenAPIHono } from '@hono/zod-openapi' import { Scalar } from '@scalar/hono-api-reference' -import { AGENT_OAUTH_CLIENT_ID } from '@shared/agent-oauth' -import { AGENT_API_KEY_SHORTCUT_SCOPES, AgentApiKeyShortcut } from '@shared/api-key-templates' -import { AuthorizationScope } from '@shared/authorization' +import { AGENT_OAUTH_SCOPE_DESCRIPTIONS, AGENT_OAUTH_SCOPES } from '@shared/agent-oauth' import type { Context } from 'hono' import { cors } from 'hono/cors' import type { Auth } from './auth' @@ -12,8 +10,8 @@ import { createDeps } from './composition' import { isPotentialWebDavPublicRequest, isWebDavPublicRequest } from './domain/webdav-public-url' import { adminOverview } from './http/admin-overview' import { adminStats } from './http/admin-stats' -import agentApiKeys from './http/agent-api-keys' import { agentOAuthGrants } from './http/agent-oauth-grants' +import { ARAZZO_DOCUMENT_PATH, ARAZZO_MEDIA_TYPE, createArazzoDocument } from './http/arazzo' import { serveAvatarBlob } from './http/avatar-blobs' import backgroundJobs from './http/background-jobs' import { configz } from './http/configz' @@ -24,6 +22,7 @@ import ihostConfig from './http/image-hosting/config' import ihost from './http/image-hosting/images' import internal from './http/internal' import { notifications } from './http/notifications' +import { oauthResourceScopes } from './http/oauth-resource-scopes' import objects from './http/objects' import { adminQuotas, userQuotas } from './http/quotas' import redirect from './http/redirect' @@ -137,7 +136,25 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep app.on(['POST', 'GET', 'HEAD'], '/api/auth/*', async (c) => { const a = c.get('auth') - return a.handler(c.req.raw) + const revokeRequest = c.req.path === '/api/auth/oauth2/revoke' ? c.req.raw.clone() : null + const response = await a.handler(c.req.raw) + if (revokeRequest && response.status === 400) { + const error = (await response + .clone() + .json() + .catch(() => null)) as { error?: string } | null + if (error?.error === 'unsupported_token_type') { + const token = (await revokeRequest.formData()).get('token') + if (typeof token === 'string') { + await c.get('deps').agentOAuth.revokeJwtAccessToken(c.get('platform').db, token) + return new Response(null, { + status: 200, + headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' }, + }) + } + } + } + return response }) app.on(['GET', 'HEAD'], '/.well-known/oauth-authorization-server/api/auth', async (c) => { @@ -155,21 +172,32 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep resource: `${origin}/api`, authorization_servers: [authorizationServer], bearer_methods_supported: ['header'], - scopes_supported: [ - 'objects:read', - 'objects:create', - 'objects:update', - 'objects:delete', - 'shares:read', - 'shares:create', - 'shares:delete', - 'quota:read', - 'storage-usage:read', - ], + scopes_supported: AGENT_OAUTH_SCOPES.filter((scope) => scope.includes(':')), + dpop_signing_alg_values_supported: ['ES256', 'EdDSA'], resource_name: 'ZPan API', }) }) + app.get('/api', (c) => { + c.header( + 'Link', + [ + '; rel="service-desc"; type="application/openapi+json"', + `<${ARAZZO_DOCUMENT_PATH}>; rel="describedby"; type="application/vnd.oai.workflows+json"`, + ].join(', '), + ) + return c.json({ name: 'ZPan API', openapi: '/api/openapi.json', workflows: ARAZZO_DOCUMENT_PATH }) + }) + + app.on(['GET', 'HEAD'], ARAZZO_DOCUMENT_PATH, (c) => { + const headers = { + 'Cache-Control': 'public, max-age=300', + 'Content-Type': ARAZZO_MEDIA_TYPE, + } + if (c.req.method === 'HEAD') return c.newResponse(null, 200, headers) + return c.newResponse(JSON.stringify(createArazzoDocument(new URL(c.req.url).origin)), 200, headers) + }) + // Global OpenAPI document. Aggregates every route defined with `.openapi()` // across all mounted sub-apps — a route appears here as soon as its resource is // converted to OpenAPIHono, no curation needed. better-auth endpoints (incl. the @@ -179,6 +207,10 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep openapi: '3.1.0', info: { title: 'ZPan API', version: '0.1.0' }, servers: [{ url: '/', description: 'Current ZPan origin' }], + externalDocs: { + description: 'Machine-readable API workflows (Arazzo 1.1)', + url: ARAZZO_DOCUMENT_PATH, + }, // Top-level tag order + descriptions; Scalar groups operations by these. tags: [ { name: 'Objects', description: 'Files and folders, including S3 multipart upload sessions' }, @@ -218,14 +250,12 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep }, }, }, - agentApiKey: { type: 'http', scheme: 'bearer', description: 'Workspace-scoped Agent API key' }, } doc.components.schemas = { ...(authDoc.components?.schemas as typeof doc.components.schemas), ...doc.components.schemas, } Object.assign(doc, { - 'x-cli-config': restishCliConfig(), 'x-zpan-discovery': { oauthAuthorizationServer: '/.well-known/oauth-authorization-server/api/auth', oauthProtectedResource: '/.well-known/oauth-protected-resource/api', @@ -307,6 +337,7 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep // /s/:token is intentionally left for the SPA landing page. app.route('/api/shares', publicShares) app.route('/api/configz', configz) + app.route('/api/oauth-resource-scopes', oauthResourceScopes) // Self-hosted avatar blobs (CF + AVATARS R2 binding, no AVATARS_PUBLIC_URL). Public. app.get('/api/avatar-blobs/:scope/:id', serveAvatarBlob) app.route('/r', redirect) @@ -326,7 +357,6 @@ export function createApp(platform: Platform, auth: Auth, deps: Deps = createDep app.route('/api/objects', objects) app.route('/api/shares', authedShares) app.route('/api/trash', trash) - app.route('/api/workspaces', agentApiKeys) app.route('/api', agentOAuthGrants) app.route('/api/teams', teams) app.route('/api/teams', adminTeams) @@ -432,66 +462,7 @@ function getCorsOrigins(platform: Platform): Set { } function agentScopeDescriptions(): Record { - return { - [AuthorizationScope.OBJECTS_READ]: 'List, inspect, and download objects', - [AuthorizationScope.OBJECTS_CREATE]: 'Create folders and upload objects', - [AuthorizationScope.OBJECTS_UPDATE]: 'Rename, move, and copy objects', - [AuthorizationScope.OBJECTS_DELETE]: 'Soft-delete objects', - [AuthorizationScope.SHARES_READ]: 'List and inspect shares', - [AuthorizationScope.SHARES_CREATE]: 'Create public shares', - [AuthorizationScope.SHARES_DELETE]: 'Revoke shares', - [AuthorizationScope.QUOTA_READ]: 'Inspect workspace quota', - [AuthorizationScope.STORAGE_USAGE_READ]: 'Inspect workspace storage usage', - } -} - -function restishCliConfig() { - const oauthCredential = (scopes: readonly AuthorizationScope[]) => ({ - auth: { - type: 'oauth-authorization-code', - params: { - authorize_url: '/api/auth/oauth2/authorize', - token_url: '/api/auth/oauth2/token', - client_id: AGENT_OAUTH_CLIENT_ID, - scopes: ['openid', 'offline_access', ...scopes].join(' '), - redirect_path: '/callback', - }, - }, - satisfies: [...scopes], - }) - - return { - profiles: { - default: { - credentials: { - agentOAuth2: oauthCredential(AGENT_API_KEY_SHORTCUT_SCOPES[AgentApiKeyShortcut.READER]), - }, - }, - reader: { - credentials: { - agentOAuth2: oauthCredential(AGENT_API_KEY_SHORTCUT_SCOPES[AgentApiKeyShortcut.READER]), - }, - }, - 'file-manager': { - credentials: { - agentOAuth2: oauthCredential(AGENT_API_KEY_SHORTCUT_SCOPES[AgentApiKeyShortcut.FILE_MANAGER]), - }, - }, - publisher: { - credentials: { - agentOAuth2: oauthCredential(AGENT_API_KEY_SHORTCUT_SCOPES[AgentApiKeyShortcut.PUBLISHER]), - }, - }, - ci: { - credentials: { - agentApiKey: { - auth: { type: 'bearer', params: { token: 'env:ZPAN_AGENT_API_KEY' } }, - satisfies: [...AGENT_API_KEY_SHORTCUT_SCOPES[AgentApiKeyShortcut.FILE_MANAGER]], - }, - }, - }, - }, - } + return { ...AGENT_OAUTH_SCOPE_DESCRIPTIONS } } export type AppType = ReturnType @@ -537,5 +508,5 @@ export type AdminAuditRoute = typeof adminAudit export type AdminOverviewRoute = typeof adminOverview export type AdminStatsRoute = typeof adminStats export type StorageUsageRoute = typeof storageUsage -export type AgentApiKeysRoute = typeof agentApiKeys export type AgentOAuthGrantsRoute = typeof agentOAuthGrants +export type OAuthResourceScopesRoute = typeof oauthResourceScopes diff --git a/server/auth.integration.test.ts b/server/auth.integration.test.ts index b6e2d388..ecd12294 100644 --- a/server/auth.integration.test.ts +++ b/server/auth.integration.test.ts @@ -1,5 +1,8 @@ +import { createHash } from 'node:crypto' import { isPersonalOrgLike } from '@shared/org-slugs' +import { deriveDpopAth } from 'better-auth/oauth2' import { eq } from 'drizzle-orm' +import { exportJWK, generateKeyPair, SignJWT } from 'jose' import { afterEach, describe, expect, it, vi } from 'vitest' import { createInviteRepo } from './adapters/repos/invite.js' import { createSiteInvitationRepo } from './adapters/repos/site-invitations.js' @@ -8,7 +11,7 @@ import { createAuth } from './auth.js' import * as authSchema from './db/auth-schema.js' import * as schema from './db/schema.js' import { inviteCodes, siteInvitations } from './db/schema.js' -import { createTestApp, seedProLicense } from './test/setup.js' +import { adminHeaders, createTestApp, seedProLicense } from './test/setup.js' type TestCtx = Awaited> @@ -527,6 +530,18 @@ describe('buildVerificationEmailHtml — via send-verification-email with email_ describe('loadProviderConfigs — createAuth with OIDC provider pre-configured', () => { it('createAuth succeeds when a valid enabled OIDC provider config is present', async () => { const ctx = await createTestApp() + vi.stubGlobal( + 'fetch', + vi.fn(async () => + Response.json({ + issuer: 'https://auth.example.com', + authorization_endpoint: 'https://auth.example.com/authorize', + token_endpoint: 'https://auth.example.com/token', + userinfo_endpoint: 'https://auth.example.com/userinfo', + jwks_uri: 'https://auth.example.com/jwks', + }), + ), + ) const oidcConfig = JSON.stringify({ providerId: 'my-oidc', type: 'oidc', @@ -666,7 +681,7 @@ describe('loadProviderConfigs — builtin social provider resolution', () => { expect(res.status).not.toBe(200) }) - it('createAuth runs exactly one DB query during init (no per-provider I/O)', async () => { + it('createAuth initializes provider config and the two OAuth resources with three DB reads', async () => { const ctx = await createTestApp() let selectCalls = 0 const countingDb = new Proxy(ctx.db, { @@ -677,7 +692,7 @@ describe('loadProviderConfigs — builtin social provider resolution', () => { }, }) await createAuth(countingDb as typeof ctx.db, 'test-secret', 'http://localhost:3000') - expect(selectCalls).toBe(1) + expect(selectCalls).toBe(3) }) it('createAuth resolves better-auth $context before returning', async () => { @@ -695,7 +710,272 @@ describe('loadProviderConfigs — builtin social provider resolution', () => { }) describe('Agent OAuth consent guards', () => { - it('issues an authorization code after full consent for the managed PKCE client', async () => { + it('publishes the external resource discovery contract at the exact API URL', async () => { + const ctx = await createTestApp() + const resource = await ctx.app.request('http://localhost:3000/api') + const metadata = await ctx.app.request('http://localhost:3000/.well-known/oauth-protected-resource/api') + const authorizationServer = await ctx.app.request( + 'http://localhost:3000/.well-known/oauth-authorization-server/api/auth', + ) + + expect(resource.status).toBe(200) + expect(resource.headers.get('link')).toBe( + [ + '; rel="service-desc"; type="application/openapi+json"', + '; rel="describedby"; type="application/vnd.oai.workflows+json"', + ].join(', '), + ) + await expect(metadata.json()).resolves.toMatchObject({ + resource: 'http://localhost:3000/api', + authorization_servers: ['http://localhost:3000/api/auth'], + }) + await expect(authorizationServer.json()).resolves.toMatchObject({ + registration_endpoint: 'http://localhost:3000/api/auth/oauth2/register', + grant_types_supported: expect.arrayContaining([ + 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'urn:ietf:params:oauth:grant-type:token-exchange', + ]), + dpop_signing_alg_values_supported: expect.any(Array), + }) + }) + + it('dynamically registers an external resource client without hard-coded identity', async () => { + const ctx = await createTestApp() + const res = await ctx.app.request('/api/auth/oauth2/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'External Resource Broker', + redirect_uris: ['https://broker.example.com/api/account-connections/oauth/callback'], + grant_types: [ + 'authorization_code', + 'refresh_token', + 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'urn:ietf:params:oauth:grant-type:token-exchange', + ], + response_types: ['code'], + token_endpoint_auth_method: 'client_secret_basic', + scope: 'openid offline_access', + jwks_uri: 'https://broker.example.com/api/auth/jwks', + }), + }) + const body = (await res.json()) as Record + + expect(res.status, JSON.stringify(body)).toBe(201) + expect(body).toMatchObject({ + client_id: expect.any(String), + client_secret: expect.any(String), + token_endpoint_auth_method: 'client_secret_basic', + }) + expect(String(body.scope).split(' ')).toEqual(expect.arrayContaining(['openid', 'offline_access', 'objects:read'])) + + const applicationsResponse = await ctx.app.request('/api/site/auth-providers', { + headers: await adminHeaders(ctx.app), + }) + const applications = (await applicationsResponse.json()) as { + registeredApplications: Array<{ clientId: string; name: string }> + } + expect(applicationsResponse.status).toBe(200) + expect(applications.registeredApplications).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + clientId: body.client_id, + name: 'External Resource Broker', + }), + ]), + ) + }) + + it('issues a DPoP API token through JWT bearer and token exchange grants', async () => { + const ctx = await createTestApp() + const { privateKey: agentPrivateKey, publicKey: agentPublicKey } = await generateKeyPair('ES256') + const agentPublicJwk = { ...(await exportJWK(agentPublicKey)), kid: 'agent-key', use: 'sig', alg: 'ES256' } + const getJwks = ctx.auth.api.getJwks + vi.stubGlobal( + 'fetch', + vi.fn(async (input: string | URL | Request) => { + const url = input instanceof Request ? input.url : String(input) + if (url === 'https://broker.example.com/api/auth/jwks') { + return Response.json({ keys: [agentPublicJwk] }) + } + if (url === 'http://localhost:3000/api/auth/jwks') { + return Response.json(await getJwks()) + } + throw new Error(`Unexpected fetch: ${url}`) + }), + ) + + const registration = await ctx.app.request('http://localhost:3000/api/auth/oauth2/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'External Resource Broker', + redirect_uris: ['https://broker.example.com/api/account-connections/oauth/callback'], + grant_types: [ + 'authorization_code', + 'refresh_token', + 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'urn:ietf:params:oauth:grant-type:token-exchange', + ], + response_types: ['code'], + token_endpoint_auth_method: 'client_secret_basic', + scope: 'openid offline_access', + jwks_uri: 'https://broker.example.com/api/auth/jwks', + }), + }) + const registered = (await registration.json()) as { client_id: string; client_secret: string } + expect(registration.status).toBe(201) + + const signUpResponse = await signUp(ctx, 'external-resource@example.com') + const cookie = signUpResponse.headers + .getSetCookie() + .map((value) => value.split(';', 1)[0]) + .join('; ') + const verifier = 'external-resource-verifier-with-sufficient-entropy-1234567890' + const challenge = createHash('sha256').update(verifier).digest('base64url') + const redirectUri = 'https://broker.example.com/api/account-connections/oauth/callback' + const scope = 'openid offline_access objects:read quota:read' + const authorizeParams = new URLSearchParams({ + client_id: registered.client_id, + redirect_uri: redirectUri, + response_type: 'code', + resource: 'http://localhost:3000/api', + scope, + state: 'external-resource', + code_challenge: challenge, + code_challenge_method: 'S256', + }) + const authorize = await ctx.app.request( + `http://localhost:3000/api/auth/oauth2/authorize?${authorizeParams.toString()}`, + { headers: { Cookie: cookie, Origin: 'http://localhost:3000' } }, + ) + const consentLocation = authorize.headers.get('location') + expect(authorize.status).toBe(302) + expect(consentLocation).toMatch(/^\/settings\/agent-access\?/) + const consent = await ctx.app.request('http://localhost:3000/api/auth/oauth2/consent', { + method: 'POST', + headers: { Cookie: cookie, Origin: 'http://localhost:3000', 'Content-Type': 'application/json' }, + body: JSON.stringify({ + accept: true, + oauth_query: consentLocation?.slice(consentLocation.indexOf('?') + 1), + }), + }) + const consentBody = (await consent.json()) as { url: string } + expect(consent.status).toBe(200) + const code = new URL(consentBody.url).searchParams.get('code') + expect(code).toBeTruthy() + + const tokenEndpoint = 'http://localhost:3000/api/auth/oauth2/token' + const basic = `Basic ${Buffer.from(`${registered.client_id}:${registered.client_secret}`).toString('base64')}` + const subjectResponse = await ctx.app.request(tokenEndpoint, { + method: 'POST', + headers: { Authorization: basic, 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'authorization_code', + code: code!, + redirect_uri: redirectUri, + code_verifier: verifier, + resource: 'http://localhost:3000/api', + }).toString(), + }) + const subject = (await subjectResponse.json()) as { access_token: string } + expect(subjectResponse.status).toBe(200) + + const now = Math.floor(Date.now() / 1000) + const assertion = await new SignJWT({}) + .setProtectedHeader({ typ: 'JWT', alg: 'ES256', kid: 'agent-key' }) + .setIssuer('https://broker.example.com/api/auth') + .setSubject('agent-123') + .setAudience(tokenEndpoint) + .setIssuedAt(now) + .setExpirationTime(now + 300) + .setJti(crypto.randomUUID()) + .sign(agentPrivateKey) + const actorResponse = await ctx.app.request(tokenEndpoint, { + method: 'POST', + headers: { Authorization: basic, 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + assertion, + }).toString(), + }) + const actor = (await actorResponse.json()) as { access_token: string } + expect(actorResponse.status, JSON.stringify(actor)).toBe(200) + + const { privateKey: dpopPrivateKey, publicKey: dpopPublicKey } = await generateKeyPair('ES256') + const dpopPublicJwk = await exportJWK(dpopPublicKey) + const exchangeProof = await new SignJWT({ + htm: 'POST', + htu: tokenEndpoint, + }) + .setProtectedHeader({ typ: 'dpop+jwt', alg: 'ES256', jwk: dpopPublicJwk }) + .setIssuedAt() + .setJti(crypto.randomUUID()) + .sign(dpopPrivateKey) + const exchangeResponse = await ctx.app.request(tokenEndpoint, { + method: 'POST', + headers: { + Authorization: basic, + DPoP: exchangeProof, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + subject_token: subject.access_token, + subject_token_type: 'urn:ietf:params:oauth:token-type:access_token', + actor_token: actor.access_token, + actor_token_type: 'urn:ietf:params:oauth:token-type:access_token', + requested_token_type: 'urn:ietf:params:oauth:token-type:access_token', + resource: 'http://localhost:3000/api', + scope: 'objects:read quota:read', + }).toString(), + }) + const exchanged = (await exchangeResponse.json()) as { access_token: string; token_type: string; scope: string } + expect(exchangeResponse.status).toBe(200) + expect(exchanged).toMatchObject({ token_type: 'DPoP', scope: 'objects:read quota:read' }) + + const apiUrl = 'http://localhost:3000/api/objects' + const apiProof = await new SignJWT({ + htm: 'GET', + htu: apiUrl, + ath: await deriveDpopAth(exchanged.access_token), + }) + .setProtectedHeader({ typ: 'dpop+jwt', alg: 'ES256', jwk: dpopPublicJwk }) + .setIssuedAt() + .setJti(crypto.randomUUID()) + .sign(dpopPrivateKey) + const apiResponse = await ctx.app.request(apiUrl, { + headers: { Authorization: `DPoP ${exchanged.access_token}`, DPoP: apiProof }, + }) + expect(apiResponse.status).toBe(200) + + const revokeResponse = await ctx.app.request('http://localhost:3000/api/auth/oauth2/revoke', { + method: 'POST', + headers: { Authorization: basic, 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + token: exchanged.access_token, + token_type_hint: 'access_token', + }).toString(), + }) + expect(revokeResponse.status).toBe(200) + + const revokedProof = await new SignJWT({ + htm: 'GET', + htu: apiUrl, + ath: await deriveDpopAth(exchanged.access_token), + }) + .setProtectedHeader({ typ: 'dpop+jwt', alg: 'ES256', jwk: dpopPublicJwk }) + .setIssuedAt() + .setJti(crypto.randomUUID()) + .sign(dpopPrivateKey) + const revokedResponse = await ctx.app.request(apiUrl, { + headers: { Authorization: `DPoP ${exchanged.access_token}`, DPoP: revokedProof }, + }) + expect(revokedResponse.status).toBe(401) + expect(revokedResponse.headers.get('www-authenticate')).toContain('DPoP') + }) + + it('issues an authorization code after full consent for a dynamically registered PKCE client', async () => { const ctx = await createTestApp() const previewOrigin = 'https://preview-zpan.example.com' const auth = await createAuth(ctx.platform, 'test-secret', 'https://zpan-staging.example.com', [previewOrigin]) @@ -705,9 +985,23 @@ describe('Agent OAuth consent guards', () => { .getSetCookie() .map((value) => value.split(';', 1)[0]) .join('; ') + const registration = await app.request('/api/auth/oauth2/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'Consent Test Client', + redirect_uris: ['https://broker.example.com/callback'], + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + token_endpoint_auth_method: 'none', + scope: 'openid offline_access objects:read quota:read', + }), + }) + const registered = (await registration.json()) as { client_id: string } + expect(registration.status).toBe(201) const params = new URLSearchParams({ - client_id: 'zpan-agent', - redirect_uri: 'http://127.0.0.1:8484/callback', + client_id: registered.client_id, + redirect_uri: 'https://broker.example.com/callback', response_type: 'code', scope: 'openid offline_access objects:read quota:read', state: 'oauth-consent-test', @@ -737,7 +1031,7 @@ describe('Agent OAuth consent guards', () => { expect(consent.status, consentBody).toBe(200) expect(JSON.parse(consentBody)).toMatchObject({ - url: expect.stringMatching(/^http:\/\/127\.0\.0\.1:8484\/callback\?code=/), + url: expect.stringMatching(/^https:\/\/broker\.example\.com\/callback\?code=/), }) }) @@ -747,7 +1041,7 @@ describe('Agent OAuth consent guards', () => { const res = await ctx.app.request('/api/auth/oauth2/consent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ client_id: 'zpan-agent', scope: 'objects:read' }), + body: JSON.stringify({ client_id: 'dynamic-client', scope: 'objects:read' }), }) expect(res.status).toBe(400) diff --git a/server/auth.ts b/server/auth.ts index 3579b568..41805f80 100644 --- a/server/auth.ts +++ b/server/auth.ts @@ -9,6 +9,7 @@ import { bearer, captcha, deviceAuthorization, + jwt, lastLoginMethod, openAPI, organization, @@ -18,6 +19,7 @@ import { genericOAuth } from 'better-auth/plugins/generic-oauth' import { adminAc, memberAc, ownerAc } from 'better-auth/plugins/organization/access' import { count, eq, like } from 'drizzle-orm' import { customAlphabet, nanoid } from 'nanoid' +import { AGENT_OAUTH_SCOPES, JWT_BEARER_GRANT_TYPE, TOKEN_EXCHANGE_GRANT_TYPE } from '../shared/agent-oauth' import { API_KEY_TEMPLATES, ApiKeyTemplate, @@ -39,7 +41,6 @@ import { } from '../shared/oauth-providers' import { generateUserOrgSlug, isPersonalOrgLike } from '../shared/org-slugs' import { createEmailGateway } from './adapters/gateways/email' -import { createAgentOAuthGateway } from './adapters/repos/agent-oauth' import { deleteApiKeysScopedToOrganization } from './adapters/repos/api-key-scopes' import { createAuditRepo } from './adapters/repos/audit' import { createDownloadTokenGateway } from './adapters/repos/download-tokens' @@ -92,6 +93,49 @@ interface ProviderConfigs { builtin: Array<{ providerId: string; clientId: string; clientSecret: string }> } +const EXTERNAL_RESOURCE_GRANTS = new Set([ + 'authorization_code', + 'refresh_token', + JWT_BEARER_GRANT_TYPE, + TOKEN_EXCHANGE_GRANT_TYPE, +]) + +function isExternalResourceClientRegistration(body: Record): boolean { + const grants = Array.isArray(body.grant_types) ? body.grant_types : [] + const responses = Array.isArray(body.response_types) ? body.response_types : [] + return ( + body.token_endpoint_auth_method === 'client_secret_basic' && + typeof body.jwks_uri === 'string' && + Array.isArray(body.redirect_uris) && + body.redirect_uris.length > 0 && + grants.length === EXTERNAL_RESOURCE_GRANTS.size && + grants.every((grant) => typeof grant === 'string' && EXTERNAL_RESOURCE_GRANTS.has(grant)) && + responses.length === 1 && + responses[0] === 'code' + ) +} + +async function dynamicRegistrationOrigins(request: Request): Promise { + if (!new URL(request.url).pathname.endsWith('/oauth2/register') || request.method !== 'POST') return [] + let body: Record + try { + body = (await request.clone().json()) as Record + } catch { + return [] + } + if (!isExternalResourceClientRegistration(body)) return [] + const redirectUris = body.redirect_uris as string[] + const jwksUri = body.jwks_uri as string + try { + const origins = new Set(redirectUris.map((uri) => new URL(uri).origin)) + const jwksOrigin = new URL(jwksUri).origin + if (origins.size !== 1 || !origins.has(jwksOrigin)) return [] + return [jwksOrigin] + } catch { + return [] + } +} + // One query loads every oauth_provider_* row. Configs are snapshotted at auth // instance creation: better-auth resolves social providers eagerly during its // context init, so per-request dynamic loading is not possible anyway. Admin @@ -347,20 +391,21 @@ export async function createAuth( const systemOptionsRepo = createSystemOptionsRepo(db) const email = createEmailGateway(systemOptionsRepo) const providerConfigs = await loadProviderConfigs(rawDb) - const agentOAuth = createAgentOAuthGateway() - await agentOAuth.ensureSystemClient(db) + const resourceAudience = baseURL ? `${new URL(baseURL).origin}/api` : undefined const usesNativeWebDavRateLimit = Boolean(authPlatform.getBinding(WEBDAV_RATE_LIMITER_BINDING)) const authOptions = { database: drizzleAdapter(db, { provider: 'sqlite', schema: authSchema }), secret, baseURL, + basePath: '/api/auth', // Function form: better-auth merges the result with baseURL per request. // Loopback/LAN origins are trusted automatically so self-hosted users can // log in via 127.0.0.1 or a LAN IP without configuring TRUSTED_ORIGINS. - trustedOrigins: (request?: Request) => { + trustedOrigins: async (request?: Request) => { const origin = request?.headers.get('origin') const list = trustedOrigins ?? [] - return origin && isLocalNetworkOrigin(origin) ? [...list, origin] : list + const registrationOrigins = request ? await dynamicRegistrationOrigins(request) : [] + return [...list, ...(origin && isLocalNetworkOrigin(origin) ? [origin] : []), ...registrationOrigins] }, advanced: { cookiePrefix: 'zp', @@ -438,6 +483,13 @@ export async function createAuth( } return } + if (ctx.path === '/oauth2/register') { + const body = ctx.body as Record | undefined + if (body && isExternalResourceClientRegistration(body)) { + body.scope = AGENT_OAUTH_SCOPES.join(' ') + } + return + } if (ctx.path === '/oauth2/update-consent' || ctx.path === '/oauth2/delete-consent') { throw new APIError('FORBIDDEN', { error: 'invalid_request', @@ -450,10 +502,6 @@ export async function createAuth( if (!body) return const configId = body.configId if (typeof configId !== 'string' || !API_KEY_TEMPLATES.includes(configId as ApiKeyTemplate)) return - if (configId === ApiKeyTemplate.AGENT) { - throw new APIError('BAD_REQUEST', { message: 'Create Agent API keys from the Agent Access API' }) - } - const session = await getSessionFromCtx(ctx) const userId = session?.user.id ?? (typeof body?.userId === 'string' ? body.userId : null) if (!userId) throw new APIError('UNAUTHORIZED', { message: 'Unauthorized' }) @@ -630,7 +678,8 @@ export async function createAuth( verificationUri: '/device', validateClient: async (clientId) => clientId === LEGACY_DOWNLOADER_CLIENT_ID, }), - oauthProvider(createAgentOAuthProviderOptions({ db, agentOAuth })), + jwt(), + oauthProvider(createAgentOAuthProviderOptions({ db, resourceAudience })), apiKey([ { configId: ApiKeyTemplate.IHOST, @@ -677,19 +726,6 @@ export async function createAuth( defaultPermissions: REMOTE_DOWNLOAD_API_KEY_PERMISSIONS, }, }, - { - configId: ApiKeyTemplate.AGENT, - references: 'user', - enableMetadata: true, - rateLimit: { - enabled: true, - timeWindow: 60_000, - maxRequests: 600, - }, - permissions: { - defaultPermissions: {}, - }, - }, ]), ], databaseHooks: { diff --git a/server/auth/agent-oauth-provider.test.ts b/server/auth/agent-oauth-provider.test.ts index ca015058..05fbf44b 100644 --- a/server/auth/agent-oauth-provider.test.ts +++ b/server/auth/agent-oauth-provider.test.ts @@ -1,30 +1,16 @@ -import { AGENT_OAUTH_ACCESS_TOKEN_SECONDS, AGENT_OAUTH_CLIENT_ID, AGENT_OAUTH_SCOPES } from '@shared/agent-oauth' +import { AGENT_OAUTH_ACCESS_TOKEN_SECONDS, AGENT_OAUTH_SCOPES } from '@shared/agent-oauth' import { AuthorizationScope } from '@shared/authorization' import { describe, expect, it, vi } from 'vitest' -import type { AgentOAuthGateway } from '../usecases/ports' import { createAgentOAuthProviderOptions } from './agent-oauth-provider' const db = {} as never -function createGateway(): AgentOAuthGateway { - return { - ensureSystemClient: vi.fn(), - assertLiveGrant: vi.fn(), - verifyAccessToken: vi.fn(), - listGrants: vi.fn(), - recordGrantUse: vi.fn(), - revokeGrant: vi.fn(), - } -} - function createOptions(input?: { findPersonalOrg?: (userId: string) => Promise getMemberRole?: (orgId: string, userId: string) => Promise - gateway?: AgentOAuthGateway }) { return createAgentOAuthProviderOptions({ db, - agentOAuth: input?.gateway ?? createGateway(), orgs: { findPersonalOrg: input?.findPersonalOrg ?? vi.fn(async () => 'personal-org'), getMemberRole: input?.getMemberRole ?? vi.fn(async () => 'owner'), @@ -33,11 +19,10 @@ function createOptions(input?: { } describe('createAgentOAuthProviderOptions', () => { - it('configures the managed public native Agent OAuth provider contract', async () => { + it('configures a dynamic-client OAuth provider contract', async () => { const options = createOptions() expect(options).toMatchObject({ - disableJwtPlugin: true, loginPage: '/sign-in', consentPage: '/settings/agent-access', accessTokenExpiresIn: AGENT_OAUTH_ACCESS_TOKEN_SECONDS, @@ -45,7 +30,9 @@ describe('createAgentOAuthProviderOptions', () => { postLogin: { page: '/settings/agent-access' }, }) expect(options.scopes).toEqual([...AGENT_OAUTH_SCOPES]) - expect(options.cachedTrustedClients?.has(AGENT_OAUTH_CLIENT_ID)).toBe(true) + expect(options.cachedTrustedClients).toBeUndefined() + expect(options.allowDynamicClientRegistration).toBe(true) + expect(options.allowUnauthenticatedClientRegistration).toBe(true) await expect(options.postLogin?.shouldRedirect?.({} as never)).resolves.toBe(false) }) @@ -109,36 +96,25 @@ describe('createAgentOAuthProviderOptions', () => { }) }) - it('adds ZPan Agent claims only for valid live grants', async () => { - const gateway = createGateway() - const options = createOptions({ gateway }) - + it('adds ZPan resource claims to a consent-bound access token', async () => { + const options = createOptions() await expect( options.customAccessTokenClaims?.({ user: { id: 'user-1' }, referenceId: 'team-org', - scopes: [AuthorizationScope.OBJECTS_READ], - metadata: {}, } as never), ).resolves.toEqual({ zpan_org_id: 'team-org', zpan_actor: 'agent_oauth' }) - expect(gateway.assertLiveGrant).toHaveBeenCalledWith(db, { - userId: 'user-1', - clientId: AGENT_OAUTH_CLIENT_ID, - orgId: 'team-org', - scopes: [AuthorizationScope.OBJECTS_READ], - }) }) - it('skips non-agent clients and rejects missing user or workspace context', async () => { + it('omits ZPan resource claims without user or workspace context', async () => { const options = createOptions() + await expect(options.customAccessTokenClaims?.({ metadata: {}, scopes: [] } as never)).resolves.toEqual({}) await expect( - options.customAccessTokenClaims?.({ metadata: { client_id: 'other-client' }, scopes: [] } as never), + options.customAccessTokenClaims?.({ + user: { id: 'user-1' }, + scopes: [], + } as never), ).resolves.toEqual({}) - await expect( - options.customAccessTokenClaims?.({ user: { id: 'user-1' }, scopes: [] } as never), - ).rejects.toMatchObject({ - body: expect.objectContaining({ error_description: 'Agent OAuth grant is missing workspace context' }), - }) }) }) diff --git a/server/auth/agent-oauth-provider.ts b/server/auth/agent-oauth-provider.ts index 95e68a0c..07c6254f 100644 --- a/server/auth/agent-oauth-provider.ts +++ b/server/auth/agent-oauth-provider.ts @@ -1,34 +1,70 @@ -import type { oauthProvider } from '@better-auth/oauth-provider' -import { APIError } from 'better-auth' import { + consumeClientAssertion, + type OAuthProviderExtension, + type oauthProvider, + type SchemaClient, + type Scope, +} from '@better-auth/oauth-provider' +import { APIError, type User } from 'better-auth' +import { createLocalJWKSet, createRemoteJWKSet, type JSONWebKeySet, jwtVerify } from 'jose' +import { + AGENT_ACTOR_RESOURCE, AGENT_OAUTH_ACCESS_TOKEN_SECONDS, - AGENT_OAUTH_CLIENT_ID, + AGENT_OAUTH_ACTOR_TOKEN_SECONDS, AGENT_OAUTH_REFRESH_TOKEN_SECONDS, AGENT_OAUTH_SCOPES, + AGENT_OAUTH_STANDARD_SCOPES, + JWT_BEARER_GRANT_TYPE, + OAUTH_ACCESS_TOKEN_TYPE, + TOKEN_EXCHANGE_GRANT_TYPE, } from '../../shared/agent-oauth' +import { isAuthorizationScope } from '../../shared/authorization' import { createOrgRepo } from '../adapters/repos/org' import type { Database } from '../platform/interface' -import type { AgentOAuthGateway } from '../usecases/ports' type AgentOAuthOrgLookup = Pick, 'findPersonalOrg' | 'getMemberRole'> type AgentOAuthProviderOptions = Parameters[0] export function createAgentOAuthProviderOptions(input: { db: Database - agentOAuth: AgentOAuthGateway + resourceAudience?: string orgs?: AgentOAuthOrgLookup }): AgentOAuthProviderOptions { const orgs = input.orgs ?? createOrgRepo(input.db) + const resources = input.resourceAudience + ? [ + { + identifier: input.resourceAudience, + name: 'ZPan API', + accessTokenTtl: AGENT_OAUTH_ACCESS_TOKEN_SECONDS, + allowedScopes: [...AGENT_OAUTH_SCOPES], + }, + { + identifier: AGENT_ACTOR_RESOURCE, + name: 'ZPan Agent Actor', + accessTokenTtl: AGENT_OAUTH_ACTOR_TOKEN_SECONDS, + allowedScopes: ['openid'], + }, + ] + : undefined + return { - disableJwtPlugin: true, loginPage: '/sign-in', consentPage: '/settings/agent-access', accessTokenExpiresIn: AGENT_OAUTH_ACCESS_TOKEN_SECONDS, + m2mAccessTokenExpiresIn: AGENT_OAUTH_ACTOR_TOKEN_SECONDS, refreshTokenExpiresIn: AGENT_OAUTH_REFRESH_TOKEN_SECONDS, grantTypes: ['authorization_code', 'refresh_token'], scopes: [...AGENT_OAUTH_SCOPES], + resources, + enforcePerClientResources: false, + allowDynamicClientRegistration: true, + allowUnauthenticatedClientRegistration: true, + clientRegistrationRequirePKCE: true, + clientRegistrationAllowedScopes: [...AGENT_OAUTH_SCOPES], + clientRegistrationDefaultScopes: [...AGENT_OAUTH_STANDARD_SCOPES], + extensions: input.resourceAudience ? [externalResourceGrantExtension(input.resourceAudience)] : [], advertisedMetadata: { scopes_supported: [...AGENT_OAUTH_SCOPES] }, - cachedTrustedClients: new Set([AGENT_OAUTH_CLIENT_ID]), silenceWarnings: { oauthAuthServerConfig: true, openidConfig: true, @@ -40,16 +76,11 @@ export function createAgentOAuthProviderOptions(input: { const clientScopes = scopes.filter((scope) => scope !== 'openid' && scope !== 'profile' && scope !== 'email') const grantableScopes = new Set(AGENT_OAUTH_SCOPES) if (clientScopes.some((scope) => !grantableScopes.has(scope))) { - throw new APIError('BAD_REQUEST', { error: 'invalid_scope', error_description: 'Scope is not grantable' }) + throw oauthError('invalid_scope', 'Scope is not grantable') } const orgId = typeof session.activeOrganizationId === 'string' ? session.activeOrganizationId : null const selectedOrgId = orgId || (await orgs.findPersonalOrg(user.id)) - if (!selectedOrgId) { - throw new APIError('BAD_REQUEST', { - error: 'invalid_request', - error_description: 'A workspace is required for Agent OAuth', - }) - } + if (!selectedOrgId) throw oauthError('invalid_request', 'A workspace is required for Agent OAuth') const role = await orgs.getMemberRole(selectedOrgId, user.id) if (!role && selectedOrgId !== (await orgs.findPersonalOrg(user.id))) { throw new APIError('FORBIDDEN', { @@ -60,20 +91,8 @@ export function createAgentOAuthProviderOptions(input: { return selectedOrgId }, }, - customAccessTokenClaims: async ({ user, referenceId, scopes, metadata }) => { - if (metadata?.client_id && metadata.client_id !== AGENT_OAUTH_CLIENT_ID) return {} - if (!user?.id || !referenceId) { - throw new APIError('BAD_REQUEST', { - error: 'invalid_grant', - error_description: 'Agent OAuth grant is missing workspace context', - }) - } - await input.agentOAuth.assertLiveGrant(input.db, { - userId: user.id, - clientId: AGENT_OAUTH_CLIENT_ID, - orgId: referenceId, - scopes, - }) + customAccessTokenClaims: async ({ user, referenceId }) => { + if (!user?.id || !referenceId) return {} return { zpan_org_id: referenceId, zpan_actor: 'agent_oauth', @@ -81,3 +100,148 @@ export function createAgentOAuthProviderOptions(input: { }, } } + +function externalResourceGrantExtension(resourceAudience: string): OAuthProviderExtension { + return { + grants: { + [JWT_BEARER_GRANT_TYPE]: async ({ ctx, opts, provider }) => { + const { client } = await provider.authenticateClient() + const assertion = bodyString(ctx.body, 'assertion') + const payload = await verifyAgentAssertion(ctx, opts, client, assertion) + const subject = requiredClaim(payload.sub, 'assertion sub') + const issuer = requiredClaim(payload.iss, 'assertion iss') + + return provider.issueTokens({ + client, + scopes: ['openid'], + user: assertionUser(subject), + resources: [AGENT_ACTOR_RESOURCE], + accessTokenClaims: { + zpan_actor_token: true, + zpan_actor_issuer: issuer, + }, + }) + }, + [TOKEN_EXCHANGE_GRANT_TYPE]: async ({ ctx, provider }) => { + if (!ctx.headers?.get('dpop')) throw oauthError('invalid_dpop_proof', 'DPoP proof header is required') + const requestedScopes = uniqueScopes(bodyString(ctx.body, 'scope')) + if (requestedScopes.length === 0 || requestedScopes.some((scope) => !isAuthorizationScope(scope))) { + throw oauthError('invalid_scope', 'Token exchange requires ZPan API scopes') + } + requireTokenType(ctx.body, 'subject_token_type') + requireTokenType(ctx.body, 'actor_token_type') + requireTokenType(ctx.body, 'requested_token_type') + const resource = bodyString(ctx.body, 'resource') + if (resource !== resourceAudience) throw oauthError('invalid_target', 'Unsupported token exchange resource') + + const { client } = await provider.authenticateClient({ scopes: requestedScopes }) + const subject = await provider.requireActiveAccessToken(bodyString(ctx.body, 'subject_token'), client.clientId) + const actor = await provider.requireActiveAccessToken(bodyString(ctx.body, 'actor_token'), client.clientId) + if (actor.zpan_actor_token !== true || typeof actor.sub !== 'string') { + throw oauthError('invalid_grant', 'Actor token is invalid') + } + const subjectScopes = uniqueScopes(typeof subject.scope === 'string' ? subject.scope : '') + if (requestedScopes.some((scope) => !subjectScopes.includes(scope))) { + throw oauthError('invalid_scope', 'Requested scope exceeds the connected account grant') + } + if (typeof subject.sub !== 'string') throw oauthError('invalid_grant', 'Subject token has no user') + const orgId = typeof subject.zpan_org_id === 'string' ? subject.zpan_org_id : undefined + if (!orgId) throw oauthError('invalid_grant', 'Subject token has no workspace') + const user = await ctx.context.internalAdapter.findUserById(subject.sub) + if (!user) throw oauthError('invalid_grant', 'Subject user no longer exists') + + return provider.issueTokens({ + client, + scopes: requestedScopes, + user, + referenceId: orgId, + resources: [resourceAudience], + accessTokenClaims: { + act: { + sub: actor.sub, + ...(typeof actor.zpan_actor_issuer === 'string' ? { iss: actor.zpan_actor_issuer } : {}), + }, + }, + tokenResponse: { issued_token_type: OAUTH_ACCESS_TOKEN_TYPE }, + }) + }, + }, + } +} + +async function verifyAgentAssertion( + ctx: Parameters[string]>[0]['ctx'], + opts: Parameters[string]>[0]['opts'], + client: SchemaClient, + assertion: string, +) { + const jwks = client.jwks + ? createLocalJWKSet(JSON.parse(client.jwks) as JSONWebKeySet) + : client.jwksUri + ? createRemoteJWKSet(new URL(client.jwksUri)) + : null + if (!jwks) throw oauthError('invalid_client', 'Registered client has no JWKS') + const endpoint = ctx.request?.url ?? `${ctx.context.baseURL}${ctx.path ?? '/oauth2/token'}` + let verified: Awaited> + try { + verified = await jwtVerify(assertion, jwks, { audience: endpoint, maxTokenAge: '5m' }) + } catch { + throw oauthError('invalid_grant', 'Agent assertion is invalid') + } + const issuer = requiredClaim(verified.payload.iss, 'assertion iss') + if (client.jwksUri) { + let issuerOrigin: string + try { + issuerOrigin = new URL(issuer).origin + } catch { + throw oauthError('invalid_grant', 'Agent assertion issuer must be an absolute URL') + } + if (issuerOrigin !== new URL(client.jwksUri).origin) { + throw oauthError('invalid_grant', 'Agent assertion issuer does not match the registered client') + } + } + await consumeClientAssertion(ctx, opts, { + namespace: `${JWT_BEARER_GRANT_TYPE}:${client.clientId}`, + payload: verified.payload, + expectedAudience: endpoint, + }) + return verified.payload +} + +function assertionUser(subject: string): User { + const now = new Date() + return { + id: subject, + name: subject, + email: `${encodeURIComponent(subject)}@agent.invalid`, + emailVerified: false, + image: null, + createdAt: now, + updatedAt: now, + } +} + +function bodyString(body: unknown, field: string): string { + const value = body && typeof body === 'object' ? (body as Record)[field] : undefined + if (typeof value !== 'string' || !value) throw oauthError('invalid_request', `${field} is required`) + return value +} + +function requireTokenType(body: unknown, field: string) { + if (bodyString(body, field) !== OAUTH_ACCESS_TOKEN_TYPE) { + throw oauthError('invalid_request', `${field} must be ${OAUTH_ACCESS_TOKEN_TYPE}`) + } +} + +function uniqueScopes(value: string): string[] { + return [...new Set(value.split(/\s+/).filter(Boolean))] +} + +function requiredClaim(value: unknown, name: string): string { + if (typeof value !== 'string' || !value) throw oauthError('invalid_grant', `${name} is required`) + return value +} + +function oauthError(error: string, errorDescription: string): APIError { + return new APIError('BAD_REQUEST', { error, error_description: errorDescription }) +} diff --git a/server/db/auth-schema.ts b/server/db/auth-schema.ts index b37b80f4..d7b0ec3d 100644 --- a/server/db/auth-schema.ts +++ b/server/db/auth-schema.ts @@ -1,5 +1,5 @@ import { relations, sql } from 'drizzle-orm' -import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core' +import { index, integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core' export const user = sqliteTable( 'user', @@ -54,7 +54,8 @@ export const account = sqliteTable( 'account', { id: text('id').primaryKey(), - accountId: text('account_id').notNull(), + issuer: text('issuer').notNull().default(''), + providerAccountId: text('account_id').notNull(), providerId: text('provider_id').notNull(), userId: text('user_id') .notNull() @@ -77,7 +78,10 @@ export const account = sqliteTable( .$onUpdate(() => /* @__PURE__ */ new Date()) .notNull(), }, - (table) => [index('account_userId_idx').on(table.userId)], + (table) => [ + index('account_userId_idx').on(table.userId), + uniqueIndex('account_issuer_providerAccountId_unique').on(table.issuer, table.providerAccountId), + ], ) export const verification = sqliteTable( @@ -98,6 +102,18 @@ export const verification = sqliteTable( (table) => [index('verification_identifier_idx').on(table.identifier)], ) +export const jwks = sqliteTable('jwks', { + id: text('id').primaryKey(), + publicKey: text('public_key').notNull(), + privateKey: text('private_key').notNull(), + alg: text('alg'), + crv: text('crv'), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }), +}) + export const organization = sqliteTable('organization', { id: text('id').primaryKey(), name: text('name').notNull(), @@ -246,18 +262,71 @@ export const oauthClient = sqliteTable( softwareStatement: text('software_statement'), redirectUris: text('redirect_uris').notNull(), // JSON-serialized string[] postLogoutRedirectUris: text('post_logout_redirect_uris'), // JSON-serialized string[] + backchannelLogoutUri: text('backchannel_logout_uri'), + backchannelLogoutSessionRequired: integer('backchannel_logout_session_required', { mode: 'boolean' }), tokenEndpointAuthMethod: text('token_endpoint_auth_method'), + jwks: text('jwks'), + jwksUri: text('jwks_uri'), grantTypes: text('grant_types'), // JSON-serialized string[] responseTypes: text('response_types'), // JSON-serialized string[] public: integer('public', { mode: 'boolean' }), type: text('type'), requirePKCE: integer('require_pkce', { mode: 'boolean' }), + dpopBoundAccessTokens: integer('dpop_bound_access_tokens', { mode: 'boolean' }).default(false), referenceId: text('reference_id'), metadata: text('metadata'), }, (table) => [index('oauthClient_client_id_idx').on(table.clientId), index('oauthClient_user_id_idx').on(table.userId)], ) +export const oauthResource = sqliteTable( + 'oauthResource', + { + id: text('id').primaryKey(), + identifier: text('identifier').notNull().unique(), + name: text('name').notNull(), + accessTokenTtl: integer('access_token_ttl'), + refreshTokenTtl: integer('refresh_token_ttl'), + signingAlgorithm: text('signing_algorithm'), + signingKeyId: text('signing_key_id'), + allowedScopes: text('allowed_scopes'), + customClaims: text('custom_claims', { mode: 'json' }), + dpopBoundAccessTokensRequired: integer('dpop_bound_access_tokens_required', { mode: 'boolean' }).default(false), + disabled: integer('disabled', { mode: 'boolean' }).default(false), + policyVersion: integer('policy_version').default(1), + metadata: text('metadata', { mode: 'json' }), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + updatedAt: integer('updated_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index('oauthResource_identifier_idx').on(table.identifier)], +) + +export const oauthClientResource = sqliteTable( + 'oauthClientResource', + { + id: text('id').primaryKey(), + clientId: text('client_id') + .notNull() + .references(() => oauthClient.clientId, { onDelete: 'cascade' }), + resourceId: text('resource_id') + .notNull() + .references(() => oauthResource.identifier, { onDelete: 'cascade' }), + metadata: text('metadata', { mode: 'json' }), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + }, + (table) => [ + index('oauthClientResource_client_id_idx').on(table.clientId), + index('oauthClientResource_resource_id_idx').on(table.resourceId), + ], +) + export const oauthRefreshToken = sqliteTable( 'oauthRefreshToken', { @@ -271,12 +340,19 @@ export const oauthRefreshToken = sqliteTable( .notNull() .references(() => user.id, { onDelete: 'cascade' }), referenceId: text('reference_id'), + authorizationCodeId: text('authorization_code_id'), + resources: text('resources'), + requestedUserInfoClaims: text('requested_user_info_claims'), expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) .notNull(), revoked: integer('revoked', { mode: 'timestamp_ms' }), + rotatedAt: integer('rotated_at', { mode: 'timestamp_ms' }), + rotationReplayResponse: text('rotation_replay_response'), + rotationReplayExpiresAt: integer('rotation_replay_expires_at', { mode: 'timestamp_ms' }), authTime: integer('auth_time', { mode: 'timestamp_ms' }), + confirmation: text('confirmation', { mode: 'json' }), scopes: text('scopes').notNull(), // JSON-serialized string[] }, (table) => [ @@ -298,11 +374,16 @@ export const oauthAccessToken = sqliteTable( sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), referenceId: text('reference_id'), + authorizationCodeId: text('authorization_code_id'), + resources: text('resources'), + requestedUserInfoClaims: text('requested_user_info_claims'), refreshId: text('refresh_id').references(() => oauthRefreshToken.id, { onDelete: 'cascade' }), expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) .notNull(), + revoked: integer('revoked', { mode: 'timestamp_ms' }), + confirmation: text('confirmation', { mode: 'json' }), scopes: text('scopes').notNull(), // JSON-serialized string[] }, (table) => [ @@ -323,6 +404,8 @@ export const oauthConsent = sqliteTable( .references(() => oauthClient.clientId, { onDelete: 'cascade' }), userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }), referenceId: text('reference_id'), + resources: text('resources'), + requestedUserInfoClaims: text('requested_user_info_claims'), scopes: text('scopes').notNull(), // JSON-serialized string[] createdAt: integer('created_at', { mode: 'timestamp_ms' }) .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) @@ -339,6 +422,24 @@ export const oauthConsent = sqliteTable( ], ) +export const oauthClientAssertion = sqliteTable('oauthClientAssertion', { + id: text('id').primaryKey(), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), +}) + +export const oauthJwtRevocation = sqliteTable( + 'oauthJwtRevocation', + { + id: text('id').primaryKey(), + clientId: text('client_id').notNull(), + expiresAt: integer('expires_at', { mode: 'timestamp_ms' }).notNull(), + createdAt: integer('created_at', { mode: 'timestamp_ms' }) + .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`) + .notNull(), + }, + (table) => [index('oauthJwtRevocation_expires_at_idx').on(table.expiresAt)], +) + export const downloaderBootstrapCredential = sqliteTable( 'downloader_bootstrap_credentials', { diff --git a/server/http/agent-api-keys.integration.test.ts b/server/http/agent-api-keys.integration.test.ts deleted file mode 100644 index 91687afd..00000000 --- a/server/http/agent-api-keys.integration.test.ts +++ /dev/null @@ -1,346 +0,0 @@ -import { defaultKeyHasher } from '@better-auth/api-key' -import { sql } from 'drizzle-orm' -import { describe, expect, it } from 'vitest' -import { authedHeaders, createTestApp } from '../test/setup.js' - -type TestApp = Awaited> - -function futureIso(days: number): string { - const date = new Date() - date.setDate(date.getDate() + days) - return date.toISOString() -} - -async function getUserAndPersonalOrg(db: TestApp['db'], email = 'test@example.com') { - const users = await db.all<{ id: string }>(sql`SELECT id FROM user WHERE email = ${email}`) - const orgs = await db.all<{ id: string }>(sql` - SELECT o.id - FROM organization o - INNER JOIN member m ON m.organization_id = o.id - WHERE m.user_id = ${users[0]?.id} AND o.metadata LIKE '%"type":"personal"%' - LIMIT 1 - `) - if (!users[0] || !orgs[0]) throw new Error('expected user and personal org') - return { userId: users[0].id, orgId: orgs[0].id } -} - -async function insertStorage(db: TestApp['db']) { - const now = Date.now() - await db.run(sql` - INSERT INTO storages ( - id, bucket, endpoint, region, access_key, secret_key, file_path, custom_host, - capacity, used, enabled, status, egress_credit_billing_enabled, egress_credit_unit_bytes, - egress_credit_per_unit, created_at, updated_at - ) - VALUES ( - 'st-agent', 'test-bucket', 'https://s3.amazonaws.com', 'us-east-1', - 'AKIAIOSFODNN7EXAMPLE', 'secret', '', '', 0, 0, 1, 'untested', - 0, ${100 * 1024 ** 2}, 1, ${now}, ${now} - ) - `) -} - -async function insertFile(db: TestApp['db'], orgId: string, id: string) { - const now = Date.now() - await db.run(sql` - INSERT INTO matters (id, org_id, alias, name, type, size, dirtype, parent, object, storage_id, status, trashed_at, created_at, updated_at) - VALUES (${id}, ${orgId}, ${`${id}-alias`}, ${`${id}.txt`}, 'text/plain', 100, 0, '', 'some/key.txt', 'st-agent', 'active', NULL, ${now}, ${now}) - `) -} - -async function insertLandingShare( - db: TestApp['db'], - input: { token: string; orgId: string; matterId: string; userId: string }, -) { - const now = Date.now() - await db.run(sql` - INSERT INTO shares (id, token, kind, matter_id, org_id, creator_id, status, private, created_at) - VALUES (${`${input.token}-id`}, ${input.token}, 'landing', ${input.matterId}, ${input.orgId}, ${input.userId}, 'active', 0, ${now}) - `) -} - -async function insertTeamOrg(db: TestApp['db'], orgId: string, userId: string, role = 'editor') { - const now = Date.now() - await db.run(sql` - INSERT INTO organization (id, name, slug, metadata, created_at, updated_at) - VALUES (${orgId}, ${`Team ${orgId}`}, ${orgId}, '{"type":"team"}', ${now}, ${now}) - `) - await db.run(sql` - INSERT INTO member (id, organization_id, user_id, role, created_at) - VALUES (${`${orgId}-member`}, ${orgId}, ${userId}, ${role}, ${now}) - `) - await db.run(sql` - INSERT INTO org_quotas (id, org_id, quota, used, traffic_quota, traffic_used, traffic_period) - VALUES (${`${orgId}-quota`}, ${orgId}, 1000000, 0, 0, 0, '1970-01') - `) -} - -async function createAgentKey(app: TestApp['app'], headers: Record, orgId: string, scopes: string[]) { - const res = await app.request(`/api/workspaces/${orgId}/agent-api-keys`, { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: 'CI', scopes, expiresAt: futureIso(90) }), - }) - if (res.status !== 201) throw new Error(`create failed: ${res.status} ${await res.text()}`) - return (await res.json()) as { key: string; item: { id: string; orgId: string; scopes: string[]; status: string } } -} - -async function insertLegacyAgentKey(db: TestApp['db'], userId: string): Promise { - const now = Date.now() - const key = 'zpan_agent_legacy_integration_key' - const hashedKey = await defaultKeyHasher(key) - await db.run(sql` - INSERT INTO apikey ( - id, config_id, name, start, reference_id, prefix, key, - enabled, rate_limit_enabled, rate_limit_time_window, rate_limit_max, request_count, - expires_at, created_at, updated_at, permissions, metadata - ) - VALUES ( - 'legacy-agent-key', 'agent', 'Legacy Agent key', 'zpan_age', ${userId}, 'zpan_agent_', ${hashedKey}, - 1, 1, 60000, 600, 0, - ${now + 90 * 24 * 60 * 60 * 1000}, ${now}, ${now}, '{"objects":["read"]}', NULL - ) - `) - return key -} - -describe('Agent API keys', () => { - it('creates, lists, rotates, and revokes a personal workspace key [spec: agent-api-keys/lifecycle]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { orgId } = await getUserAndPersonalOrg(db) - - const created = await createAgentKey(app, headers, orgId, ['objects:read']) - expect(created.key).toMatch(/^zpan_agent_/) - expect(created.item).toMatchObject({ orgId, scopes: ['objects:read'], status: 'active' }) - - const list = await app.request(`/api/workspaces/${orgId}/agent-api-keys`, { headers }) - expect(list.status).toBe(200) - const listed = (await list.json()) as { items: Array<{ id: string; key?: string }> } - expect(listed.items.map((item) => item.id)).toContain(created.item.id) - expect(listed.items[0]?.key).toBeUndefined() - - const rotated = await app.request(`/api/workspaces/${orgId}/agent-api-keys/${created.item.id}/rotations`, { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({}), - }) - expect(rotated.status).toBe(201) - const rotatedBody = (await rotated.json()) as { key: string; item: { id: string } } - expect(rotatedBody.key).toMatch(/^zpan_agent_/) - expect(rotatedBody.item.id).not.toBe(created.item.id) - - const revoke = await app.request(`/api/workspaces/${orgId}/agent-api-keys/${rotatedBody.item.id}`, { - method: 'DELETE', - headers, - }) - expect(revoke.status).toBe(204) - }) - - it('creates and uses a team workspace key for allowed file operations [spec: agent-api-keys/team-file-ops]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - await insertStorage(db) - const { userId } = await getUserAndPersonalOrg(db) - await insertTeamOrg(db, 'agent-team', userId, 'owner') - await insertFile(db, 'agent-team', 'agent-readable') - const created = await createAgentKey(app, headers, 'agent-team', ['objects:read', 'objects:create']) - await db.run(sql`UPDATE member SET role = 'editor' WHERE organization_id = 'agent-team' AND user_id = ${userId}`) - const auth = { Authorization: `Bearer ${created.key}` } - - const list = await app.request('/api/objects', { headers: auth }) - expect(list.status).toBe(200) - const listBody = (await list.json()) as { items: Array<{ id: string }> } - expect(listBody.items.map((item) => item.id)).toContain('agent-readable') - - const create = await app.request('/api/objects', { - method: 'POST', - headers: { ...auth, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: 'agent-folder', type: 'folder', dirtype: 1, parent: '' }), - }) - expect(create.status).toBe(201) - }) - - it('allows team owners and admins to manage keys but denies editors [spec: agent-api-keys/management-role]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { userId } = await getUserAndPersonalOrg(db) - await insertStorage(db) - await insertTeamOrg(db, 'agent-editor-team', userId, 'editor') - await insertTeamOrg(db, 'agent-admin-team', userId, 'admin') - await insertFile(db, 'agent-admin-team', 'agent-admin-readable') - - const editorList = await app.request('/api/workspaces/agent-editor-team/agent-api-keys', { headers }) - expect(editorList.status).toBe(403) - const editorCreate = await app.request('/api/workspaces/agent-editor-team/agent-api-keys', { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: 'Denied', scopes: ['objects:read'], expiresAt: futureIso(90) }), - }) - expect(editorCreate.status).toBe(403) - - const adminCreated = await createAgentKey(app, headers, 'agent-admin-team', ['objects:read']) - expect(adminCreated.item.orgId).toBe('agent-admin-team') - const adminList = await app.request('/api/objects', { - headers: { Authorization: `Bearer ${adminCreated.key}` }, - }) - expect(adminList.status).toBe(200) - }) - - it('rejects disallowed scopes and raw Better Auth Agent key creation [spec: agent-api-keys/scope-boundary]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { orgId } = await getUserAndPersonalOrg(db) - - const disallowed = await app.request(`/api/workspaces/${orgId}/agent-api-keys`, { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: 'bad', scopes: ['images:upload'], expiresAt: futureIso(90) }), - }) - expect(disallowed.status).toBe(400) - - const raw = await app.request('/api/auth/api-key/create', { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ configId: 'agent', organizationId: orgId, permissions: { images: ['upload'] } }), - }) - expect(raw.status).toBe(400) - }) - - it('denies missing scope, wrong workspace, revoked key, expired key, and banned owner [spec: agent-api-keys/denials]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { orgId, userId } = await getUserAndPersonalOrg(db) - await insertStorage(db) - const created = await createAgentKey(app, headers, orgId, ['objects:create']) - const auth = { Authorization: `Bearer ${created.key}` } - - const missingScope = await app.request('/api/objects', { headers: auth }) - expect(missingScope.status).toBe(403) - - const wrongWorkspace = await app.request('/api/objects?orgId=agent-other-workspace', { headers: auth }) - expect(wrongWorkspace.status).toBe(403) - - await app.request(`/api/workspaces/${orgId}/agent-api-keys/${created.item.id}`, { method: 'DELETE', headers }) - const revoked = await app.request('/api/objects', { headers: auth }) - expect(revoked.status).toBe(401) - - const expired = await createAgentKey(app, headers, orgId, ['objects:read']) - await db.run(sql`UPDATE apikey SET expires_at = ${Date.now() - 1000} WHERE id = ${expired.item.id}`) - const expiredRes = await app.request('/api/objects', { headers: { Authorization: `Bearer ${expired.key}` } }) - expect(expiredRes.status).toBe(401) - - const banned = await createAgentKey(app, headers, orgId, ['objects:read']) - await db.run(sql`UPDATE user SET banned = 1 WHERE id = ${userId}`) - const bannedRes = await app.request('/api/objects', { headers: { Authorization: `Bearer ${banned.key}` } }) - expect(bannedRes.status).toBe(401) - }) - - it('treats expired and revoked keys as terminal for rotation [spec: agent-api-keys/terminal-rotation]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { orgId } = await getUserAndPersonalOrg(db) - - const expired = await createAgentKey(app, headers, orgId, ['objects:read']) - await db.run(sql`UPDATE apikey SET expires_at = ${Date.now() - 1000} WHERE id = ${expired.item.id}`) - const expiredRotation = await app.request(`/api/workspaces/${orgId}/agent-api-keys/${expired.item.id}/rotations`, { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ expiresAt: futureIso(90) }), - }) - expect(expiredRotation.status).toBe(409) - await expect(expiredRotation.json()).resolves.toMatchObject({ - error: { details: [{ reason: 'AGENT_API_KEY_NOT_ACTIVE' }] }, - }) - - const revoked = await createAgentKey(app, headers, orgId, ['objects:read']) - await app.request(`/api/workspaces/${orgId}/agent-api-keys/${revoked.item.id}`, { - method: 'DELETE', - headers, - }) - const revokedRotation = await app.request(`/api/workspaces/${orgId}/agent-api-keys/${revoked.item.id}/rotations`, { - method: 'POST', - headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({}), - }) - expect(revokedRotation.status).toBe(409) - await expect(revokedRotation.json()).resolves.toMatchObject({ - error: { details: [{ reason: 'AGENT_API_KEY_NOT_ACTIVE' }] }, - }) - }) - - it('rechecks team role before management and file operations [spec: agent-api-keys/role-reduction]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { userId } = await getUserAndPersonalOrg(db) - await insertStorage(db) - await insertTeamOrg(db, 'agent-role-team', userId, 'owner') - await insertFile(db, 'agent-role-team', 'agent-role-share-file') - await insertLandingShare(db, { - token: 'agent-role-share', - orgId: 'agent-role-team', - matterId: 'agent-role-share-file', - userId, - }) - const created = await createAgentKey(app, headers, 'agent-role-team', [ - 'objects:create', - 'shares:create', - 'shares:delete', - ]) - await db.run( - sql`UPDATE member SET role = 'viewer' WHERE organization_id = 'agent-role-team' AND user_id = ${userId}`, - ) - const auth = { Authorization: `Bearer ${created.key}`, 'Content-Type': 'application/json' } - - const management = await app.request('/api/workspaces/agent-role-team/agent-api-keys', { headers }) - expect(management.status).toBe(403) - - const create = await app.request('/api/objects', { - method: 'POST', - headers: auth, - body: JSON.stringify({ name: 'blocked', type: 'folder', dirtype: 1, parent: '' }), - }) - expect(create.status).toBe(403) - - const privacy = await app.request('/api/shares/agent-role-share/privacy', { - method: 'PUT', - headers: auth, - body: JSON.stringify({ private: true }), - }) - expect(privacy.status).toBe(403) - - const revoke = await app.request('/api/shares/agent-role-share/status', { - method: 'PUT', - headers: auth, - body: JSON.stringify({ status: 'revoked' }), - }) - expect(revoke.status).toBe(403) - }) - - it('denies an old team workspace key after the owner membership is removed [spec: agent-api-keys/denials]', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app) - const { userId } = await getUserAndPersonalOrg(db) - await insertStorage(db) - await insertTeamOrg(db, 'agent-removed-team', userId, 'owner') - await insertFile(db, 'agent-removed-team', 'agent-removed-readable') - const created = await createAgentKey(app, headers, 'agent-removed-team', ['objects:read']) - - await db.run(sql`DELETE FROM member WHERE organization_id = 'agent-removed-team' AND user_id = ${userId}`) - - const denied = await app.request('/api/objects?orgId=agent-removed-team', { - headers: { Authorization: `Bearer ${created.key}` }, - }) - expect(denied.status).toBe(403) - }) - - it('denies a legacy Better Auth Agent key without scoped metadata [spec: agent-api-keys/denials]', async () => { - const { app, db } = await createTestApp() - await authedHeaders(app) - const { userId } = await getUserAndPersonalOrg(db) - const key = await insertLegacyAgentKey(db, userId) - - const denied = await app.request('/api/objects', { headers: { Authorization: `Bearer ${key}` } }) - expect(denied.status).toBe(401) - }) -}) diff --git a/server/http/agent-api-keys.ts b/server/http/agent-api-keys.ts deleted file mode 100644 index cffdc020..00000000 --- a/server/http/agent-api-keys.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { OpenAPIHono, z } from '@hono/zod-openapi' -import { AuthorizationScope } from '@shared/authorization' -import { - agentApiKeyCreatedSchema, - agentApiKeyCreateSchema, - agentApiKeyListSchema, - agentApiKeyRotateSchema, -} from '@shared/schemas' -import type { Env } from '../middleware/platform' -import { createAgentApiKey, listAgentApiKeys, revokeAgentApiKey, rotateAgentApiKey } from '../usecases/agent-api-keys' -import { authRoute, errorResponse, jsonBody, jsonContent } from './openapi' - -const workspaceParamsSchema = z.object({ orgId: z.string().min(1) }) -const keyParamsSchema = workspaceParamsSchema.extend({ keyId: z.string().min(1) }) -const listQuerySchema = z.object({ - page: z.coerce.number().int().min(1).default(1), - pageSize: z.coerce.number().int().min(1).max(100).default(50), -}) - -const listRoute = authRoute( - { scopes: [AuthorizationScope.AGENT_API_KEYS_READ] }, - { - operationId: 'listWorkspaceAgentApiKeys', - summary: 'List Agent API keys for a workspace', - tags: ['Agent Access'], - method: 'get', - path: '/{orgId}/agent-api-keys', - request: { params: workspaceParamsSchema, query: listQuerySchema }, - responses: { - 200: jsonContent(agentApiKeyListSchema, 'Agent API keys'), - 403: errorResponse('Forbidden'), - }, - }, -) - -const createRoute = authRoute( - { scopes: [AuthorizationScope.AGENT_API_KEYS_CREATE] }, - { - operationId: 'createWorkspaceAgentApiKey', - summary: 'Create an Agent API key for a workspace', - tags: ['Agent Access'], - method: 'post', - path: '/{orgId}/agent-api-keys', - request: { params: workspaceParamsSchema, ...jsonBody(agentApiKeyCreateSchema) }, - responses: { - 201: jsonContent(agentApiKeyCreatedSchema, 'Created Agent API key'), - 400: errorResponse('Bad request'), - 403: errorResponse('Forbidden'), - }, - }, -) - -const rotateRoute = authRoute( - { scopes: [AuthorizationScope.AGENT_API_KEYS_UPDATE] }, - { - operationId: 'rotateWorkspaceAgentApiKey', - summary: 'Rotate an Agent API key for a workspace', - tags: ['Agent Access'], - method: 'post', - path: '/{orgId}/agent-api-keys/{keyId}/rotations', - request: { params: keyParamsSchema, ...jsonBody(agentApiKeyRotateSchema) }, - responses: { - 201: jsonContent(agentApiKeyCreatedSchema, 'Rotated Agent API key'), - 400: errorResponse('Bad request'), - 409: errorResponse('Agent API key is not active'), - 403: errorResponse('Forbidden'), - 404: errorResponse('Agent API key not found'), - }, - }, -) - -const revokeRoute = authRoute( - { scopes: [AuthorizationScope.AGENT_API_KEYS_DELETE] }, - { - operationId: 'revokeWorkspaceAgentApiKey', - summary: 'Revoke an Agent API key for a workspace', - tags: ['Agent Access'], - method: 'delete', - path: '/{orgId}/agent-api-keys/{keyId}', - request: { params: keyParamsSchema }, - responses: { - 204: { description: 'Revoked' }, - 403: errorResponse('Forbidden'), - 404: errorResponse('Agent API key not found'), - }, - }, -) - -const agentApiKeys = new OpenAPIHono() - .openapi(listRoute, async (c) => { - const { orgId } = c.req.valid('param') - const { page, pageSize } = c.req.valid('query') - const result = await listAgentApiKeys(c.get('deps'), c.get('platform').db, { - userId: c.get('userId')!, - orgId, - page, - pageSize, - }) - return c.json(result, 200) - }) - .openapi(createRoute, async (c) => { - const { orgId } = c.req.valid('param') - const result = await createAgentApiKey(c.get('deps'), c.get('platform').db, { - userId: c.get('userId')!, - orgId, - body: c.req.valid('json'), - }) - return c.json(result, 201) - }) - .openapi(rotateRoute, async (c) => { - const { orgId, keyId } = c.req.valid('param') - const result = await rotateAgentApiKey(c.get('deps'), c.get('platform').db, { - userId: c.get('userId')!, - orgId, - keyId, - body: c.req.valid('json'), - }) - return c.json(result, 201) - }) - .openapi(revokeRoute, async (c) => { - const { orgId, keyId } = c.req.valid('param') - await revokeAgentApiKey(c.get('deps'), c.get('platform').db, { - userId: c.get('userId')!, - orgId, - keyId, - }) - return c.body(null, 204) - }) - -export default agentApiKeys diff --git a/server/http/agent-oauth-grants.integration.test.ts b/server/http/agent-oauth-grants.integration.test.ts index 306bebe2..45475a30 100644 --- a/server/http/agent-oauth-grants.integration.test.ts +++ b/server/http/agent-oauth-grants.integration.test.ts @@ -1,16 +1,14 @@ -import { createHash } from 'node:crypto' -import { - AGENT_OAUTH_ACCESS_TOKEN_SECONDS, - AGENT_OAUTH_CLIENT_ID, - AGENT_OAUTH_CLIENT_NAME, - AGENT_OAUTH_REFRESH_TOKEN_SECONDS, -} from '@shared/agent-oauth' +import { AGENT_OAUTH_ACCESS_TOKEN_SECONDS, AGENT_OAUTH_REFRESH_TOKEN_SECONDS } from '@shared/agent-oauth' import { AuthorizationScope } from '@shared/authorization' -import { eq, sql } from 'drizzle-orm' +import { sql } from 'drizzle-orm' import { describe, expect, it } from 'vitest' import * as authSchema from '../db/auth-schema.js' import { authedHeaders, createTestApp } from '../test/setup.js' +const CLIENT_ID = 'dynamic-client' +const CLIENT_NAME = 'FlareAuth' +const REDIRECT_URI = 'https://flareauth.example/callback' + type TestContext = Awaited> async function getUserAndPersonalOrg(db: TestContext['db'], email: string) { @@ -26,16 +24,31 @@ async function getUserAndPersonalOrg(db: TestContext['db'], email: string) { return rows[0] } -async function insertTeamOrg(db: TestContext['db'], orgId: string, userId: string) { - const now = Date.now() - await db.run(sql` - INSERT INTO organization (id, name, slug, metadata, created_at, updated_at) - VALUES (${orgId}, ${`Team ${orgId}`}, ${orgId}, '{"type":"team"}', ${now}, ${now}) - `) - await db.run(sql` - INSERT INTO member (id, organization_id, user_id, role, created_at) - VALUES (${`${orgId}-member`}, ${orgId}, ${userId}, 'owner', ${now}) - `) +async function insertClient(db: TestContext['db']) { + await db.insert(authSchema.oauthClient).values({ + id: CLIENT_ID, + clientId: CLIENT_ID, + clientSecret: null, + disabled: false, + skipConsent: false, + enableEndSession: false, + subjectType: 'public', + scopes: JSON.stringify([ + 'openid', + 'offline_access', + AuthorizationScope.OBJECTS_READ, + AuthorizationScope.QUOTA_READ, + ]), + name: CLIENT_NAME, + uri: 'https://flareauth.example', + redirectUris: JSON.stringify([REDIRECT_URI]), + tokenEndpointAuthMethod: 'none', + grantTypes: JSON.stringify(['authorization_code', 'refresh_token']), + responseTypes: JSON.stringify(['code']), + public: true, + type: 'web', + requirePKCE: true, + }) } async function insertGrant( @@ -45,7 +58,7 @@ async function insertGrant( const now = new Date('2026-07-29T12:00:00.000Z') await db.insert(authSchema.oauthConsent).values({ id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId: input.userId, referenceId: input.orgId, scopes: JSON.stringify(input.scopes), @@ -55,7 +68,7 @@ async function insertGrant( await db.insert(authSchema.oauthRefreshToken).values({ id: 'refresh-1', token: 'hashed-refresh', - clientId: AGENT_OAUTH_CLIENT_ID, + clientId: CLIENT_ID, userId: input.userId, referenceId: input.orgId, expiresAt: new Date(Date.now() + 60_000), @@ -64,8 +77,8 @@ async function insertGrant( }) await db.insert(authSchema.oauthAccessToken).values({ id: 'access-1', - token: hashStoredToken('live-agent-token'), - clientId: AGENT_OAUTH_CLIENT_ID, + token: 'hashed-access', + clientId: CLIENT_ID, userId: input.userId, referenceId: input.orgId, refreshId: 'refresh-1', @@ -75,29 +88,35 @@ async function insertGrant( }) } +function oauthQuery() { + return new URLSearchParams({ + client_id: CLIENT_ID, + redirect_uri: REDIRECT_URI, + response_type: 'code', + scope: `${AuthorizationScope.OBJECTS_READ} ${AuthorizationScope.QUOTA_READ} openid offline_access`, + }).toString() +} + describe('Agent OAuth grants API integration', () => { - it('returns server-owned Agent OAuth consent context for the active workspace', async () => { + it('returns consent context for a dynamically registered application', async () => { const { app, db } = await createTestApp() + await insertClient(db) const headers = await authedHeaders(app, 'agent-consent@example.com') const { orgId } = await getUserAndPersonalOrg(db, 'agent-consent@example.com') - const oauthQuery = new URLSearchParams({ - client_id: AGENT_OAUTH_CLIENT_ID, - redirect_uri: 'http://127.0.0.1:8484/callback', - response_type: 'code', - scope: `${AuthorizationScope.OBJECTS_READ} ${AuthorizationScope.QUOTA_READ} openid offline_access`, - }).toString() - const res = await app.request(`/api/agent-oauth-consent?oauthQuery=${encodeURIComponent(oauthQuery)}`, { headers }) + const res = await app.request(`/api/agent-oauth-consent?oauthQuery=${encodeURIComponent(oauthQuery())}`, { + headers, + }) expect(res.status).toBe(200) await expect(res.json()).resolves.toEqual({ - clientId: AGENT_OAUTH_CLIENT_ID, - clientName: AGENT_OAUTH_CLIENT_NAME, + clientId: CLIENT_ID, + clientName: CLIENT_NAME, instanceOrigin: 'http://localhost', workspace: { id: orgId, name: expect.any(String) }, scopes: [AuthorizationScope.OBJECTS_READ, AuthorizationScope.QUOTA_READ], standardScopes: ['openid', 'offline_access'], - redirectUri: 'http://127.0.0.1:8484/callback', + redirectUri: REDIRECT_URI, grantLifetime: { accessTokenSeconds: AGENT_OAUTH_ACCESS_TOKEN_SECONDS, refreshTokenSeconds: AGENT_OAUTH_REFRESH_TOKEN_SECONDS, @@ -105,61 +124,24 @@ describe('Agent OAuth grants API integration', () => { }) }) - it('revalidates OAuth consent submission through the Agent Access API', async () => { - const { app } = await createTestApp() + it('revalidates malformed OAuth consent submissions', async () => { + const { app, db } = await createTestApp() + await insertClient(db) const headers = await authedHeaders(app, 'agent-submit@example.com') const res = await app.request('/api/agent-oauth-consent', { method: 'POST', headers: { ...headers, 'Content-Type': 'application/json' }, - body: JSON.stringify({ accept: true, oauthQuery: 'client_id=zpan-agent&response_type=token' }), + body: JSON.stringify({ accept: true, oauthQuery: `client_id=${CLIENT_ID}&response_type=token` }), }) expect(res.status).toBe(400) - await expect(res.json()).resolves.toMatchObject({ - error: { - message: 'Invalid Agent OAuth request', - }, - }) + await expect(res.json()).resolves.toMatchObject({ error: { message: 'Invalid Agent OAuth request' } }) }) - it('submits full OAuth consent through the Agent Access API', async () => { - const { app } = await createTestApp() - const headers = await authedHeaders(app, 'agent-submit-success@example.com') - const oauthParams = new URLSearchParams({ - client_id: AGENT_OAUTH_CLIENT_ID, - redirect_uri: 'http://127.0.0.1:8484/callback', - response_type: 'code', - scope: `${AuthorizationScope.OBJECTS_READ} ${AuthorizationScope.QUOTA_READ} openid offline_access`, - state: 'agent-submit-success', - code_challenge: 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM', - code_challenge_method: 'S256', - }) - const authorize = await app.request(`/api/auth/oauth2/authorize?${oauthParams}`, { - headers: { ...headers, Origin: 'http://localhost' }, - }) - const consentLocation = authorize.headers.get('location') - expect(authorize.status).toBe(302) - expect(consentLocation).toMatch(/^\/settings\/agent-access\?/) - - const consent = await app.request('/api/agent-oauth-consent', { - method: 'POST', - headers: { ...headers, Origin: 'http://localhost', 'Content-Type': 'application/json' }, - body: JSON.stringify({ - accept: true, - oauthQuery: consentLocation?.slice(consentLocation.indexOf('?') + 1), - }), - }) - const consentBody = await consent.text() - - expect(consent.status, consentBody).toBe(200) - expect(JSON.parse(consentBody)).toMatchObject({ - url: expect.stringMatching(/^http:\/\/127\.0\.0\.1:8484\/callback\?code=/), - }) - }) - - it('lists and revokes the current user grant family', async () => { + it('lists and revokes the current user dynamic-client grant family', async () => { const { app, db } = await createTestApp() + await insertClient(db) const headers = await authedHeaders(app, 'agent-grants@example.com') const { userId, orgId } = await getUserAndPersonalOrg(db, 'agent-grants@example.com') await insertGrant(db, { userId, orgId, scopes: [AuthorizationScope.OBJECTS_READ, AuthorizationScope.QUOTA_READ] }) @@ -170,8 +152,8 @@ describe('Agent OAuth grants API integration', () => { items: [ { id: 'grant-1', - clientId: AGENT_OAUTH_CLIENT_ID, - clientName: 'ZPan Agent', + clientId: CLIENT_ID, + clientName: CLIENT_NAME, userId, orgId, workspaceName: expect.any(String), @@ -191,68 +173,13 @@ describe('Agent OAuth grants API integration', () => { expect(refresh.revoked).not.toBeNull() }) - it('enforces live grant membership and a bound workspace for Agent OAuth bearer access', async () => { - const { app, db } = await createTestApp() - const headers = await authedHeaders(app, 'agent-scope@example.com') - const { userId, orgId } = await getUserAndPersonalOrg(db, 'agent-scope@example.com') - await insertTeamOrg(db, 'other-workspace', userId) - await insertGrant(db, { userId, orgId, scopes: [AuthorizationScope.OBJECTS_READ] }) - - const list = await app.request('/api/agent-oauth-grants', { headers }) - expect(list.status).toBe(200) - await expect(list.json()).resolves.toMatchObject({ items: [{ id: 'grant-1', lastUsedAt: null }] }) - - const bearer = { Authorization: 'Bearer live-agent-token' } - const allowed = await app.request('/api/objects', { headers: bearer }) - expect(allowed.status).toBe(200) - const [usedGrant] = await db - .select({ lastUsedAt: authSchema.oauthConsent.lastUsedAt }) - .from(authSchema.oauthConsent) - .where(eq(authSchema.oauthConsent.id, 'grant-1')) - expect(usedGrant.lastUsedAt).toBeInstanceOf(Date) - - const wrongWorkspace = await app.request('/api/objects?orgId=other-workspace', { headers: bearer }) - expect(wrongWorkspace.status).toBe(403) - - const revoke = await app.request('/api/agent-oauth-grants/grant-1', { method: 'DELETE', headers }) - expect(revoke.status).toBe(204) - - const revoked = await app.request('/api/objects', { headers: bearer }) - expect(revoked.status).toBe(401) - }) - - it('blocks generic Better Auth OAuth consent mutation endpoints', async () => { - const { app } = await createTestApp() - - for (const path of ['/api/auth/oauth2/update-consent', '/api/auth/oauth2/delete-consent']) { - const res = await app.request(path, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ client_id: AGENT_OAUTH_CLIENT_ID }), - }) - - expect(res.status).toBe(403) - await expect(res.json()).resolves.toMatchObject({ - error_description: 'Manage Agent OAuth grants from the Agent Access API', - }) - } - }) - - it('returns 404 when revoking a missing Agent OAuth grant', async () => { + it('returns 404 when revoking a missing grant', async () => { const { app } = await createTestApp() const headers = await authedHeaders(app, 'agent-missing-grant@example.com') const revoke = await app.request('/api/agent-oauth-grants/missing-grant', { method: 'DELETE', headers }) expect(revoke.status).toBe(404) - await expect(revoke.json()).resolves.toMatchObject({ - error: { - message: 'Agent OAuth grant not found', - }, - }) + await expect(revoke.json()).resolves.toMatchObject({ error: { message: 'Agent OAuth grant not found' } }) }) }) - -function hashStoredToken(token: string): string { - return createHash('sha256').update(token).digest('base64url') -} diff --git a/server/http/agent-oauth-grants.ts b/server/http/agent-oauth-grants.ts index 4ebeb900..78190171 100644 --- a/server/http/agent-oauth-grants.ts +++ b/server/http/agent-oauth-grants.ts @@ -82,6 +82,7 @@ export const agentOAuthGrants = new OpenAPIHono() .openapi(consentContextRoute, async (c) => { const { oauthQuery } = c.req.valid('query') const context = await getAgentOAuthConsentContext(c.get('deps'), { + db: c.get('platform').db, userId: c.get('userId')!, orgId: c.get('orgId'), requestUrl: c.req.url, @@ -92,6 +93,7 @@ export const agentOAuthGrants = new OpenAPIHono() .openapi(consentSubmitRoute, async (c) => { const { accept, oauthQuery } = c.req.valid('json') await getAgentOAuthConsentContext(c.get('deps'), { + db: c.get('platform').db, userId: c.get('userId')!, orgId: c.get('orgId'), requestUrl: c.req.url, diff --git a/server/http/arazzo.ts b/server/http/arazzo.ts new file mode 100644 index 00000000..d25ae829 --- /dev/null +++ b/server/http/arazzo.ts @@ -0,0 +1,184 @@ +export const ARAZZO_DOCUMENT_PATH = '/api/workflows.arazzo.json' +export const ARAZZO_MEDIA_TYPE = 'application/vnd.oai.workflows+json; version=1.1.0' + +export function createArazzoDocument(origin: string) { + return { + arazzo: '1.1.0', + $self: `${origin}${ARAZZO_DOCUMENT_PATH}`, + info: { + title: 'ZPan API workflows', + summary: 'Machine-readable file workflows for the ZPan API', + description: + 'These workflows compose the OpenAPI operations around direct-to-storage uploads. Presigned storage requests are executed from the runtime upload descriptor returned by prepareDirectFileUpload.', + version: '1.0.0', + }, + sourceDescriptions: [ + { + name: 'zpan', + url: './openapi.json', + type: 'openapi', + }, + ], + workflows: [ + { + workflowId: 'prepareDirectFileUpload', + summary: 'Prepare a direct file upload', + description: + 'Creates a file draft and returns the runtime upload descriptor. PUT every local file slice identified by upload.parts[].offset and upload.parts[].length to upload.parts[].url with upload.parts[].headers. Capture each response ETag, then invoke completeDirectFileUpload. If a presigned URL expires, invoke refreshDirectFileUploadParts. File bytes are sent directly to storage, not to ZPan.', + inputs: { + type: 'object', + properties: { + name: { type: 'string', minLength: 1 }, + contentType: { type: 'string', minLength: 1 }, + size: { type: 'integer', minimum: 0 }, + parent: { type: 'string', default: '' }, + onConflict: { + type: 'string', + enum: ['fail', 'rename', 'replace'], + default: 'fail', + }, + }, + required: ['name', 'contentType', 'size', 'parent', 'onConflict'], + }, + steps: [ + { + stepId: 'createUploadDraft', + operationId: 'createObject', + requestBody: { + contentType: 'application/json', + payload: { + name: '$inputs.name', + type: '$inputs.contentType', + size: '$inputs.size', + parent: '$inputs.parent', + onConflict: '$inputs.onConflict', + }, + }, + successCriteria: [{ condition: '$statusCode == 201' }], + outputs: { + objectId: '$response.body#/id', + sessionId: '$response.body#/upload/sessionId', + upload: '$response.body#/upload', + }, + }, + ], + outputs: { + objectId: '$steps.createUploadDraft.outputs.objectId', + sessionId: '$steps.createUploadDraft.outputs.sessionId', + upload: '$steps.createUploadDraft.outputs.upload', + }, + }, + { + workflowId: 'refreshDirectFileUploadParts', + summary: 'Refresh expired direct-upload URLs', + description: + 'Requests replacement presigned URLs for selected multipart upload parts. Continue using the returned offset, length, headers, and URL for each part.', + inputs: { + type: 'object', + properties: { + objectId: { type: 'string', minLength: 1 }, + sessionId: { type: 'string', minLength: 1 }, + partNumbers: { + type: 'array', + minItems: 1, + items: { type: 'integer', minimum: 1 }, + }, + }, + required: ['objectId', 'sessionId', 'partNumbers'], + }, + steps: [ + { + stepId: 'refreshUploadParts', + operationId: 'presignObjectUploadParts', + parameters: [ + { name: 'id', in: 'path', value: '$inputs.objectId' }, + { name: 'uploadSessionId', in: 'path', value: '$inputs.sessionId' }, + ], + requestBody: { + contentType: 'application/json', + payload: { partNumbers: '$inputs.partNumbers' }, + }, + successCriteria: [{ condition: '$statusCode == 200' }], + outputs: { + uploadParts: '$response.body', + }, + }, + ], + outputs: { + uploadParts: '$steps.refreshUploadParts.outputs.uploadParts', + }, + }, + { + workflowId: 'completeDirectFileUpload', + summary: 'Complete a direct file upload', + description: + 'Finalizes a prepared upload after every part has been PUT to storage. Supply one partNumber and captured ETag for every advertised part.', + inputs: { + type: 'object', + properties: { + objectId: { type: 'string', minLength: 1 }, + sessionId: { type: 'string', minLength: 1 }, + parts: { + type: 'array', + minItems: 1, + items: { + type: 'object', + properties: { + partNumber: { type: 'integer', minimum: 1 }, + etag: { type: 'string', minLength: 1 }, + }, + required: ['partNumber', 'etag'], + }, + }, + }, + required: ['objectId', 'sessionId', 'parts'], + }, + steps: [ + { + stepId: 'completeUpload', + operationId: 'completeObjectUpload', + parameters: [ + { name: 'id', in: 'path', value: '$inputs.objectId' }, + { name: 'uploadSessionId', in: 'path', value: '$inputs.sessionId' }, + ], + requestBody: { + contentType: 'application/json', + payload: { parts: '$inputs.parts' }, + }, + successCriteria: [{ condition: '$statusCode == 200' }], + outputs: { + object: '$response.body', + }, + }, + ], + outputs: { + object: '$steps.completeUpload.outputs.object', + }, + }, + { + workflowId: 'abortDirectFileUpload', + summary: 'Abort an unfinished direct file upload', + description: 'Discards an unfinished upload session and its draft object.', + inputs: { + type: 'object', + properties: { + objectId: { type: 'string', minLength: 1 }, + sessionId: { type: 'string', minLength: 1 }, + }, + required: ['objectId', 'sessionId'], + }, + steps: [ + { + stepId: 'abortUpload', + operationId: 'abortObjectUpload', + parameters: [ + { name: 'id', in: 'path', value: '$inputs.objectId' }, + { name: 'uploadSessionId', in: 'path', value: '$inputs.sessionId' }, + ], + successCriteria: [{ condition: '$statusCode == 204' }], + }, + ], + }, + ], + } as const +} diff --git a/server/http/auth/auth.cf-test.ts b/server/http/auth/auth.cf-test.ts index 398f98b5..714f0a69 100644 --- a/server/http/auth/auth.cf-test.ts +++ b/server/http/auth/auth.cf-test.ts @@ -55,9 +55,23 @@ describe('[CF] Auth API', () => { .getSetCookie() .map((value) => value.split(';', 1)[0]) .join('; ') + const registration = await app.request('/api/auth/oauth2/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + client_name: 'CF Consent Test Client', + redirect_uris: ['https://broker.example.com/callback'], + grant_types: ['authorization_code', 'refresh_token'], + response_types: ['code'], + token_endpoint_auth_method: 'none', + scope: 'openid offline_access objects:read quota:read', + }), + }) + const registered = (await registration.json()) as { client_id: string } + expect(registration.status).toBe(201) const params = new URLSearchParams({ - client_id: 'zpan-agent', - redirect_uri: 'http://127.0.0.1:8484/callback', + client_id: registered.client_id, + redirect_uri: 'https://broker.example.com/callback', response_type: 'code', scope: 'openid offline_access objects:read quota:read', state: 'cf-agent-oauth', @@ -83,7 +97,7 @@ describe('[CF] Auth API', () => { expect(consent.status, consentBody).toBe(200) expect(JSON.parse(consentBody)).toMatchObject({ - url: expect.stringMatching(/^http:\/\/127\.0\.0\.1:8484\/callback\?code=/), + url: expect.stringMatching(/^https:\/\/broker\.example\.com\/callback\?code=/), }) }) diff --git a/server/http/downloads/download-tasks.integration.test.ts b/server/http/downloads/download-tasks.integration.test.ts index 88941c74..e9e0f592 100644 --- a/server/http/downloads/download-tasks.integration.test.ts +++ b/server/http/downloads/download-tasks.integration.test.ts @@ -207,7 +207,7 @@ describe('Download tasks API integration', () => { const wrongClient = await app.request('/api/auth/device/code', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ client_id: 'zpan-agent', scope: 'downloader:register' }), + body: JSON.stringify({ client_id: 'unrelated-client', scope: 'downloader:register' }), }) expect(wrongClient.status).toBe(400) }) diff --git a/server/http/oauth-resource-scopes.ts b/server/http/oauth-resource-scopes.ts new file mode 100644 index 00000000..06f51436 --- /dev/null +++ b/server/http/oauth-resource-scopes.ts @@ -0,0 +1,47 @@ +import { OpenAPIHono, z } from '@hono/zod-openapi' +import { AGENT_OAUTH_RESOURCE_SCOPES, AGENT_OAUTH_SCOPE_DESCRIPTIONS } from '@shared/agent-oauth' +import type { Env } from '../middleware/platform' +import { authRoute, jsonContent } from './openapi' + +const scopeSchema = z.object({ + value: z.string(), + description: z.string(), +}) + +const route = authRoute( + { public: true }, + { + operationId: 'listOAuthResourceScopes', + summary: 'List OAuth resource scopes', + description: + 'Public scope catalog for external authorization controllers. Runtime API operations remain protected by their x-zpan-auth declarations.', + tags: ['OAuth'], + method: 'get', + path: '/', + responses: { + 200: jsonContent(z.object({ scopes: z.array(scopeSchema) }), 'OAuth resource scope catalog'), + }, + }, +) + +// FlareAuth derives requestable business scopes from standard OAuth operation +// security. The empty alternative truthfully documents that this catalog +// endpoint itself is public. Protected business operations remain unbound so a +// delegated credential hook can sign them before Restish's built-in auth runs. +const scopeCatalogSecurity: Record[] = [{ agentOAuth2: [...AGENT_OAUTH_RESOURCE_SCOPES] }, {}] +const scopeCatalogRoute = Object.assign(route, { + security: scopeCatalogSecurity, + 'x-mcp-ignore': true, +}) + +export const oauthResourceScopes = new OpenAPIHono().openapi(scopeCatalogRoute, (c) => + c.json( + { + scopes: AGENT_OAUTH_RESOURCE_SCOPES.map((value) => ({ + value, + description: AGENT_OAUTH_SCOPE_DESCRIPTIONS[value], + })), + }, + 200, + ), +) diff --git a/server/http/objects.ts b/server/http/objects.ts index 0ead27c2..eb327fc5 100644 --- a/server/http/objects.ts +++ b/server/http/objects.ts @@ -201,6 +201,8 @@ const createObjectRoute = authRoute( { operationId: 'createObject', summary: 'Create object', + description: + 'For a file, creates a draft and returns a self-contained direct-upload workflow. PUT each local file slice identified by upload.parts[].offset and upload.parts[].length to its presigned URL with the supplied headers, record each response ETag, then call upload.workflow.complete.operationId with every {partNumber, etag}. File bytes go directly to storage and never pass through ZPan. Use the advertised re-presign action for expired URLs and abort action to discard an unfinished draft.', tags: ['Objects'], method: 'post', path: '/', @@ -220,6 +222,8 @@ const presignPartsRoute = authRoute( { operationId: 'presignObjectUploadParts', summary: 'Re-presign upload parts', + description: + 'Returns replacement presigned PUT descriptors for the requested part numbers. Each descriptor includes the exact local file offset and length. Upload those slices, capture each response ETag, and finish through completeObjectUpload.', tags: ['Objects'], method: 'post', path: '/{id}/uploads/{uploadSessionId}/parts', @@ -239,6 +243,8 @@ const completionsRoute = authRoute( { operationId: 'completeObjectUpload', summary: 'Complete upload', + description: + 'Finalizes a direct upload after every advertised part has been PUT to storage. Send one {partNumber, etag} entry per part, using the ETag response header returned by storage.', tags: ['Objects'], method: 'post', path: '/{id}/uploads/{uploadSessionId}/completions', diff --git a/server/http/openapi.ts b/server/http/openapi.ts index 6497d5d7..fe85449a 100644 --- a/server/http/openapi.ts +++ b/server/http/openapi.ts @@ -1,5 +1,5 @@ import { createRoute, type RouteConfig, type z } from '@hono/zod-openapi' -import { AGENT_GRANTABLE_API_KEY_SCOPES } from '@shared/api-key-templates' +import { AGENT_OAUTH_SCOPES } from '@shared/agent-oauth' import { errorResponseSchema } from '@shared/schemas' import { authorize, type RouteAuthorizationDeclaration, type ScopedAuthorizationPolicy } from '../middleware/authz' @@ -24,7 +24,7 @@ export const jsonBody = (schema: T) => ({ // `jsonError`; this just documents the response shape in the OpenAPI document. export const errorResponse = (description: string) => jsonContent(errorResponseSchema, description) -const AGENT_GRANTABLE_SCOPE_SET = new Set(AGENT_GRANTABLE_API_KEY_SCOPES) +const AGENT_OAUTH_SCOPE_SET = new Set(AGENT_OAUTH_SCOPES) export function authRoute

& { path: P }>( auth: RouteAuthorizationDeclaration, @@ -34,7 +34,7 @@ export function authRoute

return createRoute({ ...config, middleware, - security: openApiSecurity(auth), + ...openApiSecurity(auth), 'x-zpan-auth': openApiAuthMetadata(auth), ...openApiCliMetadata(auth), } as T) as T & { getRoutingPath(): string } @@ -62,9 +62,10 @@ function hasValidAuthContract(operation: object): boolean { return auth.public ? auth.scopes.length === 0 : auth.scopes.length > 0 } -function openApiSecurity(auth: RouteAuthorizationDeclaration): Record[] { - if ('public' in auth) return [] - return openApiPolicySecurity(auth) +function openApiSecurity(auth: RouteAuthorizationDeclaration): { security?: Record[] } { + if ('public' in auth) return { security: [] } + if (isAgentCallablePolicy(auth)) return {} + return { security: openApiPolicySecurity(auth) } } function openApiCliMetadata(auth: RouteAuthorizationDeclaration): Record { @@ -81,13 +82,11 @@ function openApiAuthMetadata(auth: RouteAuthorizationDeclaration): Record[] { - return policy.scopes.every((scope) => AGENT_GRANTABLE_SCOPE_SET.has(scope)) - ? [{ agentOAuth2: [...policy.scopes] }, { agentApiKey: [...policy.scopes] }, { cookieAuth: [] }] - : [{ bearerAuth: [...policy.scopes] }, { cookieAuth: [] }] + return [{ bearerAuth: [...policy.scopes] }, { cookieAuth: [] }] } function isAgentCallablePolicy(policy: ScopedAuthorizationPolicy): boolean { - return policy.scopes.every((scope) => AGENT_GRANTABLE_SCOPE_SET.has(scope)) + return policy.scopes.every((scope) => AGENT_OAUTH_SCOPE_SET.has(scope)) } function openApiPolicyMetadata(policy: ScopedAuthorizationPolicy): Record { diff --git a/server/http/site/auth-providers.ts b/server/http/site/auth-providers.ts index 3b34b2e8..836738be 100644 --- a/server/http/site/auth-providers.ts +++ b/server/http/site/auth-providers.ts @@ -1,7 +1,7 @@ import { OpenAPIHono, z } from '@hono/zod-openapi' import { AuthorizationScope } from '@shared/authorization' import type { Env } from '../../middleware/platform' -import { deleteAuthProvider, listAuthProviders, upsertAuthProvider } from '../../usecases/site/auth-provider' +import { deleteAuthProvider, listAuthProviderSettings, upsertAuthProvider } from '../../usecases/site/auth-provider' import { authRoute, errorResponse, jsonBody, jsonContent } from '../openapi' // Full management shape. Public consumers receive the minimal provider projection @@ -28,6 +28,18 @@ const authProviderListSchema = z page: z.number().int(), pageSize: z.number().int(), callbackBaseUri: z.string(), + registeredApplications: z.array( + z.object({ + clientId: z.string(), + name: z.string(), + uri: z.string().nullable(), + redirectUris: z.array(z.string()), + grantTypes: z.array(z.string()), + scopes: z.array(z.string()), + disabled: z.boolean(), + createdAt: z.string(), + }), + ), }) .openapi('AuthProviderList') @@ -97,8 +109,20 @@ function resolveAuthBaseUri(c: { get(key: 'platform'): Env['Variables']['platfor export const authProviders = new OpenAPIHono() .openapi(listRoute, async (c) => { const authOrigin = resolveAuthBaseUri(c) - const { items } = await listAuthProviders(c.get('deps'), { authOrigin }) - return c.json({ items, total: items.length, page: 1, pageSize: items.length, callbackBaseUri: authOrigin }, 200) + const { items, registeredApplications } = await listAuthProviderSettings(c.get('deps'), c.get('platform').db, { + authOrigin, + }) + return c.json( + { + items, + total: items.length, + page: 1, + pageSize: items.length, + callbackBaseUri: authOrigin, + registeredApplications, + }, + 200, + ) }) .openapi(upsertRoute, async (c) => { const authOrigin = resolveAuthBaseUri(c) diff --git a/server/middleware/audit-actor.test.ts b/server/middleware/audit-actor.test.ts index d951100c..8ccd906f 100644 --- a/server/middleware/audit-actor.test.ts +++ b/server/middleware/audit-actor.test.ts @@ -8,7 +8,7 @@ describe('auditActor', () => { kind: 'agent-oauth', userId: 'user-1', grantId: 'grant-1', - clientId: 'zpan-agent', + clientId: 'dynamic-client', orgId: 'org-1', scopes: [], authMethod: 'bearer', diff --git a/server/middleware/auth.ts b/server/middleware/auth.ts index 030a5e09..a3ef2625 100644 --- a/server/middleware/auth.ts +++ b/server/middleware/auth.ts @@ -1,4 +1,7 @@ +import { oauthProviderResourceClient } from '@better-auth/oauth-provider/resource-client' import { AuthorizationScope, isAuthorizationScope, permissionScopes } from '@shared/authorization' +import { APIError } from 'better-auth' +import { createDpopReplayStore } from 'better-auth/oauth2' import { createMiddleware } from 'hono/factory' import { isDownloaderBootstrapRegistrationRequest, @@ -6,7 +9,7 @@ import { LEGACY_DOWNLOADER_CLIENT_ID, LEGACY_DOWNLOADER_REGISTER_SCOPE, } from '../domain/legacy-downloader-bootstrap' -import { ApiKeyRateLimitError, rateLimited, unauthorized } from '../usecases/ports' +import { ApiKeyRateLimitError, AppError, rateLimited, unauthorized } from '../usecases/ports' import { anonymousAuthzContext, type Env } from './platform' type SessionWithPlugins = { @@ -16,6 +19,61 @@ type SessionWithPlugins = { export const authMiddleware = createMiddleware(async (c, next) => { const authHeader = c.req.raw.headers.get('Authorization') + if (authHeader?.startsWith('DPoP ')) { + const auth = c.get('auth') + const authContext = await auth.$context + const audience = `${new URL(c.req.url).origin}/api` + let payload: Awaited< + ReturnType['getActions']>['verifyAccessTokenRequest']> + > + try { + payload = await oauthProviderResourceClient(auth) + .getActions() + .verifyAccessTokenRequest(c.req.raw, { + verifyOptions: { audience, issuer: authContext.baseURL }, + dpop: { replayStore: createDpopReplayStore(authContext.internalAdapter) }, + }) + } catch (error) { + if (error instanceof APIError) throw dpopUnauthorized(audience) + throw error + } + const userId = typeof payload.sub === 'string' ? payload.sub : null + const orgId = typeof payload.zpan_org_id === 'string' ? payload.zpan_org_id : null + const clientId = typeof payload.client_id === 'string' ? payload.client_id : null + const actor = payload.act && typeof payload.act === 'object' ? (payload.act as Record).sub : null + if (!userId || !orgId || !clientId || typeof actor !== 'string') throw unauthorized('Unauthorized') + if ( + typeof payload.jti !== 'string' || + (await c.get('deps').agentOAuth.isJwtAccessTokenRevoked(c.get('platform').db, payload.jti)) + ) { + throw dpopUnauthorized(audience) + } + if (await c.get('deps').userAdmin.isBanned(userId)) throw unauthorized('Unauthorized') + const scopes = typeof payload.scope === 'string' ? payload.scope.split(/\s+/).filter(isAuthorizationScope) : [] + const grantId = typeof payload.jti === 'string' ? payload.jti : actor + c.set('principal', { + kind: 'agent-oauth', + grantId, + clientId, + orgId, + userId, + scopes, + authMethod: 'dpop', + }) + c.set('authzContext', { + credential: 'agent_oauth', + userId, + workspace: { mode: 'bound', orgId }, + grantedScopes: new Set(scopes), + actor: { type: 'agent_oauth', ref: actor }, + state: { clientId }, + }) + c.set('userId', userId) + c.set('userRole', null) + c.set('orgId', orgId) + await next() + return + } if (authHeader?.startsWith('Bearer ')) { const token = authHeader.slice('Bearer '.length).trim() const platform = c.get('platform') @@ -98,32 +156,6 @@ export const authMiddleware = createMiddleware(async (c, next) => { await next() return } - const agentOAuth = await deps.agentOAuth.verifyAccessToken(platform.db, token) - if (agentOAuth) { - if (await deps.userAdmin.isBanned(agentOAuth.userId)) throw unauthorized('Unauthorized') - c.set('principal', { - kind: 'agent-oauth', - grantId: agentOAuth.grantId, - clientId: agentOAuth.clientId, - orgId: agentOAuth.orgId, - userId: agentOAuth.userId, - scopes: agentOAuth.scopes, - authMethod: 'bearer', - }) - c.set('authzContext', { - credential: 'agent_oauth', - userId: agentOAuth.userId, - workspace: { mode: 'bound', orgId: agentOAuth.orgId }, - grantedScopes: new Set(agentOAuth.scopes), - actor: { type: 'agent_oauth', ref: agentOAuth.grantId }, - state: { clientId: agentOAuth.clientId }, - }) - c.set('userId', agentOAuth.userId) - c.set('userRole', null) - c.set('orgId', agentOAuth.orgId) - await next() - return - } const bootstrap = await deps.downloaderBootstrapCredentials.resolve(platform, token, new Date()) if (bootstrap) { c.set('userId', bootstrap.userId) @@ -187,3 +219,11 @@ export const authMiddleware = createMiddleware(async (c, next) => { await next() }) + +function dpopUnauthorized(resource: string): AppError { + return new AppError(401, 'Unauthorized', { + headers: { + 'WWW-Authenticate': `DPoP resource_metadata="${new URL('/.well-known/oauth-protected-resource/api', resource).toString()}"`, + }, + }) +} diff --git a/server/middleware/authz.test.ts b/server/middleware/authz.test.ts deleted file mode 100644 index 0342e13e..00000000 --- a/server/middleware/authz.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { AuthorizationScope } from '@shared/authorization' -import { Hono } from 'hono' -import { describe, expect, it, vi } from 'vitest' -import { authorize, type RouteAuthorizationDeclaration } from './authz' -import { type AuthzContext, type Env, workspaceOrgId } from './platform' - -function probeApp(context: AuthzContext, declaration: RouteAuthorizationDeclaration) { - const recordGrantUse = vi.fn(async () => {}) - const app = new Hono() - app.use('/probe', async (c, next) => { - c.set('authzContext', context) - c.set('platform', { db: { kind: 'unit-db' } } as unknown as Env['Variables']['platform']) - c.set('deps', { - agentOAuth: { recordGrantUse }, - audit: { record: vi.fn() }, - org: { - getMemberRole: vi.fn(async () => 'owner'), - findPersonalOrg: vi.fn(async () => workspaceOrgId(context)), - }, - } as unknown as Env['Variables']['deps']) - await next() - }) - app.get('/probe', authorize(declaration), (c) => c.json({ ok: true })) - return { app, recordGrantUse } -} - -describe('authorize Agent OAuth grant-use tracking', () => { - const context: AuthzContext = { - credential: 'agent_oauth', - userId: 'user-1', - workspace: { mode: 'bound', orgId: 'org-1' }, - grantedScopes: new Set([AuthorizationScope.OBJECTS_READ]), - actor: { type: 'agent_oauth', ref: 'grant-1' }, - state: { clientId: 'zpan-agent' }, - } - - it('records actual Agent OAuth use for scoped protected routes', async () => { - const { app, recordGrantUse } = probeApp(context, { - scopes: [AuthorizationScope.OBJECTS_READ], - }) - - const res = await app.request('/probe') - - expect(res.status).toBe(200) - expect(recordGrantUse).toHaveBeenCalledTimes(1) - expect(recordGrantUse).toHaveBeenCalledWith( - { kind: 'unit-db' }, - expect.objectContaining({ - grantId: 'grant-1', - userId: 'user-1', - orgId: 'org-1', - now: expect.any(Date), - }), - ) - }) - - it('does not record public access as grant use', async () => { - const { app, recordGrantUse } = probeApp(context, { public: true }) - - const res = await app.request('/probe') - - expect(res.status).toBe(200) - expect(recordGrantUse).not.toHaveBeenCalled() - }) - - it('does not record non-Agent OAuth protected access as grant use', async () => { - const { app, recordGrantUse } = probeApp( - { - credential: 'session', - userId: 'user-1', - workspace: { mode: 'selected', orgId: 'org-1' }, - grantedScopes: null, - actor: { type: 'user', ref: 'user-1' }, - state: { firstParty: true }, - }, - { scopes: [AuthorizationScope.OBJECTS_READ] }, - ) - - const res = await app.request('/probe') - - expect(res.status).toBe(200) - expect(recordGrantUse).not.toHaveBeenCalled() - }) -}) diff --git a/server/middleware/authz.ts b/server/middleware/authz.ts index 1ae2fe76..eb4f6383 100644 --- a/server/middleware/authz.ts +++ b/server/middleware/authz.ts @@ -116,7 +116,6 @@ export function authorize(declaration: RouteAuthorizationDeclaration) { }) if (decision.allowed) { if (decision.effectiveOrgId) c.set('orgId', decision.effectiveOrgId) - await recordAgentOAuthGrantUse(c, declaration, decision.effectiveOrgId) await next() return } @@ -159,23 +158,6 @@ function isSafeMethod(method: string): boolean { return method === 'GET' || method === 'HEAD' || method === 'OPTIONS' } -async function recordAgentOAuthGrantUse( - c: Context, - declaration: RouteAuthorizationDeclaration, - effectiveOrgId: string | null, -) { - const context = c.get('authzContext') - if (context.credential !== 'agent_oauth') return - if (!declaredScopes(declaration).length) return - if (!context.userId || !effectiveOrgId || context.actor?.type !== 'agent_oauth') return - await c.get('deps').agentOAuth.recordGrantUse(c.get('platform').db, { - grantId: context.actor.ref, - userId: context.userId, - orgId: effectiveOrgId, - now: new Date(), - }) -} - function allow(effectiveOrgId: string | null): AuthzDecision { return { allowed: true, effectiveOrgId, reason: 'allowed' } } @@ -199,11 +181,6 @@ function shouldAudit(declaration: RouteAuthorizationDeclaration): boolean { return declaration.auditDenied !== false } -function declaredScopes(declaration: RouteAuthorizationDeclaration): AuthorizationScope[] { - if ('public' in declaration) return [] - return [...declaration.scopes] -} - async function recordDenialAudit(c: Context, reason: AuthzDenialReason) { const context = c.get('authzContext') if (!context.actor) return diff --git a/server/middleware/platform.ts b/server/middleware/platform.ts index 2c964a3f..7a9de706 100644 --- a/server/middleware/platform.ts +++ b/server/middleware/platform.ts @@ -59,7 +59,7 @@ export type AuthPrincipal = orgId: string userId: string scopes: readonly AuthorizationScope[] - authMethod: 'bearer' + authMethod: 'bearer' | 'dpop' } | { kind: 'downloader' diff --git a/server/openapi.test.ts b/server/openapi.test.ts index 572680bc..5fa4cb20 100644 --- a/server/openapi.test.ts +++ b/server/openapi.test.ts @@ -53,7 +53,83 @@ describe('global OpenAPI document', () => { expect(html).toContain('/api/openapi.json') }) - it('publishes Agent OAuth security schemes and Restish profiles', async () => { + it('advertises and serves the Arazzo workflow description', async () => { + const { app } = await createTestApp({ DOWNLOAD_TOKEN_SECRET: 'test-download-token-secret' }) + const [rootResponse, workflowResponse, documentResponse] = await Promise.all([ + app.request('https://zpan.example/api'), + app.request('https://zpan.example/api/workflows.arazzo.json'), + app.request('https://zpan.example/api/openapi.json'), + ]) + const root = (await rootResponse.json()) as { workflows?: string } + const workflows = (await workflowResponse.json()) as { + arazzo?: string + $self?: string + sourceDescriptions?: { name?: string; url?: string; type?: string }[] + workflows?: { + workflowId?: string + steps?: { operationId?: string }[] + outputs?: Record + }[] + } + const document = (await documentResponse.json()) as { + externalDocs?: { description?: string; url?: string } + paths?: Record> + } + + expect(rootResponse.status).toBe(200) + expect(rootResponse.headers.get('link')).toContain( + '; rel="service-desc"; type="application/openapi+json"', + ) + expect(rootResponse.headers.get('link')).toContain( + '; rel="describedby"; type="application/vnd.oai.workflows+json"', + ) + expect(root.workflows).toBe('/api/workflows.arazzo.json') + + expect(workflowResponse.status).toBe(200) + expect(workflowResponse.headers.get('content-type')).toBe('application/vnd.oai.workflows+json; version=1.1.0') + expect(workflows).toMatchObject({ + arazzo: '1.1.0', + $self: 'https://zpan.example/api/workflows.arazzo.json', + sourceDescriptions: [{ name: 'zpan', url: './openapi.json', type: 'openapi' }], + }) + expect(workflows.workflows?.map((workflow) => workflow.workflowId)).toEqual([ + 'prepareDirectFileUpload', + 'refreshDirectFileUploadParts', + 'completeDirectFileUpload', + 'abortDirectFileUpload', + ]) + const workflowOperationIds = workflows.workflows + ?.flatMap((workflow) => workflow.steps ?? []) + .map((step) => step.operationId) + expect(workflowOperationIds).toEqual([ + 'createObject', + 'presignObjectUploadParts', + 'completeObjectUpload', + 'abortObjectUpload', + ]) + const openApiOperationIds = new Set( + Object.values(document.paths ?? {}).flatMap((path) => + Object.values(path).flatMap((operation) => operation.operationId ?? []), + ), + ) + expect(workflowOperationIds?.every((operationId) => operationId && openApiOperationIds.has(operationId))).toBe(true) + expect(workflows.workflows?.[0]?.outputs).toMatchObject({ + objectId: '$steps.createUploadDraft.outputs.objectId', + sessionId: '$steps.createUploadDraft.outputs.sessionId', + upload: '$steps.createUploadDraft.outputs.upload', + }) + expect(document.externalDocs).toEqual({ + description: 'Machine-readable API workflows (Arazzo 1.1)', + url: '/api/workflows.arazzo.json', + }) + + const headResponse = await app.request('https://zpan.example/api/workflows.arazzo.json', { method: 'HEAD' }) + expect(headResponse.status).toBe(200) + expect(headResponse.headers.get('content-type')).toBe('application/vnd.oai.workflows+json; version=1.1.0') + expect(await headResponse.text()).toBe('') + }) + + it('publishes the external OAuth scope catalog without Restish profiles', async () => { const { app } = await createTestApp({ DOWNLOAD_TOKEN_SECRET: 'test-download-token-secret' }) const res = await app.request('/api/openapi.json') const doc = (await res.json()) as { @@ -63,20 +139,7 @@ describe('global OpenAPI document', () => { { type?: string; scheme?: string; flows?: { authorizationCode?: { scopes?: Record } } } > } - 'x-cli-config'?: { - profiles?: Record< - string, - { - credentials?: Record< - string, - { - auth?: { type?: string; params?: Record } - satisfies?: string[] - } - > - } - > - } + 'x-cli-config'?: unknown } expect(doc.components?.securitySchemes?.agentOAuth2).toMatchObject({ @@ -94,86 +157,48 @@ describe('global OpenAPI document', () => { }, }, }) - expect(doc.components?.securitySchemes?.agentApiKey).toMatchObject({ type: 'http', scheme: 'bearer' }) - const profiles = doc['x-cli-config']?.profiles - expect(Object.keys(profiles ?? {})).toEqual(['default', 'reader', 'file-manager', 'publisher', 'ci']) - expect(profiles?.reader?.credentials?.agentOAuth2).toMatchObject({ - auth: { - type: 'oauth-authorization-code', - params: { - authorize_url: '/api/auth/oauth2/authorize', - token_url: '/api/auth/oauth2/token', - client_id: 'zpan-agent', - redirect_path: '/callback', - scopes: 'openid offline_access objects:read shares:read quota:read storage-usage:read', + expect(doc.components?.securitySchemes?.agentApiKey).toBeUndefined() + expect(doc['x-cli-config']).toBeUndefined() + }) + + it('publishes a public resource-scope catalog for external controller discovery', async () => { + const { app } = await createTestApp({ DOWNLOAD_TOKEN_SECRET: 'test-download-token-secret' }) + const [catalogResponse, documentResponse] = await Promise.all([ + app.request('/api/oauth-resource-scopes'), + app.request('/api/openapi.json'), + ]) + const catalog = (await catalogResponse.json()) as { + scopes: { value: string; description: string }[] + } + const document = (await documentResponse.json()) as { + paths: Record[]; 'x-zpan-auth'?: unknown } }> + } + + expect(catalogResponse.status).toBe(200) + expect(catalog.scopes).toEqual( + expect.arrayContaining([ + { + value: AuthorizationScope.OBJECTS_CREATE, + description: 'Create folders and upload objects', }, - }, - satisfies: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.SHARES_READ, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], - }) - expect(profiles?.default?.credentials?.agentOAuth2).toEqual(profiles?.reader?.credentials?.agentOAuth2) - expect(profiles?.['file-manager']?.credentials?.agentOAuth2).toMatchObject({ - auth: { - type: 'oauth-authorization-code', - params: { - authorize_url: '/api/auth/oauth2/authorize', - token_url: '/api/auth/oauth2/token', - client_id: 'zpan-agent', - redirect_path: '/callback', - scopes: - 'openid offline_access objects:read objects:create objects:update objects:delete shares:read quota:read storage-usage:read', + { + value: AuthorizationScope.OBJECTS_UPDATE, + description: 'Rename, move, and copy objects', }, - }, - satisfies: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.OBJECTS_CREATE, - AuthorizationScope.OBJECTS_UPDATE, - AuthorizationScope.OBJECTS_DELETE, - AuthorizationScope.SHARES_READ, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], - }) - expect(profiles?.publisher?.credentials?.agentOAuth2).toMatchObject({ - auth: { - type: 'oauth-authorization-code', - params: { - authorize_url: '/api/auth/oauth2/authorize', - token_url: '/api/auth/oauth2/token', - client_id: 'zpan-agent', - redirect_path: '/callback', - scopes: - 'openid offline_access objects:read shares:read shares:create shares:delete quota:read storage-usage:read', + ]), + ) + expect(document.paths['/api/oauth-resource-scopes']?.get).toMatchObject({ + security: [ + { + agentOAuth2: expect.arrayContaining([ + AuthorizationScope.OBJECTS_READ, + AuthorizationScope.OBJECTS_CREATE, + AuthorizationScope.OBJECTS_UPDATE, + ]), }, - }, - satisfies: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.SHARES_READ, - AuthorizationScope.SHARES_CREATE, - AuthorizationScope.SHARES_DELETE, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], - }) - expect(profiles?.default?.credentials?.agentOAuth2?.auth?.params).toMatchObject({ - client_id: 'zpan-agent', - redirect_path: '/callback', - }) - expect(profiles?.ci?.credentials?.agentApiKey).toMatchObject({ - auth: { type: 'bearer', params: { token: 'env:ZPAN_AGENT_API_KEY' } }, - satisfies: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.OBJECTS_CREATE, - AuthorizationScope.OBJECTS_UPDATE, - AuthorizationScope.OBJECTS_DELETE, - AuthorizationScope.SHARES_READ, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, + {}, ], + 'x-zpan-auth': { public: true, scopes: [] }, }) }) @@ -330,7 +355,7 @@ describe('global OpenAPI document', () => { } }) - it('emits Agent OAuth and API-key security only for Agent-grantable protected scopes', () => { + it('leaves externally authorized operations unbound so delegated hooks can authenticate them', () => { const route = authRoute( { scopes: [AuthorizationScope.OBJECTS_CREATE], @@ -344,11 +369,7 @@ describe('global OpenAPI document', () => { }, ) as { security?: unknown } - expect(route.security).toEqual([ - { agentOAuth2: [AuthorizationScope.OBJECTS_CREATE] }, - { agentApiKey: [AuthorizationScope.OBJECTS_CREATE] }, - { cookieAuth: [] }, - ]) + expect(route.security).toBeUndefined() }) it('hides non-agent scoped policies from MCP without hiding them from Restish', () => { @@ -422,8 +443,6 @@ describe('global OpenAPI document', () => { } const ignoredOperations = [ - doc.paths['/api/workspaces/{orgId}/agent-api-keys']?.get, - doc.paths['/api/workspaces/{orgId}/agent-api-keys']?.post, doc.paths['/api/agent-oauth-grants']?.get, doc.paths['/api/agent-oauth-grants/{grantId}']?.delete, doc.paths['/api/site/storages']?.post, @@ -474,12 +493,12 @@ describe('global OpenAPI document', () => { expect(doc.paths['/api/objects']?.post).toMatchObject({ operationId: 'createObject', - security: [ - { agentOAuth2: [AuthorizationScope.OBJECTS_CREATE] }, - { agentApiKey: [AuthorizationScope.OBJECTS_CREATE] }, - { cookieAuth: [] }, - ], + 'x-zpan-auth': { + public: false, + scopes: [AuthorizationScope.OBJECTS_CREATE], + }, }) + expect(doc.paths['/api/objects']?.post?.security).toBeUndefined() expect(doc.paths['/api/objects']?.post?.responses?.['201']).toBeDefined() expect(doc.paths['/api/objects']?.post?.requestBody).toBeDefined() expect(doc.paths['/api/objects']?.post?.requestBody?.content?.['application/json']?.schema).toMatchObject({ @@ -512,6 +531,7 @@ describe('global OpenAPI document', () => { 'requiredHeaders', 'urls', 'parts', + 'workflow', ], }, }, diff --git a/server/test/setup.ts b/server/test/setup.ts index 12eec060..57b37e07 100644 --- a/server/test/setup.ts +++ b/server/test/setup.ts @@ -44,6 +44,7 @@ const AUTH_SCHEMA_SQL = ` CREATE INDEX IF NOT EXISTS session_userId_idx ON session(user_id); CREATE TABLE IF NOT EXISTS account ( id TEXT PRIMARY KEY, + issuer TEXT NOT NULL DEFAULT '', account_id TEXT NOT NULL, provider_id TEXT NOT NULL, user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, @@ -58,6 +59,7 @@ const AUTH_SCHEMA_SQL = ` updated_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) ); CREATE INDEX IF NOT EXISTS account_userId_idx ON account(user_id); + CREATE UNIQUE INDEX IF NOT EXISTS account_issuer_providerAccountId_unique ON account(issuer, account_id); CREATE TABLE IF NOT EXISTS verification ( id TEXT PRIMARY KEY, identifier TEXT NOT NULL, @@ -114,6 +116,15 @@ const AUTH_SCHEMA_SQL = ` CREATE INDEX IF NOT EXISTS deviceCode_device_code_idx ON deviceCode(device_code); CREATE INDEX IF NOT EXISTS deviceCode_user_code_idx ON deviceCode(user_code); CREATE INDEX IF NOT EXISTS deviceCode_status_idx ON deviceCode(status); + CREATE TABLE IF NOT EXISTS jwks ( + id TEXT PRIMARY KEY, + public_key TEXT NOT NULL, + private_key TEXT NOT NULL, + alg TEXT, + crv TEXT, + created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)), + expires_at INTEGER + ); CREATE TABLE IF NOT EXISTS oauthClient ( id TEXT PRIMARY KEY, client_id TEXT NOT NULL UNIQUE, @@ -137,17 +148,49 @@ const AUTH_SCHEMA_SQL = ` software_statement TEXT, redirect_uris TEXT NOT NULL, post_logout_redirect_uris TEXT, + backchannel_logout_uri TEXT, + backchannel_logout_session_required INTEGER, token_endpoint_auth_method TEXT, + jwks TEXT, + jwks_uri TEXT, grant_types TEXT, response_types TEXT, public INTEGER, type TEXT, require_pkce INTEGER, + dpop_bound_access_tokens INTEGER DEFAULT 0, reference_id TEXT, metadata TEXT ); CREATE INDEX IF NOT EXISTS oauthClient_client_id_idx ON oauthClient(client_id); CREATE INDEX IF NOT EXISTS oauthClient_user_id_idx ON oauthClient(user_id); + CREATE TABLE IF NOT EXISTS oauthResource ( + id TEXT PRIMARY KEY, + identifier TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + access_token_ttl INTEGER, + refresh_token_ttl INTEGER, + signing_algorithm TEXT, + signing_key_id TEXT, + allowed_scopes TEXT, + custom_claims TEXT, + dpop_bound_access_tokens_required INTEGER DEFAULT 0, + disabled INTEGER DEFAULT 0, + policy_version INTEGER DEFAULT 1, + metadata TEXT, + created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)), + updated_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) + ); + CREATE INDEX IF NOT EXISTS oauthResource_identifier_idx ON oauthResource(identifier); + CREATE TABLE IF NOT EXISTS oauthClientResource ( + id TEXT PRIMARY KEY, + client_id TEXT NOT NULL REFERENCES oauthClient(client_id) ON DELETE CASCADE, + resource_id TEXT NOT NULL REFERENCES oauthResource(identifier) ON DELETE CASCADE, + metadata TEXT, + created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) + ); + CREATE INDEX IF NOT EXISTS oauthClientResource_client_id_idx ON oauthClientResource(client_id); + CREATE INDEX IF NOT EXISTS oauthClientResource_resource_id_idx ON oauthClientResource(resource_id); CREATE TABLE IF NOT EXISTS oauthRefreshToken ( id TEXT PRIMARY KEY, token TEXT NOT NULL UNIQUE, @@ -155,10 +198,17 @@ const AUTH_SCHEMA_SQL = ` session_id TEXT REFERENCES session(id) ON DELETE SET NULL, user_id TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE, reference_id TEXT, + authorization_code_id TEXT, + resources TEXT, + requested_user_info_claims TEXT, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)), revoked INTEGER, + rotated_at INTEGER, + rotation_replay_response TEXT, + rotation_replay_expires_at INTEGER, auth_time INTEGER, + confirmation TEXT, scopes TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS oauthRefreshToken_client_id_idx ON oauthRefreshToken(client_id); @@ -172,9 +222,14 @@ const AUTH_SCHEMA_SQL = ` session_id TEXT REFERENCES session(id) ON DELETE SET NULL, user_id TEXT REFERENCES user(id) ON DELETE CASCADE, reference_id TEXT, + authorization_code_id TEXT, + resources TEXT, + requested_user_info_claims TEXT, refresh_id TEXT REFERENCES oauthRefreshToken(id) ON DELETE CASCADE, expires_at INTEGER NOT NULL, created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)), + revoked INTEGER, + confirmation TEXT, scopes TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS oauthAccessToken_client_id_idx ON oauthAccessToken(client_id); @@ -187,6 +242,8 @@ const AUTH_SCHEMA_SQL = ` client_id TEXT NOT NULL REFERENCES oauthClient(client_id) ON DELETE CASCADE, user_id TEXT REFERENCES user(id) ON DELETE CASCADE, reference_id TEXT, + resources TEXT, + requested_user_info_claims TEXT, scopes TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)), last_used_at INTEGER, @@ -194,6 +251,17 @@ const AUTH_SCHEMA_SQL = ` ); CREATE INDEX IF NOT EXISTS oauthConsent_client_id_idx ON oauthConsent(client_id); CREATE INDEX IF NOT EXISTS oauthConsent_user_id_idx ON oauthConsent(user_id); + CREATE TABLE IF NOT EXISTS oauthClientAssertion ( + id TEXT PRIMARY KEY, + expires_at INTEGER NOT NULL + ); + CREATE TABLE IF NOT EXISTS oauthJwtRevocation ( + id TEXT PRIMARY KEY, + client_id TEXT NOT NULL, + expires_at INTEGER NOT NULL, + created_at INTEGER NOT NULL DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) + ); + CREATE INDEX IF NOT EXISTS oauthJwtRevocation_expires_at_idx ON oauthJwtRevocation(expires_at); CREATE TABLE IF NOT EXISTS downloader_bootstrap_credentials ( id TEXT PRIMARY KEY, token_hash TEXT NOT NULL UNIQUE, diff --git a/server/usecases/agent-api-keys.ts b/server/usecases/agent-api-keys.ts deleted file mode 100644 index 11d04810..00000000 --- a/server/usecases/agent-api-keys.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { AGENT_GRANTABLE_API_KEY_SCOPES } from '@shared/api-key-templates' -import type { - AgentApiKeyCreated, - AgentApiKeyCreateInput, - AgentApiKeyList, - AgentApiKeyRotateInput, - AgentGrantableScope, -} from '@shared/schemas' -import type { Database } from '../platform/interface' -import type { Deps } from './deps' -import { badRequest, conflict, forbidden, notFound } from './ports' - -const MAX_AGENT_API_KEY_AGE_MS = 365 * 24 * 60 * 60 * 1000 -const AGENT_GRANTABLE_SCOPE_SET = new Set(AGENT_GRANTABLE_API_KEY_SCOPES) - -export async function listAgentApiKeys( - deps: Pick, - db: Database, - input: { userId: string; orgId: string; page: number; pageSize: number; now?: Date }, -): Promise { - await requireWorkspaceManager(deps, input.userId, input.orgId) - const items = await deps.apiKeys.listAgentApiKeys(db, input.userId, input.orgId, input.now ?? new Date()) - const offset = (input.page - 1) * input.pageSize - return { - items: items.slice(offset, offset + input.pageSize), - total: items.length, - page: input.page, - pageSize: input.pageSize, - } -} - -export async function createAgentApiKey( - deps: Pick, - db: Database, - input: { userId: string; orgId: string; body: AgentApiKeyCreateInput; now?: Date }, -): Promise { - const now = input.now ?? new Date() - await requireWorkspaceManager(deps, input.userId, input.orgId) - return deps.apiKeys.issueAgentApiKey(db, { - name: input.body.name, - orgId: input.orgId, - userId: input.userId, - scopes: normalizeScopes(input.body.scopes), - expiresAt: parseExpiresAt(input.body.expiresAt, now), - }) -} - -export async function rotateAgentApiKey( - deps: Pick, - db: Database, - input: { userId: string; orgId: string; keyId: string; body: AgentApiKeyRotateInput; now?: Date }, -): Promise { - const now = input.now ?? new Date() - await requireWorkspaceManager(deps, input.userId, input.orgId) - const existing = await deps.apiKeys.getAgentApiKey(db, input.userId, input.orgId, input.keyId, now) - if (!existing) throw notFound('Agent API key not found') - if (existing.status !== 'active') { - throw conflict('Only active Agent API keys can be rotated', 'AGENT_API_KEY_NOT_ACTIVE') - } - return deps.apiKeys.issueAgentApiKey(db, { - name: input.body.name?.trim() || `${existing.name} rotation`, - orgId: input.orgId, - userId: input.userId, - scopes: normalizeScopes(input.body.scopes ?? existing.scopes), - expiresAt: parseExpiresAt(input.body.expiresAt ?? existing.expiresAt, now), - revokeKeyId: existing.id, - }) -} - -export async function revokeAgentApiKey( - deps: Pick, - db: Database, - input: { userId: string; orgId: string; keyId: string; now?: Date }, -): Promise { - await requireWorkspaceManager(deps, input.userId, input.orgId) - const existing = await deps.apiKeys.getAgentApiKey( - db, - input.userId, - input.orgId, - input.keyId, - input.now ?? new Date(), - ) - if (!existing) throw notFound('Agent API key not found') - await deps.apiKeys.revokeAgentApiKey(db, input.keyId) -} - -async function requireWorkspaceManager(deps: Pick, userId: string, orgId: string): Promise { - if (!(await deps.org.canManageAgentAccess(userId, orgId))) { - throw forbidden('Owner or admin access to the workspace is required') - } -} - -function parseExpiresAt(value: string, now: Date): Date { - const expiresAt = new Date(value) - if (Number.isNaN(expiresAt.getTime())) throw badRequest('Invalid expiry') - if (expiresAt <= now) throw badRequest('Agent API key expiry must be in the future') - if (expiresAt.getTime() - now.getTime() > MAX_AGENT_API_KEY_AGE_MS) { - throw badRequest('Agent API key expiry cannot exceed one year') - } - return expiresAt -} - -function normalizeScopes(scopes: readonly string[]): AgentGrantableScope[] { - const unique = new Set(scopes) - if (unique.size !== scopes.length) throw badRequest('Duplicate Agent API key scopes are not allowed') - const normalized = [...unique] as AgentGrantableScope[] - if (normalized.some((scope) => !AGENT_GRANTABLE_SCOPE_SET.has(scope))) { - throw badRequest('Agent API key scope is not grantable') - } - return normalized -} diff --git a/server/usecases/agent-oauth-consent.test.ts b/server/usecases/agent-oauth-consent.test.ts index bc1211c2..fc6806a1 100644 --- a/server/usecases/agent-oauth-consent.test.ts +++ b/server/usecases/agent-oauth-consent.test.ts @@ -1,8 +1,11 @@ -import { AGENT_OAUTH_CLIENT_ID, AGENT_OAUTH_CLIENT_NAME } from '@shared/agent-oauth' import { AuthorizationScope } from '@shared/authorization' import { describe, expect, it, vi } from 'vitest' import { getAgentOAuthConsentContext } from './agent-oauth-consent' -import type { OrgRepo } from './ports' +import type { AgentOAuthGateway, OrgRepo } from './ports' + +const db = {} as never +const CLIENT_ID = 'dynamic-client' +const CLIENT_NAME = 'FlareAuth' function org(overrides: Partial = {}): OrgRepo { return { @@ -17,9 +20,38 @@ function org(overrides: Partial = {}): OrgRepo { } } +function deps( + orgRepo: OrgRepo, + client: { + clientId?: string + clientName?: string + redirectUris?: string[] + scopes?: string[] + } = {}, +) { + return { + org: orgRepo, + agentOAuth: { + findClient: vi.fn(async () => ({ + clientId: client.clientId ?? CLIENT_ID, + clientName: client.clientName ?? CLIENT_NAME, + disabled: false, + redirectUris: client.redirectUris ?? ['http://127.0.0.1:8484/callback'], + responseTypes: ['code'], + scopes: client.scopes ?? [ + 'openid', + 'offline_access', + AuthorizationScope.OBJECTS_READ, + AuthorizationScope.QUOTA_READ, + ], + })), + } as unknown as AgentOAuthGateway, + } +} + function oauthQuery(overrides: Record = {}) { return new URLSearchParams({ - client_id: AGENT_OAUTH_CLIENT_ID, + client_id: CLIENT_ID, redirect_uri: 'http://127.0.0.1:8484/callback', response_type: 'code', scope: `openid offline_access ${AuthorizationScope.OBJECTS_READ} ${AuthorizationScope.QUOTA_READ}`, @@ -28,20 +60,45 @@ function oauthQuery(overrides: Record = {}) { } describe('Agent OAuth consent usecase', () => { - it('builds server-owned consent context for the active workspace', async () => { + it('resolves a dynamically registered client instead of hard-coding its identity', async () => { + const dynamicQuery = oauthQuery({ + client_id: 'dynamic-client', + redirect_uri: 'https://broker.example.com/oauth/callback', + }) await expect( getAgentOAuthConsentContext( - { org: org() }, + deps(org(), { + clientId: 'dynamic-client', + clientName: 'Broker', + redirectUris: ['https://broker.example.com/oauth/callback'], + }), { + db, userId: 'user-1', orgId: 'org-1', requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery(), + oauthQuery: dynamicQuery, }, ), + ).resolves.toMatchObject({ + clientId: 'dynamic-client', + clientName: 'Broker', + redirectUri: 'https://broker.example.com/oauth/callback', + }) + }) + + it('builds server-owned consent context for the active workspace', async () => { + await expect( + getAgentOAuthConsentContext(deps(org()), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery(), + }), ).resolves.toEqual({ - clientId: AGENT_OAUTH_CLIENT_ID, - clientName: AGENT_OAUTH_CLIENT_NAME, + clientId: CLIENT_ID, + clientName: CLIENT_NAME, instanceOrigin: 'https://zpan.example.test', workspace: { id: 'org-1', name: 'Personal' }, scopes: [AuthorizationScope.OBJECTS_READ, AuthorizationScope.QUOTA_READ], @@ -56,15 +113,13 @@ describe('Agent OAuth consent usecase', () => { it('keeps the active workspace id when the workspace name is unavailable', async () => { await expect( - getAgentOAuthConsentContext( - { org: org({ getOrgNames: vi.fn(async () => new Map()) }) }, - { - userId: 'user-1', - orgId: 'org-1', - requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery(), - }, - ), + getAgentOAuthConsentContext(deps(org({ getOrgNames: vi.fn(async () => new Map()) })), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery(), + }), ).resolves.toMatchObject({ workspace: { id: 'org-1', name: null }, }) @@ -72,55 +127,47 @@ describe('Agent OAuth consent usecase', () => { it('rejects requests that are not the managed authorization-code client flow', async () => { await expect( - getAgentOAuthConsentContext( - { org: org() }, - { - userId: 'user-1', - orgId: 'org-1', - requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery({ response_type: 'token' }), - }, - ), + getAgentOAuthConsentContext(deps(org()), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery({ response_type: 'token' }), + }), ).rejects.toMatchObject({ httpStatus: 400 }) }) it('rejects untrusted redirect URIs and non-grantable scopes', async () => { await expect( - getAgentOAuthConsentContext( - { org: org() }, - { - userId: 'user-1', - orgId: 'org-1', - requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery({ redirect_uri: 'https://evil.example/callback' }), - }, - ), + getAgentOAuthConsentContext(deps(org()), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery({ redirect_uri: 'https://evil.example/callback' }), + }), ).rejects.toMatchObject({ httpStatus: 400 }) await expect( - getAgentOAuthConsentContext( - { org: org() }, - { - userId: 'user-1', - orgId: 'org-1', - requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery({ scope: 'objects:purge' }), - }, - ), + getAgentOAuthConsentContext(deps(org()), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery({ scope: 'objects:purge' }), + }), ).rejects.toMatchObject({ httpStatus: 400 }) }) it('rejects missing or inaccessible workspaces', async () => { await expect( - getAgentOAuthConsentContext( - { org: org({ canReadOrg: vi.fn(async () => false) }) }, - { - userId: 'user-1', - orgId: 'org-1', - requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', - oauthQuery: oauthQuery(), - }, - ), + getAgentOAuthConsentContext(deps(org({ canReadOrg: vi.fn(async () => false) })), { + db, + userId: 'user-1', + orgId: 'org-1', + requestUrl: 'https://zpan.example.test/api/agent-oauth-consent', + oauthQuery: oauthQuery(), + }), ).rejects.toMatchObject({ httpStatus: 403 }) }) }) diff --git a/server/usecases/agent-oauth-consent.ts b/server/usecases/agent-oauth-consent.ts index 20960e54..9125fc8d 100644 --- a/server/usecases/agent-oauth-consent.ts +++ b/server/usecases/agent-oauth-consent.ts @@ -1,19 +1,17 @@ import { AGENT_OAUTH_ACCESS_TOKEN_SECONDS, - AGENT_OAUTH_CLIENT_ID, - AGENT_OAUTH_CLIENT_NAME, AGENT_OAUTH_REFRESH_TOKEN_SECONDS, AGENT_OAUTH_STANDARD_SCOPES, - RESTISH_OAUTH_REDIRECT_URIS, } from '@shared/agent-oauth' import { isAuthorizationScope } from '@shared/authorization' -import { type AgentGrantableScope, type AgentOAuthConsentContext, agentGrantableScopeSchema } from '@shared/schemas' +import { type AgentOAuthConsentContext, type OAuthResourceScope, oauthResourceScopeSchema } from '@shared/schemas' +import type { Database } from '../platform/interface' import type { Deps } from './deps' import { badRequest, forbidden } from './ports' export async function getAgentOAuthConsentContext( - deps: Pick, - input: { userId: string; orgId: string | null; requestUrl: string; oauthQuery: string }, + deps: Pick, + input: { db: Database; userId: string; orgId: string | null; requestUrl: string; oauthQuery: string }, ): Promise { const params = new URLSearchParams(input.oauthQuery) const clientId = params.get('client_id') @@ -21,10 +19,16 @@ export async function getAgentOAuthConsentContext( const responseType = params.get('response_type') const scopeValue = params.get('scope') ?? '' - if (clientId !== AGENT_OAUTH_CLIENT_ID || responseType !== 'code' || !redirectUri) { + if (!clientId || responseType !== 'code' || !redirectUri) { throw badRequest('Invalid Agent OAuth request') } - if (!RESTISH_OAUTH_REDIRECT_URIS.includes(redirectUri as (typeof RESTISH_OAUTH_REDIRECT_URIS)[number])) { + const client = await deps.agentOAuth.findClient(input.db, clientId) + if ( + !client || + client.disabled || + !client.responseTypes.includes('code') || + !client.redirectUris.includes(redirectUri) + ) { throw badRequest('Invalid Agent OAuth redirect URI') } @@ -32,8 +36,12 @@ export async function getAgentOAuthConsentContext( const standardScopes = requestedScopes.filter((scope) => (AGENT_OAUTH_STANDARD_SCOPES as readonly string[]).includes(scope), ) - const scopes = requestedScopes.filter(isAgentGrantableScope) - if (scopes.length === 0 || requestedScopes.length !== standardScopes.length + scopes.length) { + const scopes = requestedScopes.filter(isOAuthResourceScope) + if ( + scopes.length === 0 || + requestedScopes.length !== standardScopes.length + scopes.length || + requestedScopes.some((scope) => !client.scopes.includes(scope)) + ) { throw badRequest('Invalid Agent OAuth scope') } @@ -45,7 +53,7 @@ export async function getAgentOAuthConsentContext( return { clientId, - clientName: AGENT_OAUTH_CLIENT_NAME, + clientName: client.clientName, instanceOrigin: new URL(input.requestUrl).origin, workspace: { id: orgId, name: names.get(orgId) ?? null }, scopes, @@ -58,6 +66,6 @@ export async function getAgentOAuthConsentContext( } } -function isAgentGrantableScope(scope: string): scope is AgentGrantableScope { - return isAuthorizationScope(scope) && agentGrantableScopeSchema.safeParse(scope).success +function isOAuthResourceScope(scope: string): scope is OAuthResourceScope { + return isAuthorizationScope(scope) && oauthResourceScopeSchema.safeParse(scope).success } diff --git a/server/usecases/agent-oauth-grants.test.ts b/server/usecases/agent-oauth-grants.test.ts index d192338a..5ed5fe37 100644 --- a/server/usecases/agent-oauth-grants.test.ts +++ b/server/usecases/agent-oauth-grants.test.ts @@ -6,11 +6,11 @@ const db = {} as never function gateway(overrides: Partial = {}): AgentOAuthGateway { return { - ensureSystemClient: vi.fn(), - assertLiveGrant: vi.fn(), - verifyAccessToken: vi.fn(), + findClient: vi.fn(), + listRegisteredApplications: vi.fn(), + revokeJwtAccessToken: vi.fn(), + isJwtAccessTokenRevoked: vi.fn(), listGrants: vi.fn(async () => []), - recordGrantUse: vi.fn(), revokeGrant: vi.fn(async () => true), ...overrides, } @@ -35,7 +35,8 @@ describe('Agent OAuth grant usecases', () => { listGrants: vi.fn(async () => [ { id: 'grant-1', - clientId: 'zpan-agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', userId: 'user-1', orgId: 'org-1', scopes: [], @@ -49,8 +50,8 @@ describe('Agent OAuth grant usecases', () => { items: [ { id: 'grant-1', - clientId: 'zpan-agent', - clientName: 'ZPan Agent', + clientId: 'dynamic-client', + clientName: 'FlareAuth', userId: 'user-1', orgId: 'org-1', workspaceName: 'Personal', diff --git a/server/usecases/agent-oauth-grants.ts b/server/usecases/agent-oauth-grants.ts index 0489966e..b18a0ef6 100644 --- a/server/usecases/agent-oauth-grants.ts +++ b/server/usecases/agent-oauth-grants.ts @@ -1,8 +1,8 @@ import { - type AgentGrantableScope, type AgentOAuthGrant as AgentOAuthGrantDTO, - agentGrantableScopeSchema, agentOAuthGrantDTO, + type OAuthResourceScope, + oauthResourceScopeSchema, } from '@shared/schemas' import type { Database } from '../platform/interface' import type { Deps } from './deps' @@ -19,15 +19,15 @@ export async function listAgentOAuthGrants( items: items.map((item) => agentOAuthGrantDTO({ ...item, - scopes: item.scopes.filter(isAgentGrantableScope), + scopes: item.scopes.filter(isOAuthResourceScope), workspaceName: orgNames.get(item.orgId) ?? null, }), ), } } -function isAgentGrantableScope(scope: string): scope is AgentGrantableScope { - return agentGrantableScopeSchema.safeParse(scope).success +function isOAuthResourceScope(scope: string): scope is OAuthResourceScope { + return oauthResourceScopeSchema.safeParse(scope).success } export async function revokeAgentOAuthGrant( diff --git a/server/usecases/object.test.ts b/server/usecases/object.test.ts index 69a06618..6ebfa91a 100644 --- a/server/usecases/object.test.ts +++ b/server/usecases/object.test.ts @@ -337,6 +337,8 @@ describe('object usecase', () => { url: 'https://up', expiresAt: out.upload.presignedExpiresAt, headers: { 'content-type': 'image/jpeg' }, + offset: 0, + length: 2048, }, ]) expect(out.matter.status).toBe('draft') @@ -467,8 +469,22 @@ describe('object usecase', () => { expect(out.upload.requiredHeaders).toEqual({}) expect(out.upload.urls).toEqual(['https://part-1', 'https://part-2']) expect(out.upload.parts).toEqual([ - { partNumber: 1, url: 'https://part-1', expiresAt: out.upload.presignedExpiresAt, headers: {} }, - { partNumber: 2, url: 'https://part-2', expiresAt: out.upload.presignedExpiresAt, headers: {} }, + { + partNumber: 1, + url: 'https://part-1', + expiresAt: out.upload.presignedExpiresAt, + headers: {}, + offset: 0, + length: multipartPartSize, + }, + { + partNumber: 2, + url: 'https://part-2', + expiresAt: out.upload.presignedExpiresAt, + headers: {}, + offset: multipartPartSize, + length: 1, + }, ]) expect(createMultipartUpload).toHaveBeenCalled() expect(presignUploadPart).toHaveBeenCalledWith( @@ -1001,8 +1017,22 @@ describe('object usecase', () => { expect(out.uploadId).toBe('mp-1') expect(out.partCount).toBe(3) expect(out.parts).toEqual([ - { partNumber: 3, url: 'https://part-3', expiresAt: out.presignedExpiresAt, headers: {} }, - { partNumber: 1, url: 'https://part-1', expiresAt: out.presignedExpiresAt, headers: {} }, + { + partNumber: 3, + url: 'https://part-3', + expiresAt: out.presignedExpiresAt, + headers: {}, + offset: 200, + length: 50, + }, + { + partNumber: 1, + url: 'https://part-1', + expiresAt: out.presignedExpiresAt, + headers: {}, + offset: 0, + length: 100, + }, ]) expect(presignUploadPart).toHaveBeenCalledWith(storage, 'key/d1', 'mp-1', 3, UPLOAD_PRESIGNED_URL_TTL_SECONDS) }) diff --git a/server/usecases/object.ts b/server/usecases/object.ts index 74656cd9..80867a27 100644 --- a/server/usecases/object.ts +++ b/server/usecases/object.ts @@ -265,6 +265,8 @@ async function prepareUpload( url: await deps.s3.presignUpload(storage, storageKey, contentType, UPLOAD_PRESIGNED_URL_TTL_SECONDS), expiresAt: presignedExpiresAt, headers, + offset: 0, + length: size, }, ] } else { @@ -285,6 +287,8 @@ async function prepareUpload( url: await deps.s3.presignUploadPart(storage, storageKey, mpId, i + 1, UPLOAD_PRESIGNED_URL_TTL_SECONDS), expiresAt: presignedExpiresAt, headers: {}, + offset: i * partSize, + length: Math.min(partSize, size - i * partSize), })), ) } @@ -310,6 +314,39 @@ async function prepareUpload( requiredHeaders: uploadId == null ? headers : {}, urls: parts.map((part) => part.url), parts, + workflow: uploadWorkflow(params.objectId, record.id), + } +} + +function uploadWorkflow(objectId: string, sessionId: string): ObjectUploadInstructions['workflow'] { + const sessionPath = `/api/objects/${objectId}/uploads/${sessionId}` + return { + version: '1', + upload: { + method: 'PUT', + urlField: 'parts[].url', + headersField: 'parts[].headers', + fileOffsetField: 'parts[].offset', + contentLengthField: 'parts[].length', + etagHeader: 'ETag', + }, + complete: { + operationId: 'completeObjectUpload', + method: 'POST', + path: `${sessionPath}/completions`, + partsBodyField: 'parts', + }, + rePresign: { + operationId: 'presignObjectUploadParts', + method: 'POST', + path: `${sessionPath}/parts`, + partNumbersBodyField: 'partNumbers', + }, + abort: { + operationId: 'abortObjectUpload', + method: 'DELETE', + path: sessionPath, + }, } } @@ -408,6 +445,8 @@ export async function presignUploadSessionParts( ), expiresAt: presignedExpiresAt, headers, + offset: 0, + length: matter.size ?? 0, })), ) return { @@ -433,6 +472,8 @@ export async function presignUploadSessionParts( ), expiresAt: presignedExpiresAt, headers: {}, + offset: (partNumber - 1) * record.partSize, + length: Math.min(record.partSize, (matter.size ?? 0) - (partNumber - 1) * record.partSize), })), ) return { diff --git a/server/usecases/ports/agent-oauth.ts b/server/usecases/ports/agent-oauth.ts index 02a0419d..3b5e2fa1 100644 --- a/server/usecases/ports/agent-oauth.ts +++ b/server/usecases/ports/agent-oauth.ts @@ -1,17 +1,10 @@ import type { AuthorizationScope } from '@shared/authorization' import type { Database } from '../../platform/interface' -export interface VerifiedAgentOAuthToken { - grantId: string - userId: string - orgId: string - clientId: string - scopes: AuthorizationScope[] -} - export interface AgentOAuthGrant { id: string clientId: string + clientName: string userId: string orgId: string scopes: AuthorizationScope[] @@ -19,14 +12,31 @@ export interface AgentOAuthGrant { lastUsedAt: string | null } +export interface AgentOAuthClient { + clientId: string + clientName: string + disabled: boolean + redirectUris: string[] + responseTypes: string[] + scopes: string[] +} + +export interface RegisteredOAuthApplication { + clientId: string + name: string + uri: string | null + redirectUris: string[] + grantTypes: string[] + scopes: string[] + disabled: boolean + createdAt: string +} + export interface AgentOAuthGateway { - ensureSystemClient(db: Database): Promise - assertLiveGrant( - db: Database, - input: { userId: string; clientId: string; orgId?: string; scopes: readonly string[] }, - ): Promise - verifyAccessToken(db: Database, token: string): Promise + findClient(db: Database, clientId: string): Promise + listRegisteredApplications(db: Database): Promise + revokeJwtAccessToken(db: Database, token: string): Promise + isJwtAccessTokenRevoked(db: Database, tokenId: string): Promise listGrants(db: Database, userId: string): Promise - recordGrantUse(db: Database, input: { grantId: string; userId: string; orgId: string; now: Date }): Promise revokeGrant(db: Database, input: { userId: string; grantId: string; now: Date }): Promise } diff --git a/server/usecases/ports/api-keys.ts b/server/usecases/ports/api-keys.ts index 2dade798..63d7d39c 100644 --- a/server/usecases/ports/api-keys.ts +++ b/server/usecases/ports/api-keys.ts @@ -1,6 +1,5 @@ import type { ApiKeyScope } from '@shared/api-key-templates' import type { ApiKeyPermissions, AuthorizationScope } from '@shared/authorization' -import type { AgentApiKey, AgentApiKeyCreated, AgentGrantableScope } from '@shared/schemas' import type { Database } from '../../platform/interface' export interface VerifiedApiKey { @@ -41,18 +40,4 @@ export interface ApiKeyGateway { ): Promise hasApiKeyPermission(permissions: ApiKeyPermissions | null | undefined, resource: string, action: string): boolean hasApiKeyScope(permissions: ApiKeyPermissions | null | undefined, scope: AuthorizationScope): boolean - listAgentApiKeys(db: Database, userId: string, orgId: string, now: Date): Promise - getAgentApiKey(db: Database, userId: string, orgId: string, keyId: string, now: Date): Promise - issueAgentApiKey( - db: Database, - input: { - name: string - userId: string - orgId: string - scopes: AgentGrantableScope[] - expiresAt: Date - revokeKeyId?: string - }, - ): Promise - revokeAgentApiKey(db: Database, keyId: string): Promise } diff --git a/server/usecases/site/auth-provider.test.ts b/server/usecases/site/auth-provider.test.ts index 85d56499..90d0b644 100644 --- a/server/usecases/site/auth-provider.test.ts +++ b/server/usecases/site/auth-provider.test.ts @@ -1,10 +1,12 @@ import { FREE_SOCIAL_LOGIN_LIMIT } from '@shared/constants' import type { BindingState } from '@shared/types' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { LicenseBindingRepo, SystemOption, SystemOptionsRepo } from '../ports' +import type { Database } from '../../platform/interface' +import type { AgentOAuthGateway, LicenseBindingRepo, SystemOption, SystemOptionsRepo } from '../ports' import { type AuthProviderDeps, deleteAuthProvider, + listAuthProviderSettings, listAuthProviders, listPublicAuthProviders, type UpsertProviderInput, @@ -122,6 +124,43 @@ describe('auth-provider usecase', () => { }) }) + describe('listAuthProviderSettings', () => { + it('combines provider configuration with dynamically registered applications', async () => { + const { deps } = makeDeps({ + listByPrefix: async () => [ + row({ + providerId: 'github', + type: 'builtin', + clientId: 'a', + clientSecret: 'super-secret-value', + enabled: true, + }), + ], + }) + const registeredApplications = [ + { + clientId: 'dynamic-client', + name: 'Build Agent', + uri: null, + redirectUris: ['http://127.0.0.1/callback'], + grantTypes: ['authorization_code'], + scopes: ['objects:read'], + disabled: false, + createdAt: '2026-07-30T12:00:00.000Z', + }, + ] + const listRegisteredApplications = vi.fn(async () => registeredApplications) + const agentOAuth = { listRegisteredApplications } as unknown as AgentOAuthGateway + const db = {} as Database + + const result = await listAuthProviderSettings({ ...deps, agentOAuth }, db, listOptions) + + expect(result.items).toHaveLength(1) + expect(result.registeredApplications).toEqual(registeredApplications) + expect(listRegisteredApplications).toHaveBeenCalledWith(db) + }) + }) + describe('upsertAuthProvider', () => { it('creates a new builtin provider under the free limit and stores it', async () => { edition(COMMUNITY) diff --git a/server/usecases/site/auth-provider.ts b/server/usecases/site/auth-provider.ts index 7e6ab898..1f5c6d7b 100644 --- a/server/usecases/site/auth-provider.ts +++ b/server/usecases/site/auth-provider.ts @@ -20,7 +20,9 @@ import { import type { SiteConfig } from '@shared/schemas' import type { AuthProvider } from '@shared/types' import { hasFeature } from '../../domain/licensing' +import type { Database } from '../../platform/interface' import { + type AgentOAuthGateway, type AppError, badRequest, type CacheService, @@ -105,6 +107,18 @@ export async function listAuthProviders( return { items } } +export async function listAuthProviderSettings( + deps: Pick & { agentOAuth: AgentOAuthGateway }, + db: Database, + { authOrigin }: { authOrigin: string }, +) { + const [{ items }, registeredApplications] = await Promise.all([ + listAuthProviders(deps, { authOrigin }), + deps.agentOAuth.listRegisteredApplications(db), + ]) + return { items, registeredApplications } +} + export async function listPublicAuthProviders( deps: Pick, ): Promise { diff --git a/shared/agent-oauth.ts b/shared/agent-oauth.ts index ba90d3fa..07568f2b 100644 --- a/shared/agent-oauth.ts +++ b/shared/agent-oauth.ts @@ -1,10 +1,33 @@ -import { AGENT_GRANTABLE_API_KEY_SCOPES } from './api-key-templates' +import { AuthorizationScope } from './authorization' -export const AGENT_OAUTH_CLIENT_ID = 'zpan-agent' -export const AGENT_OAUTH_CLIENT_NAME = 'ZPan Agent' export const AGENT_OAUTH_ACCESS_TOKEN_SECONDS = 15 * 60 export const AGENT_OAUTH_REFRESH_TOKEN_SECONDS = 30 * 24 * 60 * 60 -export const RESTISH_OAUTH_REDIRECT_URIS = ['http://localhost:8484/callback', 'http://127.0.0.1:8484/callback'] as const - +export const AGENT_OAUTH_ACTOR_TOKEN_SECONDS = 5 * 60 +export const JWT_BEARER_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:jwt-bearer' +export const TOKEN_EXCHANGE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange' +export const OAUTH_ACCESS_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token' +export const AGENT_ACTOR_RESOURCE = 'urn:zpan:oauth:agent-actor' export const AGENT_OAUTH_STANDARD_SCOPES = ['openid', 'profile', 'email', 'offline_access'] as const -export const AGENT_OAUTH_SCOPES = [...AGENT_OAUTH_STANDARD_SCOPES, ...AGENT_GRANTABLE_API_KEY_SCOPES] as const +export const AGENT_OAUTH_RESOURCE_SCOPES = [ + AuthorizationScope.OBJECTS_READ, + AuthorizationScope.OBJECTS_CREATE, + AuthorizationScope.OBJECTS_UPDATE, + AuthorizationScope.OBJECTS_DELETE, + AuthorizationScope.SHARES_READ, + AuthorizationScope.SHARES_CREATE, + AuthorizationScope.SHARES_DELETE, + AuthorizationScope.QUOTA_READ, + AuthorizationScope.STORAGE_USAGE_READ, +] as const +export const AGENT_OAUTH_SCOPES = [...AGENT_OAUTH_STANDARD_SCOPES, ...AGENT_OAUTH_RESOURCE_SCOPES] as const +export const AGENT_OAUTH_SCOPE_DESCRIPTIONS: Record<(typeof AGENT_OAUTH_RESOURCE_SCOPES)[number], string> = { + [AuthorizationScope.OBJECTS_READ]: 'List, inspect, and download objects', + [AuthorizationScope.OBJECTS_CREATE]: 'Create folders and upload objects', + [AuthorizationScope.OBJECTS_UPDATE]: 'Rename, move, and copy objects', + [AuthorizationScope.OBJECTS_DELETE]: 'Soft-delete objects', + [AuthorizationScope.SHARES_READ]: 'List and inspect shares', + [AuthorizationScope.SHARES_CREATE]: 'Create public shares', + [AuthorizationScope.SHARES_DELETE]: 'Revoke shares', + [AuthorizationScope.QUOTA_READ]: 'Inspect workspace quota', + [AuthorizationScope.STORAGE_USAGE_READ]: 'Inspect workspace storage usage', +} diff --git a/shared/api-key-templates.ts b/shared/api-key-templates.ts index 4358d281..9c258823 100644 --- a/shared/api-key-templates.ts +++ b/shared/api-key-templates.ts @@ -6,7 +6,6 @@ export const ApiKeyTemplate = { IHOST: 'ihost', WEBDAV: 'webdav', REMOTE_DOWNLOAD: 'remote-download', - AGENT: 'agent', } as const export type ApiKeyTemplate = (typeof ApiKeyTemplate)[keyof typeof ApiKeyTemplate] @@ -60,59 +59,10 @@ export const REMOTE_DOWNLOAD_API_KEY_PERMISSIONS = { ]), } satisfies ApiKeyPermissions -export const AGENT_GRANTABLE_API_KEY_SCOPES = [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.OBJECTS_CREATE, - AuthorizationScope.OBJECTS_UPDATE, - AuthorizationScope.OBJECTS_DELETE, - AuthorizationScope.SHARES_READ, - AuthorizationScope.SHARES_CREATE, - AuthorizationScope.SHARES_DELETE, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, -] as const - -export const AGENT_API_KEY_PERMISSIONS = scopePermissions(AGENT_GRANTABLE_API_KEY_SCOPES) - -export const AgentApiKeyShortcut = { - READER: 'reader', - FILE_MANAGER: 'file-manager', - PUBLISHER: 'publisher', -} as const - -export type AgentApiKeyShortcut = (typeof AgentApiKeyShortcut)[keyof typeof AgentApiKeyShortcut] - -export const AGENT_API_KEY_SHORTCUT_SCOPES = { - [AgentApiKeyShortcut.READER]: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.SHARES_READ, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], - [AgentApiKeyShortcut.FILE_MANAGER]: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.OBJECTS_CREATE, - AuthorizationScope.OBJECTS_UPDATE, - AuthorizationScope.OBJECTS_DELETE, - AuthorizationScope.SHARES_READ, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], - [AgentApiKeyShortcut.PUBLISHER]: [ - AuthorizationScope.OBJECTS_READ, - AuthorizationScope.SHARES_READ, - AuthorizationScope.SHARES_CREATE, - AuthorizationScope.SHARES_DELETE, - AuthorizationScope.QUOTA_READ, - AuthorizationScope.STORAGE_USAGE_READ, - ], -} as const satisfies Record - export const API_KEY_TEMPLATE_PERMISSIONS = { [ApiKeyTemplate.IHOST]: IHOST_API_KEY_PERMISSIONS, [ApiKeyTemplate.WEBDAV]: WEBDAV_API_KEY_PERMISSIONS, [ApiKeyTemplate.REMOTE_DOWNLOAD]: REMOTE_DOWNLOAD_API_KEY_PERMISSIONS, - [ApiKeyTemplate.AGENT]: AGENT_API_KEY_PERMISSIONS, } satisfies Record export const API_KEY_TEMPLATES = Object.values(ApiKeyTemplate) diff --git a/shared/authorization.test.ts b/shared/authorization.test.ts index 4109fce0..9d7d795e 100644 --- a/shared/authorization.test.ts +++ b/shared/authorization.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' -import { AGENT_GRANTABLE_API_KEY_SCOPES, WEBDAV_API_KEY_PERMISSIONS } from './api-key-templates' +import { AGENT_OAUTH_RESOURCE_SCOPES } from './agent-oauth' +import { WEBDAV_API_KEY_PERMISSIONS } from './api-key-templates' import { AuthorizationScope, authorizationScope, @@ -21,7 +22,7 @@ describe('authorization scope registry', () => { it('keeps permanent object purge out of agent-grantable scopes', () => { expect(CANONICAL_AUTHORIZATION_SCOPES).toContain(AuthorizationScope.OBJECTS_PURGE) - expect(AGENT_GRANTABLE_API_KEY_SCOPES).not.toContain(AuthorizationScope.OBJECTS_PURGE) + expect(AGENT_OAUTH_RESOURCE_SCOPES).not.toContain(AuthorizationScope.OBJECTS_PURGE) expect(scopePermissions([AuthorizationScope.OBJECTS_DELETE])).toEqual({ objects: ['delete'] }) }) diff --git a/shared/authorization.ts b/shared/authorization.ts index 5ba1b727..2ecfb3f9 100644 --- a/shared/authorization.ts +++ b/shared/authorization.ts @@ -14,10 +14,6 @@ export const AuthorizationScope = { DOWNLOAD_TASKS_CREATE: 'download-tasks:create', DOWNLOAD_TASKS_CANCEL: 'download-tasks:cancel', SITE_ANALYTICS_READ: 'site-analytics:read', - AGENT_API_KEYS_READ: 'agent-api-keys:read', - AGENT_API_KEYS_CREATE: 'agent-api-keys:create', - AGENT_API_KEYS_UPDATE: 'agent-api-keys:update', - AGENT_API_KEYS_DELETE: 'agent-api-keys:delete', AGENT_OAUTH_GRANTS_READ: 'agent-oauth-grants:read', AGENT_OAUTH_GRANTS_CREATE: 'agent-oauth-grants:create', AGENT_OAUTH_GRANTS_DELETE: 'agent-oauth-grants:delete', diff --git a/shared/schemas/agent-api-keys.ts b/shared/schemas/agent-api-keys.ts deleted file mode 100644 index da989b0a..00000000 --- a/shared/schemas/agent-api-keys.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { z } from 'zod' -import { - AGENT_API_KEY_SHORTCUT_SCOPES, - AGENT_GRANTABLE_API_KEY_SCOPES, - AgentApiKeyShortcut, -} from '../api-key-templates' -import { AuthorizationScope } from '../authorization' - -export const agentGrantableScopeSchema = z.enum(AGENT_GRANTABLE_API_KEY_SCOPES) -export type AgentGrantableScope = z.infer - -export const agentApiKeyShortcutSchema = z.enum(Object.values(AgentApiKeyShortcut)) -export type AgentApiKeyShortcutInput = z.infer - -export const agentApiKeyCreateSchema = z.object({ - name: z.string().trim().min(1).max(120), - scopes: z.array(agentGrantableScopeSchema).min(1), - expiresAt: z.string().datetime(), -}) -export type AgentApiKeyCreateInput = z.infer - -export const agentApiKeyRotateSchema = agentApiKeyCreateSchema.partial({ name: true, scopes: true, expiresAt: true }) -export type AgentApiKeyRotateInput = z.infer - -export const agentApiKeyStatusSchema = z.enum(['active', 'expired', 'revoked', 'inaccessible']) -export type AgentApiKeyStatus = z.infer - -export const agentApiKeySchema = z.object({ - id: z.string(), - name: z.string(), - orgId: z.string(), - workspaceName: z.string().nullable(), - scopes: z.array(agentGrantableScopeSchema), - createdAt: z.string(), - expiresAt: z.string(), - lastUsedAt: z.string().nullable(), - status: agentApiKeyStatusSchema, -}) -export type AgentApiKey = z.infer - -export const agentApiKeyListSchema = z.object({ - items: z.array(agentApiKeySchema), - total: z.number().int(), - page: z.number().int(), - pageSize: z.number().int(), -}) -export type AgentApiKeyList = z.infer - -export const agentApiKeyCreatedSchema = z.object({ - key: z.string(), - item: agentApiKeySchema, -}) -export type AgentApiKeyCreated = z.infer - -export const agentApiKeyShortcutOptions = Object.entries(AGENT_API_KEY_SHORTCUT_SCOPES).map(([id, scopes]) => ({ - id: id as AgentApiKeyShortcutInput, - scopes: [...scopes], -})) - -export const agentScopeLabels = { - [AuthorizationScope.OBJECTS_READ]: 'settings.agentAccess.scope.objectsRead', - [AuthorizationScope.OBJECTS_CREATE]: 'settings.agentAccess.scope.objectsCreate', - [AuthorizationScope.OBJECTS_UPDATE]: 'settings.agentAccess.scope.objectsUpdate', - [AuthorizationScope.OBJECTS_DELETE]: 'settings.agentAccess.scope.objectsDelete', - [AuthorizationScope.SHARES_READ]: 'settings.agentAccess.scope.sharesRead', - [AuthorizationScope.SHARES_CREATE]: 'settings.agentAccess.scope.sharesCreate', - [AuthorizationScope.SHARES_DELETE]: 'settings.agentAccess.scope.sharesDelete', - [AuthorizationScope.QUOTA_READ]: 'settings.agentAccess.scope.quotaRead', - [AuthorizationScope.STORAGE_USAGE_READ]: 'settings.agentAccess.scope.storageUsageRead', -} as const satisfies Record diff --git a/shared/schemas/agent-oauth-grants.ts b/shared/schemas/agent-oauth-grants.ts index 7339d3bf..1bf0e686 100644 --- a/shared/schemas/agent-oauth-grants.ts +++ b/shared/schemas/agent-oauth-grants.ts @@ -1,10 +1,6 @@ import { z } from 'zod' -import { - AGENT_OAUTH_ACCESS_TOKEN_SECONDS, - AGENT_OAUTH_CLIENT_NAME, - AGENT_OAUTH_REFRESH_TOKEN_SECONDS, -} from '../agent-oauth' -import { agentGrantableScopeSchema } from './agent-api-keys' +import { AGENT_OAUTH_ACCESS_TOKEN_SECONDS, AGENT_OAUTH_REFRESH_TOKEN_SECONDS } from '../agent-oauth' +import { oauthResourceScopeSchema } from './oauth-resource' export const agentOAuthGrantStatusSchema = z.enum(['active']) export type AgentOAuthGrantStatus = z.infer @@ -12,11 +8,11 @@ export type AgentOAuthGrantStatus = z.infer export const agentOAuthGrantSchema = z.object({ id: z.string(), clientId: z.string(), - clientName: z.string().default(AGENT_OAUTH_CLIENT_NAME), + clientName: z.string(), userId: z.string(), orgId: z.string(), workspaceName: z.string().nullable(), - scopes: z.array(agentGrantableScopeSchema), + scopes: z.array(oauthResourceScopeSchema), createdAt: z.string(), lastUsedAt: z.string().nullable(), status: agentOAuthGrantStatusSchema, @@ -34,7 +30,7 @@ export const agentOAuthConsentContextSchema = z.object({ id: z.string(), name: z.string().nullable(), }), - scopes: z.array(agentGrantableScopeSchema), + scopes: z.array(oauthResourceScopeSchema), standardScopes: z.array(z.string()), redirectUri: z.string(), grantLifetime: z.object({ @@ -60,10 +56,9 @@ export const agentOAuthConsentResultSchema = z.object({ }) export type AgentOAuthConsentResult = z.infer -export function agentOAuthGrantDTO(input: Omit): AgentOAuthGrant { +export function agentOAuthGrantDTO(input: Omit): AgentOAuthGrant { return { ...input, - clientName: AGENT_OAUTH_CLIENT_NAME, status: 'active', } } diff --git a/shared/schemas/index.ts b/shared/schemas/index.ts index f8441153..7b3d5ffa 100644 --- a/shared/schemas/index.ts +++ b/shared/schemas/index.ts @@ -9,28 +9,6 @@ export { adminAnalyticsTrafficSchema, adminOverviewSchema, } from './admin-analytics' -export type { - AgentApiKey, - AgentApiKeyCreated, - AgentApiKeyCreateInput, - AgentApiKeyList, - AgentApiKeyRotateInput, - AgentApiKeyShortcutInput, - AgentApiKeyStatus, - AgentGrantableScope, -} from './agent-api-keys' -export { - agentApiKeyCreatedSchema, - agentApiKeyCreateSchema, - agentApiKeyListSchema, - agentApiKeyRotateSchema, - agentApiKeySchema, - agentApiKeyShortcutOptions, - agentApiKeyShortcutSchema, - agentApiKeyStatusSchema, - agentGrantableScopeSchema, - agentScopeLabels, -} from './agent-api-keys' export type { AgentOAuthConsentContext, AgentOAuthConsentContextRequest, @@ -50,7 +28,6 @@ export { agentOAuthGrantSchema, agentOAuthGrantStatusSchema, } from './agent-oauth-grants' - export type { AnnouncementInput, AnnouncementStatus, @@ -185,6 +162,8 @@ export { } from './errors' export type { ListNotificationsQuery } from './notification' export { listNotificationsQuerySchema } from './notification' +export type { OAuthResourceScope } from './oauth-resource' +export { oauthResourceScopeLabels, oauthResourceScopeSchema } from './oauth-resource' export type { CursorPage, CursorPageQuery, Page, PageQuery } from './pagination' export { cursorPageQuerySchema, cursorPageSchema, pageQuerySchema, pageSchema } from './pagination' export type { PublicProfile, PublicProfileShare, PublicUser } from './profile' @@ -318,6 +297,37 @@ export const presignedObjectUploadPartSchema = z.object({ url: z.string(), expiresAt: z.string(), headers: z.record(z.string(), z.string()), + offset: z.number().int().min(0), + length: z.number().int().min(0), +}) + +export const objectUploadWorkflowSchema = z.object({ + version: z.literal('1'), + upload: z.object({ + method: z.literal('PUT'), + urlField: z.literal('parts[].url'), + headersField: z.literal('parts[].headers'), + fileOffsetField: z.literal('parts[].offset'), + contentLengthField: z.literal('parts[].length'), + etagHeader: z.literal('ETag'), + }), + complete: z.object({ + operationId: z.literal('completeObjectUpload'), + method: z.literal('POST'), + path: z.string(), + partsBodyField: z.literal('parts'), + }), + rePresign: z.object({ + operationId: z.literal('presignObjectUploadParts'), + method: z.literal('POST'), + path: z.string(), + partNumbersBodyField: z.literal('partNumbers'), + }), + abort: z.object({ + operationId: z.literal('abortObjectUpload'), + method: z.literal('DELETE'), + path: z.string(), + }), }) // The upload instructions returned by POST /objects for a file draft. File bytes @@ -334,6 +344,7 @@ export const objectUploadInstructionsSchema = z.object({ requiredHeaders: z.record(z.string(), z.string()), urls: z.array(z.string()), parts: z.array(presignedObjectUploadPartSchema), + workflow: objectUploadWorkflowSchema, }) export const presignObjectUploadPartsResponseSchema = z.object({ diff --git a/shared/schemas/oauth-resource.ts b/shared/schemas/oauth-resource.ts new file mode 100644 index 00000000..b0837d5b --- /dev/null +++ b/shared/schemas/oauth-resource.ts @@ -0,0 +1,18 @@ +import { z } from 'zod' +import { AGENT_OAUTH_RESOURCE_SCOPES } from '../agent-oauth' +import { AuthorizationScope } from '../authorization' + +export const oauthResourceScopeSchema = z.enum(AGENT_OAUTH_RESOURCE_SCOPES) +export type OAuthResourceScope = z.infer + +export const oauthResourceScopeLabels = { + [AuthorizationScope.OBJECTS_READ]: 'settings.agentAccess.scope.objectsRead', + [AuthorizationScope.OBJECTS_CREATE]: 'settings.agentAccess.scope.objectsCreate', + [AuthorizationScope.OBJECTS_UPDATE]: 'settings.agentAccess.scope.objectsUpdate', + [AuthorizationScope.OBJECTS_DELETE]: 'settings.agentAccess.scope.objectsDelete', + [AuthorizationScope.SHARES_READ]: 'settings.agentAccess.scope.sharesRead', + [AuthorizationScope.SHARES_CREATE]: 'settings.agentAccess.scope.sharesCreate', + [AuthorizationScope.SHARES_DELETE]: 'settings.agentAccess.scope.sharesDelete', + [AuthorizationScope.QUOTA_READ]: 'settings.agentAccess.scope.quotaRead', + [AuthorizationScope.STORAGE_USAGE_READ]: 'settings.agentAccess.scope.storageUsageRead', +} as const satisfies Record diff --git a/shared/types/index.ts b/shared/types/index.ts index 38008f58..7e30b1aa 100644 --- a/shared/types/index.ts +++ b/shared/types/index.ts @@ -200,6 +200,18 @@ export interface AuthProvider { export interface AuthProviderList { items: AuthProvider[] callbackBaseUri: string + registeredApplications?: RegisteredOAuthApplication[] +} + +export interface RegisteredOAuthApplication { + clientId: string + name: string + uri: string | null + redirectUris: string[] + grantTypes: string[] + scopes: string[] + disabled: boolean + createdAt: string } export interface CursorPage { @@ -423,6 +435,37 @@ export interface ObjectUploadPartDescriptor { url: string expiresAt: string headers: Record + offset: number + length: number +} + +export interface ObjectUploadWorkflow { + version: '1' + upload: { + method: 'PUT' + urlField: 'parts[].url' + headersField: 'parts[].headers' + fileOffsetField: 'parts[].offset' + contentLengthField: 'parts[].length' + etagHeader: 'ETag' + } + complete: { + operationId: 'completeObjectUpload' + method: 'POST' + path: string + partsBodyField: 'parts' + } + rePresign: { + operationId: 'presignObjectUploadParts' + method: 'POST' + path: string + partNumbersBodyField: 'partNumbers' + } + abort: { + operationId: 'abortObjectUpload' + method: 'DELETE' + path: string + } } // The upload instructions returned by POST /objects for a file draft: the @@ -439,6 +482,7 @@ export interface ObjectUploadInstructions { requiredHeaders: Record urls: string[] parts: ObjectUploadPartDescriptor[] + workflow: ObjectUploadWorkflow } export type BackgroundJobStatus = 'queued' | 'running' | 'completed' | 'failed' | 'canceled' diff --git a/skills/zpan/SKILL.md b/skills/zpan/SKILL.md deleted file mode 100644 index 86feecfa..00000000 --- a/skills/zpan/SKILL.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -name: zpan -description: Manage ZPan files through Restish v2.3+ and the trusted restish-zpan upload plugin. ---- - -# ZPan Agent Skill - -This Skill targets the ZPan v2.9 Restish integration. - -Use this Skill when an agent needs to browse, inspect, move, copy, delete, -upload, download, share, revoke shares, check quota, or inspect background -tasks on a ZPan instance. - -## Operating Boundary - -ZPan file management uses two surfaces: - -- Generated Restish OpenAPI commands for ordinary API operations. -- `restish zpan-upload` from the `restish-zpan` plugin for every local file - upload. - -Do not read local file bytes, orchestrate upload parts, handle storage response -tags, loop part retries, or expose storage upload URLs. The upload plugin owns -local file streaming, upload state, storage response capture, retry, resume, -abort, and checkpoint cleanup. - -## Start Here - -1. Confirm the ZPan origin with the user before connecting or mutating data. -2. Confirm the Restish API name. Use `zpan` unless the user already has a - different local API name. -3. Require Restish v2.3 or later. -4. Connect exactly one OpenAPI document: `/api/openapi.json`. -5. Select the least-privilege profile that fits the task: - `reader`, `file-manager`, `publisher`, or `ci`. -6. Sync the Restish API before use when it was connected previously. - -Use [references/setup.md](references/setup.md) for install, connect, sync, and -profile selection. - -## Workflow Routing - -- Browsing, inspecting, folders, move/copy/rename, delete, download links, - shares, quota, and tasks: use [references/file-workflows.md](references/file-workflows.md). -- Local uploads: use [references/uploads.md](references/uploads.md). -- CI or unattended automation with an Agent API key: - use [references/ci.md](references/ci.md). -- Optional MCP transport for reviewed ordinary operations: - use [references/mcp.md](references/mcp.md). -- Release or preview acceptance evidence: - use [references/acceptance.md](references/acceptance.md). - -## Safety Rules - -Confirm before: - -- choosing a target workspace; -- overwriting, replacing, or retrying conflict handling; -- soft deleting files or folders; -- permanently purging trash; -- creating public shares; -- installing executable Restish plugins. - -Never ask the user to paste a bearer token. Interactive use goes through -browser OAuth authorization code + PKCE. CI use relies on an environment-backed -Agent API key profile. - -Keep results bounded. Prefer compact object IDs, names, paths, URLs, quota -effects, task state, and upload state over full raw responses. diff --git a/skills/zpan/references/acceptance.md b/skills/zpan/references/acceptance.md deleted file mode 100644 index ab88356f..00000000 --- a/skills/zpan/references/acceptance.md +++ /dev/null @@ -1,48 +0,0 @@ -# Acceptance Evidence - -For release or preview verification, record the exact origin, Restish version, -plugin source, profile, and commands used. Do not record credentials. - -## Fresh-Machine Interactive Flow - -Verify: - -1. Install Restish v2.3 or later. -2. Confirm the ZPan origin and local API name. -3. Connect `/api/openapi.json`. -4. Sync the API. -5. Approve installing `restish-zpan` from `saltbo/zpan`. -6. Run a safe reader operation and complete browser OAuth authorization code + - PKCE consent. -7. List objects, upload a local file with `restish zpan-upload`, interrupt and - resume one upload when practical, inspect the uploaded object, create a - public share, revoke the share, and check quota. - -For pre-release PR or preview acceptance, a v2.9 GitHub release asset does not -exist yet. Build the already-reviewed `cmd/restish-zpan` source at the exact PR -head, install that trusted local executable for the acceptance run, and -separately verify that the release workflow produces the asset names expected -by Restish. Record that this was a source-build acceptance. - -After v2.9 is released, repeat the install step through the user-facing release -path: - -```sh -restish plugin install saltbo/zpan zpan -``` - -## CI Flow - -Verify: - -1. Create a workspace-scoped Agent API key in ZPan settings. -2. Store it in `ZPAN_AGENT_API_KEY`. -3. Use the `ci` Restish profile without token copy/paste. -4. Run list and upload operations. -5. Attempt an operation outside the key scope and confirm `403`. - -## Static Contract - -Run the repository Skill contract check before release. It verifies the required -Restish setup, upload plugin, profile, safety, CI, and MCP guidance while -guarding against removed or unsafe v2.9 workflows. diff --git a/skills/zpan/references/ci.md b/skills/zpan/references/ci.md deleted file mode 100644 index 5273b7eb..00000000 --- a/skills/zpan/references/ci.md +++ /dev/null @@ -1,22 +0,0 @@ -# CI and Unattended Automation - -Use the `ci` Restish profile for unattended jobs. The profile reads the Agent -API key from the environment and does not require token copy/paste: - -```sh -export ZPAN_AGENT_API_KEY="$ZPAN_AGENT_API_KEY" -restish --rsh-profile ci zpan list-objects --parent root --page-size 50 -RSH_PROFILE=ci restish zpan-upload --api zpan --profile ci --parent releases ./dist/app.tar.gz -``` - -The key must be created by a user in ZPan Agent Access settings, scoped to one -workspace, named for the environment, given explicit permissions, and stored in -the CI secret store. The plaintext key is shown once by ZPan and should never be -posted into chat, logs, issue comments, or PR output. - -Use separate keys for separate environments. Expired or revoked keys are -terminal; create a new key when the job needs a different lifetime. - -When a job receives `403`, report the missing operation and expected scope. Do -not broaden the requested scopes automatically. The user should decide whether -to issue a new key or approve a broader scope set. diff --git a/skills/zpan/references/file-workflows.md b/skills/zpan/references/file-workflows.md deleted file mode 100644 index 3f61b66b..00000000 --- a/skills/zpan/references/file-workflows.md +++ /dev/null @@ -1,75 +0,0 @@ -# Ordinary File Workflows - -Use generated Restish OpenAPI commands for ordinary ZPan operations. Run -`restish api sync zpan` before relying on operation names from an older local -connection. - -## List and Inspect - -Use a reader-capable profile for browse and inspect operations: - -```sh -restish --rsh-profile reader zpan list-objects --parent root --page-size 50 -restish --rsh-profile reader zpan get-object obj_123 -restish --rsh-profile reader zpan get-user-quota user_123 -restish --rsh-profile reader zpan get-storage-usage -``` - -Keep list limits explicit and summarize IDs, names, paths, sizes, and relevant -URLs. Do not dump unbounded trees. - -## Create Folders, Move, Copy, and Rename - -Use `file-manager` for object mutations: - -```sh -restish --rsh-profile file-manager zpan create-object 'name: releases, parent: root, type: folder, dirtype: 1' -restish --rsh-profile file-manager zpan update-object obj_123 'name: release.zip, onConflict: fail' -restish --rsh-profile file-manager zpan transfer-object obj_123 'mode: move, targetOrgId: org_123, targetParent: folder_456' -restish --rsh-profile file-manager zpan copy-object obj_123 'parent: folder_456, onConflict: fail' -``` - -Before writes, confirm the workspace and target folder. Before overwrite or -replace behavior, confirm the conflict policy. - -## Delete and Purge - -Soft delete requires `objects:delete`: - -```sh -restish --rsh-profile file-manager zpan delete-object obj_123 -``` - -Confirm destructive intent before soft delete. Permanent trash purge is more -destructive, must be confirmed separately, and is outside the v2.9 Agent -OAuth/API-key profile templates because it requires `objects:purge` on an -authorized human/operator surface. Do not attempt purge through this Skill or -invent an `operator` Restish profile. Ask the user to complete permanent purge -in an authorized operator surface instead. - -Return soft-deleted object IDs and any quota effect reported by the API. - -## Public Sharing - -Use `publisher` for public shares: - -```sh -restish --rsh-profile publisher zpan create-share 'matterId: obj_123, kind: landing, private: false' -restish --rsh-profile publisher zpan list-shares --page-size 50 -restish --rsh-profile publisher zpan revoke-share share_token_123 'status: revoked' -``` - -Confirm before creating public shares. Summaries may include share IDs, public -URLs, expiry, and revocation state, but should not include credentials. - -## Tasks - -Use generated task operations for status checks: - -```sh -restish --rsh-profile file-manager zpan list-download-tasks --page-size 25 -restish --rsh-profile file-manager zpan get-download-task task_123 -restish --rsh-profile file-manager zpan list-download-task-events task_123 -``` - -Summarize state, progress, and errors. Keep event output bounded. diff --git a/skills/zpan/references/mcp.md b/skills/zpan/references/mcp.md deleted file mode 100644 index 11839a89..00000000 --- a/skills/zpan/references/mcp.md +++ /dev/null @@ -1,20 +0,0 @@ -# Optional Restish MCP - -Restish MCP is optional and only for reviewed ordinary OpenAPI operations. It is -not the upload transport. Local file uploads still use `restish zpan-upload`. - -Default to read-only MCP: - -```sh -restish plugin install rest-sh/restish mcp -restish mcp serve zpan --operations list-objects,get-object,list-shares,get-user-quota,get-storage-usage -``` - -Enable write tools only after reviewing the exact operation allowlist. Do not -allow upload control-plane operation IDs through MCP. - -Keep results bounded. Do not route file bytes, storage upload URLs, bearer -tokens, cookies, API keys, or checkpoint contents through MCP results. - -Do not expose authentication, administration, billing, entitlement, membership, -or credential-management operations through MCP. diff --git a/skills/zpan/references/setup.md b/skills/zpan/references/setup.md deleted file mode 100644 index 6b48059a..00000000 --- a/skills/zpan/references/setup.md +++ /dev/null @@ -1,77 +0,0 @@ -# ZPan Restish Setup - -## Confirm Origin and API Name - -Before connecting, ask the user to confirm: - -- the ZPan origin, for example `https://files.example.com`; -- the local Restish API name, normally `zpan`; -- the intended workspace if the next operation reads or changes workspace data. - -Use one OpenAPI document only: - -```sh -restish api connect zpan https://files.example.com/api/openapi.json --replace --yes -``` - -The `--yes` here approves replacing the Restish API connection after the user -has confirmed the origin. It does not approve plugin installation. - -For an existing connection, sync before use: - -```sh -restish api sync zpan -``` - -## Restish Version - -Require Restish v2.3 or later: - -```sh -restish --version -``` - -Stop and ask the user to upgrade if the version is older than v2.3. - -## Profiles and Scopes - -Reader, File manager, and Publisher are Restish convenience profiles that -expand to explicit scopes. They are not server-side roles or route names. - -| Profile | Use for | Scope set | -| --- | --- | --- | -| `reader` | Browse, inspect, download links, quota | `objects:read`, `shares:read`, `quota:read`, `storage-usage:read` | -| `file-manager` | Reader plus create folders, upload, move, copy, rename, soft delete | Reader scopes plus `objects:create`, `objects:update`, `objects:delete` | -| `publisher` | Reader plus create and revoke public shares | Reader scopes plus `shares:create`, `shares:delete` | -| `ci` | Unattended file-management automation | Environment-backed `agentApiKey` with file-manager scopes | - -Prefer the narrowest profile: - -```sh -restish --rsh-profile reader zpan list-objects --page-size 50 -restish --rsh-profile file-manager zpan list-objects --page-size 50 -restish --rsh-profile publisher zpan list-shares --page-size 50 -``` - -The first safe OAuth-backed command may open the browser for authorization code -+ PKCE consent. Restish owns token storage, refresh, logout, and redacted auth -diagnostics. - -Use `--rsh-no-browser` only when the authorization-code callback can still be -completed manually. - -## Upload Plugin Trust Gate - -Install `restish-zpan` only after telling the user that Restish plugins are -trusted local executable code and asking them to approve this source: - -```sh -restish plugin install saltbo/zpan zpan -``` - -This shorthand is the post-v2.9-release user path. For pre-release preview -acceptance, follow [acceptance.md](acceptance.md) and label the trusted local -source build explicitly. - -Do not add a silent approval flag to plugin installation. After installation, -confirm that `restish zpan-upload` is available before using upload workflows. diff --git a/skills/zpan/references/uploads.md b/skills/zpan/references/uploads.md deleted file mode 100644 index 126af78c..00000000 --- a/skills/zpan/references/uploads.md +++ /dev/null @@ -1,65 +0,0 @@ -# Upload Workflows - -Every local file upload must use the Restish command plugin: - -```sh -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --parent root ./artifact.zip -``` - -The Skill must not implement upload chunking or upload orchestration. -`restish-zpan` validates `createObject`, `presignObjectUploadParts`, -`completeObjectUpload`, and `abortObjectUpload`, then creates or resumes ZPan -upload sessions through Restish delegated HTTP, streams file parts from disk to -storage, records storage responses, retries parts, and removes safe checkpoints -after completion. - -## Before Uploading - -Confirm: - -- the target workspace; -- the target folder or parent object ID; -- whether a same-name destination should fail, rename, or replace; -- plugin trust if `restish zpan-upload` is not installed yet. - -Install only after explicit source approval: - -```sh -restish plugin install saltbo/zpan zpan -``` - -## Upload - -Use the selected Restish host profile, plugin profile, and API name explicitly. -For plugin delegated HTTP on Restish v2.3, set `RSH_PROFILE` to the same value -as the plugin `--profile` flag. The environment selects the host credential; -the flag separately selects spec validation and checkpoint identity. - -```sh -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --parent folder_456 ./release.tar.gz -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --parent folder_456 ./release.tar.gz release-linux.tar.gz -``` - -If the plugin supports a conflict flag in the installed version, pass only the -user-approved policy. - -## Resume and Abort - -Resume interrupted local uploads through the plugin: - -```sh -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --resume ./release.tar.gz -``` - -Abort an upload only after confirmation: - -```sh -RSH_PROFILE=file-manager restish zpan-upload --api zpan --profile file-manager --abort ./release.tar.gz -``` - -## Output - -Return a compact summary with object ID, object URL or share URL when relevant, -parent ID, upload mode, part count, bytes uploaded, task state, and quota effect -when reported. Do not expose storage upload URLs, bearer tokens, API keys, -cookies, or checkpoint contents. diff --git a/spec/agent-api-keys.feature b/spec/agent-api-keys.feature deleted file mode 100644 index 89631682..00000000 --- a/spec/agent-api-keys.feature +++ /dev/null @@ -1,48 +0,0 @@ -Feature: Agent API keys - Workspace-scoped Agent API keys provide a CI and unattended-service credential - path. Keys are owned by one authorizing user, bound to one workspace, grant only - explicit Agent scopes, expire, and are revealed only once. - - @agent-api-keys/lifecycle @api - Scenario: A user manages a personal workspace Agent API key - Given an authenticated personal workspace owner - When they create, list, rotate, and revoke an Agent API key - Then the plaintext key is returned only on create or rotation - And revoked keys stop working immediately - - @agent-api-keys/team-file-ops @api - Scenario: A team Agent API key performs granted file operations - Given a team workspace owner creates an Agent API key with file read and create scopes - When the owner later becomes an editor - Then the key can list files and create folders in that workspace - - @agent-api-keys/management-role @api - Scenario: Team credential management is restricted to owners and admins - Given a team workspace member - When an editor tries to list or create Agent API keys - Then the API denies credential management - And an owner or admin can manage Agent API keys - - @agent-api-keys/scope-boundary @api - Scenario: Agent API keys cannot request non-Agent scopes - Given an authenticated workspace editor - When they request image-hosting or raw Better Auth Agent permissions - Then the API rejects the key creation request - - @agent-api-keys/denials @api - Scenario: Agent API keys fail closed - Given a workspace Agent API key - When the key is missing scope, crosses workspaces, is revoked, expires, or its owner is banned - Then protected APIs reject the request - - @agent-api-keys/role-reduction @api - Scenario: Agent API keys recheck current workspace role - Given a team Agent API key created by an owner - When the owner is reduced to viewer - Then management and editor-only file operations are denied - - @agent-api-keys/terminal-rotation @api - Scenario: Expired and revoked Agent API keys are terminal - Given an expired or revoked Agent API key - When an owner tries to rotate it - Then the API rejects rotation and requires a new key diff --git a/src/components/admin/registered-oauth-applications-section.tsx b/src/components/admin/registered-oauth-applications-section.tsx new file mode 100644 index 00000000..4ad4702b --- /dev/null +++ b/src/components/admin/registered-oauth-applications-section.tsx @@ -0,0 +1,62 @@ +import { useQuery } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' +import { listAuthProviders } from '@/lib/api' + +const providersQueryKey = ['admin', 'auth-providers'] as const + +export function RegisteredOAuthApplicationsSection() { + const { t } = useTranslation() + const { data, isLoading } = useQuery({ + queryKey: providersQueryKey, + queryFn: listAuthProviders, + }) + const applications = data?.registeredApplications ?? [] + + return ( +

+
+

{t('admin.auth.registeredApplications')}

+

{t('admin.auth.registeredApplicationsDescription')}

+
+ {isLoading ? ( +

{t('common.loading')}

+ ) : applications.length === 0 ? ( +
+ {t('admin.auth.noRegisteredApplications')} +
+ ) : ( +
+ + + + {t('admin.auth.application')} + {t('admin.auth.clientId')} + {t('admin.auth.redirectUri')} + {t('admin.auth.grants')} + {t('admin.auth.enabled')} + + + + {applications.map((application) => ( + + {application.name} + + {application.clientId} + + + {application.redirectUris.join(', ')} + + {application.grantTypes.join(', ')} + + {application.disabled ? t('admin.auth.statusDisabled') : t('admin.auth.statusEnabled')} + + + ))} + +
+
+ )} +
+ ) +} diff --git a/src/components/upload/multipart-upload.test.ts b/src/components/upload/multipart-upload.test.ts index 78414c5a..18d541d1 100644 --- a/src/components/upload/multipart-upload.test.ts +++ b/src/components/upload/multipart-upload.test.ts @@ -42,7 +42,37 @@ function makeUpload(urls: string[], partSize: number): ObjectUploadInstructions url, expiresAt: '2026-01-01T00:15:00.000Z', headers: {}, + offset: index * partSize, + length: partSize, })), + workflow: { + version: '1', + upload: { + method: 'PUT', + urlField: 'parts[].url', + headersField: 'parts[].headers', + fileOffsetField: 'parts[].offset', + contentLengthField: 'parts[].length', + etagHeader: 'ETag', + }, + complete: { + operationId: 'completeObjectUpload', + method: 'POST', + path: '/api/objects/object-1/upload/completions', + partsBodyField: 'parts', + }, + rePresign: { + operationId: 'presignObjectUploadParts', + method: 'POST', + path: '/api/objects/object-1/upload/parts', + partNumbersBodyField: 'partNumbers', + }, + abort: { + operationId: 'abortObjectUpload', + method: 'DELETE', + path: '/api/objects/object-1/upload', + }, + }, } } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 1beca1ec..10613273 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -912,6 +912,12 @@ "admin.auth.copyCallbackUri": "Copy callback URI", "admin.auth.callbackUriCopied": "Callback URI copied", "admin.auth.clientId": "Client ID", + "admin.auth.registeredApplications": "Registered Applications", + "admin.auth.registeredApplicationsDescription": "OAuth clients registered dynamically with this ZPan instance. Registrations are active immediately.", + "admin.auth.noRegisteredApplications": "No dynamically registered applications.", + "admin.auth.application": "Application", + "admin.auth.redirectUri": "Redirect URI", + "admin.auth.grants": "Grant types", "admin.auth.clientIdPlaceholder": "OAuth client ID", "admin.auth.clientSecret": "Client Secret", "admin.auth.clientSecretPlaceholder": "OAuth client secret", @@ -1202,19 +1208,8 @@ "settings.apiKeys.orgRequired": "Select a workspace before creating this API key.", "settings.apiKeys.manage": "Manage API Keys", "settings.agentAccess.section": "Agent Access", - "settings.agentAccess.description": "Manage workspace-scoped Agent API keys for CI and unattended services.", + "settings.agentAccess.description": "Review and revoke delegated OAuth access to your workspaces.", "settings.agentAccess.workspaceLabel": "Workspace", - "settings.agentAccess.workspacePlaceholder": "Select a workspace", - "settings.agentAccess.create": "Create Key", - "settings.agentAccess.createTitle": "Create Agent API Key", - "settings.agentAccess.createDescription": "Choose a workspace, expiry, and exact scopes.", - "settings.agentAccess.nameLabel": "Name", - "settings.agentAccess.namePlaceholder": "e.g. GitHub Actions deploy", - "settings.agentAccess.expiryLabel": "Expiry", - "settings.agentAccess.shortcutsLabel": "Shortcuts", - "settings.agentAccess.shortcut.reader": "Reader", - "settings.agentAccess.shortcut.file-manager": "File manager", - "settings.agentAccess.shortcut.publisher": "Publisher", "settings.agentAccess.scope.objectsRead": "Files: read objects", "settings.agentAccess.scope.objectsCreate": "Files: create objects", "settings.agentAccess.scope.objectsUpdate": "Files: update objects", @@ -1224,35 +1219,16 @@ "settings.agentAccess.scope.sharesDelete": "Shares: revoke shares", "settings.agentAccess.scope.quotaRead": "Quota: read workspace quota", "settings.agentAccess.scope.storageUsageRead": "Storage usage: read workspace usage", - "settings.agentAccess.colName": "Name", "settings.agentAccess.colWorkspace": "Workspace", "settings.agentAccess.colScopes": "Scopes", "settings.agentAccess.colCreated": "Created", - "settings.agentAccess.colExpires": "Expires", "settings.agentAccess.colLastUsed": "Last Used", - "settings.agentAccess.colStatus": "Status", "settings.agentAccess.colActions": "Actions", - "settings.agentAccess.status.active": "Active", - "settings.agentAccess.status.expired": "Expired", - "settings.agentAccess.status.revoked": "Revoked", - "settings.agentAccess.status.inaccessible": "Inaccessible", - "settings.agentAccess.noKeys": "No Agent API keys yet", - "settings.agentAccess.managementRequired": "Owner or admin access is required to manage Agent API keys for this workspace.", "settings.agentAccess.never": "Never", - "settings.agentAccess.copy": "Copy", - "settings.agentAccess.copied": "Copied", - "settings.agentAccess.createSuccess": "Agent API key created", - "settings.agentAccess.rotate": "Rotate", - "settings.agentAccess.rotateSuccess": "Agent API key rotated", "settings.agentAccess.revoke": "Revoke", - "settings.agentAccess.revokeTitle": "Revoke Agent API Key", - "settings.agentAccess.revokeConfirm": "Revoke Agent API key \"{{name}}\"? Any services using it will stop immediately.", - "settings.agentAccess.revokeSuccess": "Agent API key revoked", - "settings.agentAccess.revealedTitle": "Save Your Agent API Key", - "settings.agentAccess.revealedWarning": "This is the only time this key will be shown. Store it securely.", "settings.agentAccess.oauthConsentEyebrow": "Delegated OAuth access", - "settings.agentAccess.oauthConsentTitle": "Authorize ZPan Agent", - "settings.agentAccess.oauthConsentDescription": "Review the exact workspace and scopes Restish will receive before continuing.", + "settings.agentAccess.oauthConsentTitle": "Authorize Application", + "settings.agentAccess.oauthConsentDescription": "Review the exact workspace and scopes this application will receive before continuing.", "settings.agentAccess.oauthClient": "Client", "settings.agentAccess.oauthOrigin": "ZPan instance", "settings.agentAccess.oauthReturn": "Return URL", @@ -1263,16 +1239,15 @@ "settings.agentAccess.oauthApprove": "Approve Access", "settings.agentAccess.oauthDeny": "Deny", "settings.agentAccess.oauthExpiredTitle": "OAuth request expired", - "settings.agentAccess.oauthExpiredDescription": "Start the Restish connection again to create a fresh authorization request.", + "settings.agentAccess.oauthExpiredDescription": "Start the connection again to create a fresh authorization request.", "settings.agentAccess.oauthConsentFailed": "Could not finish OAuth consent.", "settings.agentAccess.oauthWorkspaceFailed": "Could not switch workspace.", "settings.agentAccess.oauthGrantsSection": "Delegated OAuth Grants", - "settings.agentAccess.oauthGrantsDescription": "Manage Restish OAuth grants connected to your workspaces.", + "settings.agentAccess.oauthGrantsDescription": "Manage application OAuth grants connected to your workspaces.", "settings.agentAccess.oauthNoGrants": "No delegated OAuth grants yet", "settings.agentAccess.oauthGrantsError": "Could not load delegated OAuth grants.", "settings.agentAccess.oauthGrantRevokeTitle": "Revoke OAuth Grant", - "settings.agentAccess.oauthGrantRevokeConfirm": "Revoke {{client}} access to {{workspace}}? Active Restish sessions for this workspace will stop immediately.", - "settings.agentAccess.oauthGrantRevokeSuccess": "OAuth grant revoked", + "settings.agentAccess.oauthGrantRevokeConfirm": "Revoke {{client}} access to {{workspace}}? Active sessions for this workspace will stop immediately.", "settings.appearance.theme.description": "Choose how ZPan looks. Follows your system setting by default.", "settings.appearance.language.description": "The display language for the app.", "settings.appearance.autoSaved": "Changes apply immediately.", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 5c2e719a..788e25b7 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -912,6 +912,12 @@ "admin.auth.copyCallbackUri": "复制 Callback URI", "admin.auth.callbackUriCopied": "Callback URI 已复制", "admin.auth.clientId": "Client ID", + "admin.auth.registeredApplications": "已注册应用", + "admin.auth.registeredApplicationsDescription": "通过动态注册接入当前 ZPan 实例的 OAuth 客户端。当前版本注册后立即生效。", + "admin.auth.noRegisteredApplications": "暂无动态注册的应用。", + "admin.auth.application": "应用", + "admin.auth.redirectUri": "回调地址", + "admin.auth.grants": "授权类型", "admin.auth.clientIdPlaceholder": "OAuth client ID", "admin.auth.clientSecret": "Client Secret", "admin.auth.clientSecretPlaceholder": "OAuth client secret", @@ -1202,19 +1208,8 @@ "settings.apiKeys.orgRequired": "创建该 API Key 前请先选择工作区。", "settings.apiKeys.manage": "管理 API Key", "settings.agentAccess.section": "Agent Access", - "settings.agentAccess.description": "管理用于 CI 和无人值守服务的工作空间级 Agent API Key。", + "settings.agentAccess.description": "查看并撤销应用对工作空间的 OAuth 委托访问。", "settings.agentAccess.workspaceLabel": "工作空间", - "settings.agentAccess.workspacePlaceholder": "选择工作空间", - "settings.agentAccess.create": "创建 Key", - "settings.agentAccess.createTitle": "创建 Agent API Key", - "settings.agentAccess.createDescription": "选择工作空间、过期时间和明确权限。", - "settings.agentAccess.nameLabel": "名称", - "settings.agentAccess.namePlaceholder": "例如:GitHub Actions deploy", - "settings.agentAccess.expiryLabel": "过期时间", - "settings.agentAccess.shortcutsLabel": "快捷模板", - "settings.agentAccess.shortcut.reader": "Reader", - "settings.agentAccess.shortcut.file-manager": "File manager", - "settings.agentAccess.shortcut.publisher": "Publisher", "settings.agentAccess.scope.objectsRead": "文件:读取对象", "settings.agentAccess.scope.objectsCreate": "文件:创建对象", "settings.agentAccess.scope.objectsUpdate": "文件:更新对象", @@ -1224,35 +1219,16 @@ "settings.agentAccess.scope.sharesDelete": "分享:撤销分享", "settings.agentAccess.scope.quotaRead": "配额:读取工作空间配额", "settings.agentAccess.scope.storageUsageRead": "存储用量:读取工作空间用量", - "settings.agentAccess.colName": "名称", "settings.agentAccess.colWorkspace": "工作空间", "settings.agentAccess.colScopes": "权限", "settings.agentAccess.colCreated": "创建时间", - "settings.agentAccess.colExpires": "过期时间", "settings.agentAccess.colLastUsed": "最近使用", - "settings.agentAccess.colStatus": "状态", "settings.agentAccess.colActions": "操作", - "settings.agentAccess.status.active": "有效", - "settings.agentAccess.status.expired": "已过期", - "settings.agentAccess.status.revoked": "已撤销", - "settings.agentAccess.status.inaccessible": "不可访问", - "settings.agentAccess.noKeys": "暂无 Agent API Key", - "settings.agentAccess.managementRequired": "需要工作空间所有者或管理员权限才能管理 Agent API Key。", "settings.agentAccess.never": "从未", - "settings.agentAccess.copy": "复制", - "settings.agentAccess.copied": "已复制", - "settings.agentAccess.createSuccess": "Agent API Key 已创建", - "settings.agentAccess.rotate": "轮换", - "settings.agentAccess.rotateSuccess": "Agent API Key 已轮换", "settings.agentAccess.revoke": "撤销", - "settings.agentAccess.revokeTitle": "撤销 Agent API Key", - "settings.agentAccess.revokeConfirm": "撤销 Agent API Key「{{name}}」?使用该 Key 的服务将立即停止工作。", - "settings.agentAccess.revokeSuccess": "Agent API Key 已撤销", - "settings.agentAccess.revealedTitle": "保存你的 Agent API Key", - "settings.agentAccess.revealedWarning": "该 Key 只会显示一次,请妥善保存。", "settings.agentAccess.oauthConsentEyebrow": "委托 OAuth 访问", - "settings.agentAccess.oauthConsentTitle": "授权 ZPan Agent", - "settings.agentAccess.oauthConsentDescription": "继续前请确认 Restish 将获得的具体工作空间和权限。", + "settings.agentAccess.oauthConsentTitle": "授权应用", + "settings.agentAccess.oauthConsentDescription": "继续前请确认该应用将获得的具体工作空间和权限。", "settings.agentAccess.oauthClient": "客户端", "settings.agentAccess.oauthOrigin": "ZPan 实例", "settings.agentAccess.oauthReturn": "返回 URL", @@ -1263,16 +1239,15 @@ "settings.agentAccess.oauthApprove": "批准访问", "settings.agentAccess.oauthDeny": "拒绝", "settings.agentAccess.oauthExpiredTitle": "OAuth 请求已过期", - "settings.agentAccess.oauthExpiredDescription": "请从 Restish 重新发起连接,生成新的授权请求。", + "settings.agentAccess.oauthExpiredDescription": "请重新发起连接,生成新的授权请求。", "settings.agentAccess.oauthConsentFailed": "无法完成 OAuth 授权。", "settings.agentAccess.oauthWorkspaceFailed": "无法切换工作空间。", "settings.agentAccess.oauthGrantsSection": "委托 OAuth 授权", - "settings.agentAccess.oauthGrantsDescription": "管理连接到你工作空间的 Restish OAuth 授权。", + "settings.agentAccess.oauthGrantsDescription": "管理连接到你工作空间的应用 OAuth 授权。", "settings.agentAccess.oauthNoGrants": "暂无委托 OAuth 授权", "settings.agentAccess.oauthGrantsError": "无法加载委托 OAuth 授权。", "settings.agentAccess.oauthGrantRevokeTitle": "撤销 OAuth 授权", - "settings.agentAccess.oauthGrantRevokeConfirm": "撤销 {{client}} 对 {{workspace}} 的访问?该工作空间的 Restish 会话将立即停止。", - "settings.agentAccess.oauthGrantRevokeSuccess": "OAuth 授权已撤销", + "settings.agentAccess.oauthGrantRevokeConfirm": "撤销 {{client}} 对 {{workspace}} 的访问?该工作空间的活动会话将立即停止。", "settings.appearance.theme.description": "选择 ZPan 的外观,默认跟随系统。", "settings.appearance.language.description": "界面显示语言。", "settings.appearance.autoSaved": "修改即时生效。", diff --git a/src/lib/api.test.ts b/src/lib/api.test.ts index 18246a83..d0093388 100644 --- a/src/lib/api.test.ts +++ b/src/lib/api.test.ts @@ -12,7 +12,6 @@ import { connectCloud, continueCloudOrderPayment, copyObject, - createAgentApiKey, createAnnouncement, createBackgroundJob, createCloudBillingPortalSession, @@ -81,7 +80,6 @@ import { listActiveAnnouncements, listAdminAnnouncements, listAdminAuditLogs, - listAgentApiKeys, listAgentOAuthGrants, listAnnouncements, listApiKeys, @@ -124,7 +122,6 @@ import { resetBrandingField, restoreObject, retryBackgroundJob, - revokeAgentApiKey, revokeAgentOAuthGrant, revokeIhostApiKey, revokeOrgEntitlement, @@ -133,7 +130,6 @@ import { revokeSiteInvitation, revokeUserEntitlement, revokeWebDavAppPassword, - rotateAgentApiKey, runDownloadTaskAction, saveBranding, saveEmailConfig, @@ -3122,102 +3118,12 @@ describe('api', () => { }) }) - describe('Agent Access API keys', () => { - const sampleList = { - items: [ - { - id: 'agent-key-1', - name: 'CI', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-07-29T00:00:00.000Z', - expiresAt: '2026-10-27T00:00:00.000Z', - lastUsedAt: null, - status: 'active', - }, - ], - total: 1, - page: 1, - pageSize: 50, - } - - it('lists workspace Agent API keys through the Hono RPC route', async () => { - vi.mocked(fetch).mockResolvedValueOnce(makeResponse(sampleList)) - - const result = await listAgentApiKeys('org-1') - - expect(result).toEqual(sampleList) - const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] - expect(url).toContain('/api/workspaces/org-1/agent-api-keys') - expect(url).toContain('page=1') - expect(url).toContain('pageSize=50') - expect(init.method).toBe('GET') - }) - - it('creates a workspace Agent API key with explicit scopes and expiry', async () => { - const payload = { key: 'zpan_agent_secret', item: sampleList.items[0] } - vi.mocked(fetch).mockResolvedValueOnce(makeResponse(payload, true, 201)) - - const result = await createAgentApiKey('org-1', { - name: 'CI', - scopes: ['objects:read'], - expiresAt: '2026-10-27T00:00:00.000Z', - }) - - expect(result).toEqual(payload) - const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] - expect(url).toContain('/api/workspaces/org-1/agent-api-keys') - expect(init.method).toBe('POST') - expect(JSON.parse(init.body as string)).toEqual({ - name: 'CI', - scopes: ['objects:read'], - expiresAt: '2026-10-27T00:00:00.000Z', - }) - }) - - it('rotates a workspace Agent API key without sending the old secret', async () => { - const payload = { key: 'zpan_agent_rotated', item: { ...sampleList.items[0], id: 'agent-key-2' } } - vi.mocked(fetch).mockResolvedValueOnce(makeResponse(payload, true, 201)) - - const result = await rotateAgentApiKey('org-1', 'agent-key-1', { name: 'CI rotated' }) - - expect(result).toEqual(payload) - const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] - expect(url).toContain('/api/workspaces/org-1/agent-api-keys/agent-key-1/rotations') - expect(init.method).toBe('POST') - expect(JSON.parse(init.body as string)).toEqual({ name: 'CI rotated' }) - }) - - it('revokes a workspace Agent API key with DELETE', async () => { - vi.mocked(fetch).mockResolvedValueOnce(makeResponse(null, true, 204)) - - await revokeAgentApiKey('org-1', 'agent-key-1') - - const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] - expect(url).toContain('/api/workspaces/org-1/agent-api-keys/agent-key-1') - expect(init.method).toBe('DELETE') - }) - - it('throws ApiError when Agent key creation fails', async () => { - vi.mocked(fetch).mockResolvedValueOnce(makeResponse({ error: 'Forbidden' }, false, 403)) - - await expect( - createAgentApiKey('org-1', { - name: 'CI', - scopes: ['objects:read'], - expiresAt: '2026-10-27T00:00:00.000Z', - }), - ).rejects.toThrow('Forbidden') - }) - }) - describe('Agent OAuth consent and grants', () => { const sampleGrantList = { items: [ { id: 'grant-1', - clientId: 'zpan-agent', + clientId: 'dynamic-client', clientName: 'ZPan Agent', userId: 'user-1', orgId: 'org-1', @@ -3232,7 +3138,7 @@ describe('api', () => { it('loads server-owned OAuth consent context with the raw OAuth query', async () => { const payload = { - clientId: 'zpan-agent', + clientId: 'dynamic-client', clientName: 'ZPan Agent', instanceOrigin: 'https://zpan.example.test', workspace: { id: 'org-1', name: 'Personal' }, @@ -3243,19 +3149,19 @@ describe('api', () => { } vi.mocked(fetch).mockResolvedValueOnce(makeResponse(payload)) - const result = await getAgentOAuthConsentContext('client_id=zpan-agent&scope=objects%3Aread') + const result = await getAgentOAuthConsentContext('client_id=dynamic-client&scope=objects%3Aread') expect(result).toEqual(payload) const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] expect(url).toContain('/api/agent-oauth-consent') - expect(url).toContain('oauthQuery=client_id%3Dzpan-agent%26scope%3Dobjects%253Aread') + expect(url).toContain('oauthQuery=client_id%3Ddynamic-client%26scope%3Dobjects%253Aread') expect(init.method).toBe('GET') }) it('submits full OAuth consent through the Hono RPC wrapper without sending scope overrides', async () => { vi.mocked(fetch).mockResolvedValueOnce(makeResponse({ url: 'http://127.0.0.1:8484/callback?code=abc' })) - const result = await submitAgentOAuthConsent({ accept: true, oauthQuery: 'client_id=zpan-agent' }) + const result = await submitAgentOAuthConsent({ accept: true, oauthQuery: 'client_id=dynamic-client' }) expect(result).toEqual({ url: 'http://127.0.0.1:8484/callback?code=abc' }) const [url, init] = vi.mocked(fetch).mock.calls[0] as [string, RequestInit] @@ -3264,7 +3170,7 @@ describe('api', () => { expect(init.credentials).toBe('include') expect(JSON.parse(init.body as string)).toEqual({ accept: true, - oauthQuery: 'client_id=zpan-agent', + oauthQuery: 'client_id=dynamic-client', }) }) @@ -3278,7 +3184,7 @@ describe('api', () => { }, } as unknown as Response) - await expect(submitAgentOAuthConsent({ accept: false, oauthQuery: 'client_id=zpan-agent' })).rejects.toThrow( + await expect(submitAgentOAuthConsent({ accept: false, oauthQuery: 'client_id=dynamic-client' })).rejects.toThrow( ApiError, ) }) diff --git a/src/lib/api.ts b/src/lib/api.ts index 804f2ce5..96ffbb4e 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -1,11 +1,6 @@ import { type ApiKeyMetadata, ApiKeyTemplate } from '@shared/api-key-templates' import type { OAuthProviderConfig } from '@shared/oauth-providers' import type { - AgentApiKey, - AgentApiKeyCreated, - AgentApiKeyCreateInput, - AgentApiKeyList, - AgentApiKeyRotateInput, AgentOAuthConsentContext, AgentOAuthConsentResult, AgentOAuthConsentSubmit, @@ -107,7 +102,6 @@ import { adminQuotas, adminSiteInvitations, adminTeams, - agentApiKeysApi, agentOAuthGrantsApi, announcementsApi, authedSharesApi, @@ -1096,49 +1090,7 @@ export function deleteIhostConfig() { }) } -// Agent Access API keys - -export type { - AgentApiKey, - AgentApiKeyCreated, - AgentApiKeyCreateInput, - AgentApiKeyList, - AgentApiKeyRotateInput, - AgentOAuthConsentContext, - AgentOAuthConsentResult, - AgentOAuthGrant, - AgentOAuthGrantList, -} - -export function listAgentApiKeys(orgId: string, page = 1, pageSize = 50) { - return unwrap( - agentApiKeysApi[':orgId']['agent-api-keys'].$get({ - param: { orgId }, - query: { page: String(page), pageSize: String(pageSize) }, - }), - ) -} - -export function createAgentApiKey(orgId: string, input: AgentApiKeyCreateInput) { - return unwrap( - agentApiKeysApi[':orgId']['agent-api-keys'].$post({ param: { orgId }, json: input }), - ) -} - -export function rotateAgentApiKey(orgId: string, keyId: string, input: AgentApiKeyRotateInput = {}) { - return unwrap( - agentApiKeysApi[':orgId']['agent-api-keys'][':keyId'].rotations.$post({ - param: { orgId, keyId }, - json: input, - }), - ) -} - -export function revokeAgentApiKey(orgId: string, keyId: string) { - return agentApiKeysApi[':orgId']['agent-api-keys'][':keyId'].$delete({ param: { orgId, keyId } }).then((res) => { - if (!res.ok) throw new ApiError(res.status, toErrorBody(res.status, { error: res.statusText })) - }) -} +export type { AgentOAuthConsentContext, AgentOAuthConsentResult, AgentOAuthGrant, AgentOAuthGrantList } export function getAgentOAuthConsentContext(oauthQuery: string) { return unwrap(agentOAuthGrantsApi['agent-oauth-consent'].$get({ query: { oauthQuery } })) diff --git a/src/lib/rpc.ts b/src/lib/rpc.ts index 7624180a..bcb782c0 100644 --- a/src/lib/rpc.ts +++ b/src/lib/rpc.ts @@ -6,7 +6,6 @@ import type { AdminSiteInvitationsRoute, AdminStatsRoute, AdminTeamsRoute, - AgentApiKeysRoute, AgentOAuthGrantsRoute, AnnouncementsRoute, AuthedSharesRoute, @@ -51,7 +50,6 @@ export const objects = hc('/api/objects', opts) export const downloadTasksApi = hc('/api/downloads/tasks', opts) export const downloaderSelfApi = hc('/api/downloads/downloaders', opts) export const trash = hc('/api/trash', opts) -export const agentApiKeysApi = hc('/api/workspaces', opts) export const agentOAuthGrantsApi = hc('/api', opts) export const storages = hc('/api/site/storages', opts) export const storageUsageApi = hc('/api/storage', opts) diff --git a/src/routes/_authenticated/admin/settings/oauth.tsx b/src/routes/_authenticated/admin/settings/oauth.tsx index d12237bd..c2d04215 100644 --- a/src/routes/_authenticated/admin/settings/oauth.tsx +++ b/src/routes/_authenticated/admin/settings/oauth.tsx @@ -1,10 +1,16 @@ import { createFileRoute } from '@tanstack/react-router' import { OAuthProvidersSection } from '@/components/admin/oauth-providers-section' +import { RegisteredOAuthApplicationsSection } from '@/components/admin/registered-oauth-applications-section' export const Route = createFileRoute('/_authenticated/admin/settings/oauth')({ component: AuthSettingsPage, }) function AuthSettingsPage() { - return + return ( +
+ + +
+ ) } diff --git a/src/routes/_authenticated/admin/storages/index.test.tsx b/src/routes/_authenticated/admin/storages/index.test.tsx index 96ff9824..34ddb2f3 100644 --- a/src/routes/_authenticated/admin/storages/index.test.tsx +++ b/src/routes/_authenticated/admin/storages/index.test.tsx @@ -117,8 +117,38 @@ const uploadDraft: CreateObjectResult = { url: 'https://uploads.example.com/object-1', expiresAt: '2026-01-01T00:15:00.000Z', headers: { 'content-type': 'text/plain' }, + offset: 0, + length: 5, }, ], + workflow: { + version: '1', + upload: { + method: 'PUT', + urlField: 'parts[].url', + headersField: 'parts[].headers', + fileOffsetField: 'parts[].offset', + contentLengthField: 'parts[].length', + etagHeader: 'ETag', + }, + complete: { + operationId: 'completeObjectUpload', + method: 'POST', + path: '/api/objects/object-1/upload/completions', + partsBodyField: 'parts', + }, + rePresign: { + operationId: 'presignObjectUploadParts', + method: 'POST', + path: '/api/objects/object-1/upload/parts', + partNumbersBodyField: 'partNumbers', + }, + abort: { + operationId: 'abortObjectUpload', + method: 'DELETE', + path: '/api/objects/object-1/upload', + }, + }, }, } diff --git a/src/routes/_authenticated/settings/agent-access.test.tsx b/src/routes/_authenticated/settings/agent-access.test.tsx deleted file mode 100644 index 5b013454..00000000 --- a/src/routes/_authenticated/settings/agent-access.test.tsx +++ /dev/null @@ -1,474 +0,0 @@ -import type { AgentApiKey, AgentOAuthGrant } from '@shared/schemas' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { toast } from 'sonner' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { - createAgentApiKey, - getAgentOAuthConsentContext, - listAgentApiKeys, - listAgentOAuthGrants, - revokeAgentApiKey, - revokeAgentOAuthGrant, - rotateAgentApiKey, - submitAgentOAuthConsent, -} from '@/lib/api' -import { setActive, useListOrganizations } from '@/lib/auth-client' -import { redirectExternal } from '@/lib/browser-navigation' -import { AgentAccessSettingsPage } from './agent-access' -import { SettingsLayout } from './route' - -const state = vi.hoisted(() => ({ - orgs: [ - { id: 'org-1', name: 'Personal' }, - { id: 'org-2', name: 'Team Alpha' }, - ], - keys: [] as AgentApiKey[], - grants: [] as AgentOAuthGrant[], - webdavEnabled: true, -})) - -const translations: Record = { - 'settings.agentAccess.scope.objectsRead': 'Files: read objects', - 'settings.agentAccess.scope.objectsCreate': 'Files: create objects', - 'settings.agentAccess.scope.objectsUpdate': 'Files: update objects', - 'settings.agentAccess.scope.objectsDelete': 'Files: delete objects', - 'settings.agentAccess.scope.sharesRead': 'Shares: read shares', - 'settings.agentAccess.scope.sharesCreate': 'Shares: create shares', - 'settings.agentAccess.scope.sharesDelete': 'Shares: revoke shares', - 'settings.agentAccess.scope.quotaRead': 'Quota: read workspace quota', - 'settings.agentAccess.scope.storageUsageRead': 'Storage usage: read workspace usage', - 'settings.agentAccess.managementRequired': 'Owner or admin access is required', - 'settings.agentAccess.oauthConsentTitle': 'Authorize ZPan Agent', - 'settings.agentAccess.oauthClient': 'Client', - 'settings.agentAccess.oauthOrigin': 'ZPan instance', - 'settings.agentAccess.oauthReturn': 'Return URL', - 'settings.agentAccess.oauthLifetime': 'Grant lifetime', - 'settings.agentAccess.oauthLifetimeValue': '30 days', - 'settings.agentAccess.oauthScopesTitle': 'Requested scopes', - 'settings.agentAccess.oauthApprove': 'Approve Access', - 'settings.agentAccess.oauthDeny': 'Deny', - 'settings.agentAccess.oauthExpiredTitle': 'OAuth request expired', - 'settings.agentAccess.oauthGrantsSection': 'Delegated OAuth Grants', - 'settings.agentAccess.oauthNoGrants': 'No delegated OAuth grants yet', - 'settings.agentAccess.oauthGrantRevokeTitle': 'Revoke OAuth Grant', - 'settings.agentAccess.oauthGrantRevokeSuccess': 'OAuth grant revoked', -} - -vi.mock('react-i18next', () => ({ - useTranslation: () => ({ t: (key: string) => translations[key] ?? key }), -})) - -vi.mock('sonner', () => ({ - toast: { success: vi.fn(), error: vi.fn() }, -})) - -vi.mock('@tanstack/react-router', () => ({ - Outlet: () =>
outlet
, - createFileRoute: () => (options: unknown) => options, -})) - -vi.mock('@/components/layout/page-header', () => ({ - PageHeader: () =>
page-header
, -})) - -vi.mock('@/components/layout/page-tabs', () => ({ - PageTabs: ({ items }: { items: Array<{ label: string }> }) =>
{items.map((item) => item.label).join('|')}
, -})) - -vi.mock('@/hooks/use-site-config', () => ({ - useSiteConfig: () => ({ - data: { services: { webdav: { enabled: state.webdavEnabled } } }, - }), -})) - -vi.mock('@/lib/auth-client', () => ({ - useListOrganizations: vi.fn(), - setActive: vi.fn(), -})) - -vi.mock('@/lib/browser-navigation', () => ({ - redirectExternal: vi.fn(), -})) - -vi.mock('@/lib/api', () => ({ - createAgentApiKey: vi.fn(), - getAgentOAuthConsentContext: vi.fn(), - listAgentApiKeys: vi.fn(), - listAgentOAuthGrants: vi.fn(), - revokeAgentApiKey: vi.fn(), - revokeAgentOAuthGrant: vi.fn(), - rotateAgentApiKey: vi.fn(), - submitAgentOAuthConsent: vi.fn(), -})) - -const queryClients: QueryClient[] = [] - -function renderWithQuery(ui: React.ReactNode) { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { retry: false }, - mutations: { retry: false }, - }, - }) - queryClient.setDefaultOptions({ - queries: { retry: false, gcTime: 0 }, - mutations: { retry: false, gcTime: 0 }, - }) - queryClients.push(queryClient) - return render({ui}) -} - -beforeEach(() => { - vi.stubGlobal( - 'ResizeObserver', - class { - observe() {} - unobserve() {} - disconnect() {} - }, - ) - Element.prototype.scrollIntoView = vi.fn() - vi.mocked(useListOrganizations).mockReturnValue({ data: state.orgs } as never) - vi.mocked(listAgentApiKeys).mockImplementation(async (orgId: string) => ({ - items: state.keys.filter((item) => item.orgId === orgId), - total: state.keys.filter((item) => item.orgId === orgId).length, - page: 1, - pageSize: 50, - })) - vi.mocked(listAgentOAuthGrants).mockImplementation(async () => ({ items: state.grants })) - vi.mocked(setActive).mockResolvedValue({ data: null, error: null } as never) - window.history.replaceState(null, '', '/settings/agent-access') -}) - -afterEach(() => { - cleanup() - for (const queryClient of queryClients.splice(0)) queryClient.clear() - vi.clearAllMocks() - vi.unstubAllGlobals() - state.keys = [] - state.grants = [] - state.webdavEnabled = true -}) - -describe('Agent Access settings page', () => { - it('loads the first workspace, fetches its keys, and keeps creation inside a dialog', async () => { - renderWithQuery() - - await waitFor(() => expect(listAgentApiKeys).toHaveBeenCalledWith('org-1')) - expect(await screen.findByText('settings.agentAccess.noKeys')).toBeTruthy() - expect(await screen.findByText('No delegated OAuth grants yet')).toBeTruthy() - expect(screen.queryByLabelText('settings.agentAccess.nameLabel')).toBeNull() - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.create' })) - - expect(screen.getByLabelText('settings.agentAccess.nameLabel')).toBeTruthy() - expect(screen.getByLabelText('settings.agentAccess.expiryLabel')).toBeTruthy() - for (const label of [ - 'Files: read objects', - 'Files: create objects', - 'Files: update objects', - 'Files: delete objects', - 'Shares: read shares', - 'Shares: create shares', - 'Shares: revoke shares', - 'Quota: read workspace quota', - 'Storage usage: read workspace usage', - ]) { - expect(screen.getByText(label)).toBeTruthy() - } - expect(screen.queryByText(/settings\.agentAccess\.scope\..*:/)).toBeNull() - }) - - it('creates a workspace Agent API key and reveals the secret once', async () => { - vi.mocked(createAgentApiKey).mockResolvedValue({ - key: 'zpan_agent_secret', - item: { - id: 'agent-key-1', - name: 'CI key', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-07-29T12:00:00.000Z', - expiresAt: '2026-10-27T23:59:59.000Z', - lastUsedAt: null, - status: 'active', - }, - }) - - renderWithQuery() - await waitFor(() => expect(listAgentApiKeys).toHaveBeenCalledWith('org-1')) - await screen.findByText('settings.agentAccess.noKeys') - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.create' })) - const dialog = await screen.findByRole('dialog', { name: 'settings.agentAccess.createTitle' }) - fireEvent.change(within(dialog).getByLabelText('settings.agentAccess.nameLabel'), { - target: { value: ' CI key ' }, - }) - fireEvent.click(within(dialog).getByRole('button', { name: 'settings.agentAccess.create' })) - - await waitFor(() => - expect(createAgentApiKey).toHaveBeenCalledWith( - 'org-1', - expect.objectContaining({ - name: 'CI key', - scopes: ['objects:read', 'shares:read', 'quota:read', 'storage-usage:read'], - expiresAt: expect.stringMatching(/T23:59:59\.000Z$/), - }), - ), - ) - expect(screen.getByText('zpan_agent_secret')).toBeTruthy() - expect(toast.success).toHaveBeenCalledWith('settings.agentAccess.createSuccess') - }) - - it('rotates and revokes an existing workspace Agent API key', async () => { - state.keys = [ - { - id: 'agent-key-1', - name: 'CI key', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-07-29T12:00:00.000Z', - expiresAt: '2026-10-27T23:59:59.000Z', - lastUsedAt: null, - status: 'active', - }, - ] - vi.mocked(rotateAgentApiKey).mockResolvedValue({ - key: 'zpan_agent_rotated', - item: { - ...state.keys[0], - id: 'agent-key-2', - }, - }) - vi.mocked(revokeAgentApiKey).mockResolvedValue(undefined) - - renderWithQuery() - await screen.findByText('CI key') - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.rotate' })) - - await waitFor(() => expect(rotateAgentApiKey).toHaveBeenCalledWith('org-1', 'agent-key-1')) - const revealedDialog = await screen.findByRole('dialog', { name: 'settings.agentAccess.revealedTitle' }) - expect(within(revealedDialog).getByText('zpan_agent_rotated')).toBeTruthy() - expect(toast.success).toHaveBeenCalledWith('settings.agentAccess.rotateSuccess') - fireEvent.click(within(revealedDialog).getAllByRole('button', { name: 'common.close' })[1]!) - await waitFor(() => expect(screen.queryByRole('dialog', { name: 'settings.agentAccess.revealedTitle' })).toBeNull()) - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.revoke' })) - const revokeDialog = await screen.findByRole('dialog', { name: 'settings.agentAccess.revokeTitle' }) - fireEvent.click(within(revokeDialog).getByRole('button', { name: 'settings.agentAccess.revoke' })) - - await waitFor(() => expect(revokeAgentApiKey).toHaveBeenCalledWith('org-1', 'agent-key-1')) - expect(toast.success).toHaveBeenCalledWith('settings.agentAccess.revokeSuccess') - }) - - it('surfaces rotate and revoke errors and lets the revoke dialog close from its close control', async () => { - state.keys = [ - { - id: 'agent-key-1', - name: 'CI key', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-07-29T12:00:00.000Z', - expiresAt: '2026-10-27T23:59:59.000Z', - lastUsedAt: null, - status: 'active', - }, - ] - vi.mocked(rotateAgentApiKey).mockRejectedValue(new Error('rotate failed')) - vi.mocked(revokeAgentApiKey).mockRejectedValue(new Error('revoke failed')) - - renderWithQuery() - await screen.findByText('CI key') - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.rotate' })) - await waitFor(() => expect(toast.error).toHaveBeenCalledWith('rotate failed')) - - fireEvent.click(screen.getByRole('button', { name: 'settings.agentAccess.revoke' })) - const revokeDialog = await screen.findByRole('dialog', { name: 'settings.agentAccess.revokeTitle' }) - fireEvent.click(within(revokeDialog).getByRole('button', { name: 'settings.agentAccess.revoke' })) - await waitFor(() => expect(toast.error).toHaveBeenCalledWith('revoke failed')) - - fireEvent.click(within(revokeDialog).getByRole('button', { name: 'common.close' })) - await waitFor(() => expect(screen.queryByRole('dialog', { name: 'settings.agentAccess.revokeTitle' })).toBeNull()) - }) - - it('does not offer rotation for expired or revoked keys', async () => { - state.keys = [ - { - id: 'expired-key', - name: 'Expired key', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-01-01T00:00:00.000Z', - expiresAt: '2026-02-01T00:00:00.000Z', - lastUsedAt: null, - status: 'expired', - }, - { - id: 'revoked-key', - name: 'Revoked key', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read'], - createdAt: '2026-01-01T00:00:00.000Z', - expiresAt: '2026-12-01T00:00:00.000Z', - lastUsedAt: null, - status: 'revoked', - }, - ] - - renderWithQuery() - await screen.findByText('Expired key') - expect(screen.getByText('Revoked key')).toBeTruthy() - expect(screen.queryByRole('button', { name: 'settings.agentAccess.rotate' })).toBeNull() - }) - - it('disables credential creation when the workspace management check fails', async () => { - vi.mocked(listAgentApiKeys).mockRejectedValue(new Error('Forbidden')) - - renderWithQuery() - - expect(await screen.findByText('Owner or admin access is required')).toBeTruthy() - expect(screen.getByRole('button', { name: 'settings.agentAccess.create' }).hasAttribute('disabled')).toBe(true) - }) - - it('lists delegated OAuth grants and revokes them server-side', async () => { - state.grants = [ - { - id: 'grant-1', - clientId: 'zpan-agent', - clientName: 'ZPan Agent', - userId: 'user-1', - orgId: 'org-1', - workspaceName: 'Personal', - scopes: ['objects:read', 'shares:create'], - createdAt: '2026-07-29T12:00:00.000Z', - lastUsedAt: '2026-07-29T12:10:00.000Z', - status: 'active', - }, - ] - vi.mocked(revokeAgentOAuthGrant).mockResolvedValue(undefined) - - renderWithQuery() - - expect(await screen.findByText('Delegated OAuth Grants')).toBeTruthy() - expect(await screen.findByText('ZPan Agent')).toBeTruthy() - expect(screen.getByText('Files: read objects')).toBeTruthy() - expect(screen.getByText('Shares: create shares')).toBeTruthy() - - const revokeButtons = screen.getAllByRole('button', { name: 'settings.agentAccess.revoke' }) - fireEvent.click(revokeButtons[revokeButtons.length - 1]!) - const dialog = await screen.findByRole('dialog', { name: 'Revoke OAuth Grant' }) - fireEvent.click(within(dialog).getByRole('button', { name: 'settings.agentAccess.revoke' })) - - await waitFor(() => expect(revokeAgentOAuthGrant).toHaveBeenCalledWith('grant-1')) - expect(toast.success).toHaveBeenCalledWith('OAuth grant revoked') - }) - - it('renders OAuth consent from server context and submits full approval', async () => { - window.history.replaceState( - null, - '', - '/settings/agent-access?client_id=zpan-agent&redirect_uri=http%3A%2F%2F127.0.0.1%3A8484%2Fcallback&response_type=code&scope=openid%20offline_access%20objects%3Aread%20quota%3Aread', - ) - vi.mocked(getAgentOAuthConsentContext).mockResolvedValue({ - clientId: 'zpan-agent', - clientName: 'ZPan Agent', - instanceOrigin: 'https://zpan.example.test', - workspace: { id: 'org-1', name: 'Personal' }, - scopes: ['objects:read', 'quota:read'], - standardScopes: ['openid', 'offline_access'], - redirectUri: 'http://127.0.0.1:8484/callback', - grantLifetime: { accessTokenSeconds: 900, refreshTokenSeconds: 2_592_000 }, - }) - vi.mocked(submitAgentOAuthConsent).mockResolvedValue({ url: 'http://127.0.0.1:8484/callback?code=abc' }) - - renderWithQuery() - - expect(await screen.findByRole('heading', { name: 'Authorize ZPan Agent' })).toBeTruthy() - expect(screen.getByText('https://zpan.example.test')).toBeTruthy() - expect(screen.getByText('http://127.0.0.1:8484/callback')).toBeTruthy() - expect(screen.getByText('Files: read objects')).toBeTruthy() - expect(screen.getByText('Quota: read workspace quota')).toBeTruthy() - - fireEvent.click(screen.getByRole('button', { name: 'Approve Access' })) - - await waitFor(() => - expect(submitAgentOAuthConsent).toHaveBeenCalledWith({ - accept: true, - oauthQuery: window.location.search.slice(1), - }), - ) - expect(redirectExternal).toHaveBeenCalledWith('http://127.0.0.1:8484/callback?code=abc') - }) - - it('switches active workspace before OAuth consent and supports denial', async () => { - window.history.replaceState( - null, - '', - '/settings/agent-access?client_id=zpan-agent&redirect_uri=http%3A%2F%2F127.0.0.1%3A8484%2Fcallback&response_type=code&scope=objects%3Aread', - ) - vi.mocked(getAgentOAuthConsentContext).mockResolvedValue({ - clientId: 'zpan-agent', - clientName: 'ZPan Agent', - instanceOrigin: 'https://zpan.example.test', - workspace: { id: 'org-1', name: 'Personal' }, - scopes: ['objects:read'], - standardScopes: [], - redirectUri: 'http://127.0.0.1:8484/callback', - grantLifetime: { accessTokenSeconds: 900, refreshTokenSeconds: 2_592_000 }, - }) - vi.mocked(submitAgentOAuthConsent).mockResolvedValue({ url: 'http://127.0.0.1:8484/callback?error=access_denied' }) - - renderWithQuery() - await screen.findByRole('heading', { name: 'Authorize ZPan Agent' }) - - fireEvent.click(screen.getByRole('combobox')) - fireEvent.click(await screen.findByRole('option', { name: 'Team Alpha' })) - await waitFor(() => expect(setActive).toHaveBeenCalledWith({ organizationId: 'org-2' })) - - fireEvent.click(screen.getByRole('button', { name: 'Deny' })) - await waitFor(() => - expect(submitAgentOAuthConsent).toHaveBeenCalledWith({ - accept: false, - oauthQuery: window.location.search.slice(1), - }), - ) - expect(redirectExternal).toHaveBeenCalledWith('http://127.0.0.1:8484/callback?error=access_denied') - }) - - it('shows an expired OAuth request state when the consent context fails', async () => { - window.history.replaceState( - null, - '', - '/settings/agent-access?client_id=zpan-agent&redirect_uri=http%3A%2F%2F127.0.0.1%3A8484%2Fcallback', - ) - vi.mocked(getAgentOAuthConsentContext).mockRejectedValue(new Error('expired')) - - renderWithQuery() - - expect(await screen.findByRole('heading', { name: 'OAuth request expired' })).toBeTruthy() - }) -}) - -describe('Settings layout tabs', () => { - it('includes the Agent Access tab alongside existing settings tabs', () => { - renderWithQuery() - - expect(screen.getByText(/settings\.tabApiKeys\|settings\.tabAgentAccess/)).toBeTruthy() - }) - - it('keeps the Agent Access tab when WebDAV is disabled', () => { - state.webdavEnabled = false - - renderWithQuery() - - expect(screen.getByText(/settings\.tabApiKeys\|settings\.tabAgentAccess/)).toBeTruthy() - expect(screen.queryByText(/settings\.tabWebDav/)).toBeNull() - }) -}) diff --git a/src/routes/_authenticated/settings/agent-access.tsx b/src/routes/_authenticated/settings/agent-access.tsx index d3e76d8e..2a93d3b0 100644 --- a/src/routes/_authenticated/settings/agent-access.tsx +++ b/src/routes/_authenticated/settings/agent-access.tsx @@ -1,14 +1,13 @@ -import { type AgentGrantableScope, agentApiKeyShortcutOptions, agentScopeLabels } from '@shared/schemas' +import { oauthResourceScopeLabels } from '@shared/schemas' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { createFileRoute } from '@tanstack/react-router' -import { Check, Copy, KeyRound, Plug, Plus, RotateCw, ShieldAlert, Trash2, X } from 'lucide-react' -import { useEffect, useState } from 'react' +import { Check, Plug, ShieldAlert, Trash2, X } from 'lucide-react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' -import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Checkbox } from '@/components/ui/checkbox' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, @@ -17,27 +16,21 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog' -import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { - type AgentApiKey, type AgentOAuthGrant, - createAgentApiKey, getAgentOAuthConsentContext, - listAgentApiKeys, listAgentOAuthGrants, - revokeAgentApiKey, revokeAgentOAuthGrant, - rotateAgentApiKey, submitAgentOAuthConsent, } from '@/lib/api' import { setActive, useListOrganizations } from '@/lib/auth-client' import { redirectExternal } from '@/lib/browser-navigation' export const Route = createFileRoute('/_authenticated/settings/agent-access')({ - component: AgentAccessSettingsPage, + component: OAuthAccessSettingsPage, }) interface Organization { @@ -45,27 +38,6 @@ interface Organization { name: string } -interface RevealedKey { - name: string - key: string -} - -const allAgentScopes = Object.keys(agentScopeLabels) as AgentGrantableScope[] - -function defaultExpiryDate(): string { - const date = new Date() - date.setDate(date.getDate() + 90) - return date.toISOString().slice(0, 10) -} - -function expiryDateToIso(value: string): string { - return new Date(`${value}T23:59:59.000Z`).toISOString() -} - -function formatDate(value: string | null) { - return value ? new Date(value).toLocaleString() : null -} - function oauthQueryFromLocation(): string { if (typeof window === 'undefined') return '' const query = window.location.search.slice(1) @@ -73,273 +45,17 @@ function oauthQueryFromLocation(): string { return params.has('client_id') && params.has('redirect_uri') ? query : '' } -function CopyButton({ value }: { value: string }) { - const { t } = useTranslation() - return ( - - ) +function formatDate(value: string | null) { + return value ? new Date(value).toLocaleString() : null } -function CreateAgentKeyDialog({ - open, - orgId, - onOpenChange, - onCreated, -}: { - open: boolean - orgId: string - onOpenChange: (open: boolean) => void - onCreated: (key: RevealedKey) => void -}) { - const { t } = useTranslation() - const queryClient = useQueryClient() - const [name, setName] = useState('') - const [expiryDate, setExpiryDate] = useState(defaultExpiryDate) - const [scopes, setScopes] = useState(agentApiKeyShortcutOptions[0]?.scopes ?? []) - - const createMutation = useMutation({ - mutationFn: () => - createAgentApiKey(orgId, { - name: name.trim(), - scopes, - expiresAt: expiryDateToIso(expiryDate), - }), - onSuccess: (result) => { - queryClient.invalidateQueries({ queryKey: ['agent-api-keys', orgId] }) - onCreated({ name: result.item.name, key: result.key }) - setName('') - setExpiryDate(defaultExpiryDate()) - setScopes(agentApiKeyShortcutOptions[0]?.scopes ?? []) - onOpenChange(false) - toast.success(t('settings.agentAccess.createSuccess')) - }, - onError: (err) => toast.error(err.message), - }) - - function toggleScope(scope: AgentGrantableScope, checked: boolean) { - setScopes((current) => (checked ? [...current, scope] : current.filter((item) => item !== scope))) - } - - return ( - - - - {t('settings.agentAccess.createTitle')} - {t('settings.agentAccess.createDescription')} - -
-
- - setName(event.target.value)} - placeholder={t('settings.agentAccess.namePlaceholder')} - /> -
-
- - setExpiryDate(event.target.value)} - /> -
-
- -
- {agentApiKeyShortcutOptions.map((shortcut) => ( - - ))} -
-
-
- {allAgentScopes.map((scope) => { - const checkboxId = `agent-key-scope-${scope}` - return ( -
- toggleScope(scope, !!checked)} - /> - -
- ) - })} -
-
- - - - -
-
- ) -} - -function oneYearDate(): string { - const date = new Date() - date.setFullYear(date.getFullYear() + 1) - return date.toISOString().slice(0, 10) -} - -function RevealedKeyDialog({ revealedKey, onClose }: { revealedKey: RevealedKey | null; onClose: () => void }) { - const { t } = useTranslation() - if (!revealedKey) return null - return ( - !open && onClose()}> - - - {t('settings.agentAccess.revealedTitle')} - {t('settings.agentAccess.revealedWarning')} - -
- -
- {revealedKey.key} - -
-
- - - -
-
- ) -} - -function RevokeAgentKeyDialog({ apiKey, onClose }: { apiKey: AgentApiKey | null; onClose: () => void }) { - const { t } = useTranslation() - const queryClient = useQueryClient() - const revokeMutation = useMutation({ - mutationFn: async () => { - if (!apiKey) return - await revokeAgentApiKey(apiKey.orgId, apiKey.id) - }, - onSuccess: () => { - if (apiKey) queryClient.invalidateQueries({ queryKey: ['agent-api-keys', apiKey.orgId] }) - toast.success(t('settings.agentAccess.revokeSuccess')) - onClose() - }, - onError: (err) => toast.error(err.message), - }) - if (!apiKey) return null - return ( - !open && onClose()}> - - - {t('settings.agentAccess.revokeTitle')} - {t('settings.agentAccess.revokeConfirm', { name: apiKey.name })} - - - - - - - - ) -} - -function RevokeAgentOAuthGrantDialog({ grant, onClose }: { grant: AgentOAuthGrant | null; onClose: () => void }) { - const { t } = useTranslation() - const queryClient = useQueryClient() - const revokeMutation = useMutation({ - mutationFn: async () => { - if (!grant) return - await revokeAgentOAuthGrant(grant.id) - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['agent-oauth-grants'] }) - toast.success(t('settings.agentAccess.oauthGrantRevokeSuccess')) - onClose() - }, - onError: (err) => toast.error(err.message), - }) - if (!grant) return null - return ( - !open && onClose()}> - - - {t('settings.agentAccess.oauthGrantRevokeTitle')} - - {t('settings.agentAccess.oauthGrantRevokeConfirm', { - client: grant.clientName, - workspace: grant.workspaceName ?? grant.orgId, - })} - - - - - - - - - ) -} - -function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: string; organizations: Organization[] }) { +function OAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: string; organizations: Organization[] }) { const { t } = useTranslation() const queryClient = useQueryClient() const [switchingOrgId, setSwitchingOrgId] = useState(null) const [submitError, setSubmitError] = useState(null) const consentQuery = useQuery({ - queryKey: ['agent-oauth-consent', oauthQuery], + queryKey: ['oauth-consent', oauthQuery], queryFn: () => getAgentOAuthConsentContext(oauthQuery), enabled: !!oauthQuery, retry: false, @@ -347,7 +63,8 @@ function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: str const submitMutation = useMutation({ mutationFn: (accept: boolean) => submitAgentOAuthConsent({ accept, oauthQuery }), onSuccess: (result) => redirectExternal(result.url), - onError: (err) => setSubmitError(err instanceof Error ? err.message : t('settings.agentAccess.oauthConsentFailed')), + onError: (error) => + setSubmitError(error instanceof Error ? error.message : t('settings.agentAccess.oauthConsentFailed')), }) async function changeWorkspace(nextOrgId: string) { @@ -356,31 +73,24 @@ function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: str try { const { error } = await setActive({ organizationId: nextOrgId }) if (error) throw error - await queryClient.invalidateQueries({ queryKey: ['agent-oauth-consent', oauthQuery] }) - } catch (err) { - toast.error(err instanceof Error ? err.message : t('settings.agentAccess.oauthWorkspaceFailed')) + await queryClient.invalidateQueries({ queryKey: ['oauth-consent', oauthQuery] }) + } catch (error) { + toast.error(error instanceof Error ? error.message : t('settings.agentAccess.oauthWorkspaceFailed')) } finally { setSwitchingOrgId(null) } } if (consentQuery.isLoading) { - return ( -
-

{t('common.loading')}

-
- ) + return

{t('common.loading')}

} - if (consentQuery.isError || !consentQuery.data) { return ( -
-
- -
-

{t('settings.agentAccess.oauthExpiredTitle')}

-

{t('settings.agentAccess.oauthExpiredDescription')}

-
+
+ +
+

{t('settings.agentAccess.oauthExpiredTitle')}

+

{t('settings.agentAccess.oauthExpiredDescription')}

) @@ -388,7 +98,6 @@ function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: str const context = consentQuery.data const lifetimeDays = Math.round(context.grantLifetime.refreshTokenSeconds / 86400) - return (
@@ -399,7 +108,6 @@ function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: str

{t('settings.agentAccess.oauthConsentTitle')}

{t('settings.agentAccess.oauthConsentDescription')}

-
{t('settings.agentAccess.oauthClient')}
@@ -418,41 +126,33 @@ function AgentOAuthConsentPanel({ oauthQuery, organizations }: { oauthQuery: str
{t('settings.agentAccess.oauthLifetimeValue', { days: lifetimeDays })}
-
- +
- -
-

{t('settings.agentAccess.oauthScopesTitle')}

-
- {context.scopes.map((scope) => ( -
- {t(agentScopeLabels[scope])} -
- ))} -
-

{t('settings.agentAccess.oauthEffects')}

+
+ {context.scopes.map((scope) => ( +
+ {t(oauthResourceScopeLabels[scope])} +
+ ))}
- {submitError ? (

{submitError}

) : null} -
+ + + + + ) +} +function OAuthGrants() { + const { t } = useTranslation() + const [revoking, setRevoking] = useState(null) + const query = useQuery({ queryKey: ['oauth-grants'], queryFn: listAgentOAuthGrants }) + const grants = query.data?.items ?? [] return ( @@ -492,14 +228,14 @@ function AgentOAuthGrantsSection() { {t('settings.agentAccess.oauthGrantsDescription')} - {grantsQuery.isLoading ? ( + {query.isLoading ? (

{t('common.loading')}

- ) : grantsQuery.isError ? ( + ) : query.isError ? (

{t('settings.agentAccess.oauthGrantsError')}

) : grants.length === 0 ? (

{t('settings.agentAccess.oauthNoGrants')}

) : ( - +
{t('settings.agentAccess.oauthClient')} @@ -507,7 +243,6 @@ function AgentOAuthGrantsSection() { {t('settings.agentAccess.colScopes')} {t('settings.agentAccess.colCreated')} {t('settings.agentAccess.colLastUsed')} - {t('settings.agentAccess.colStatus')} {t('settings.agentAccess.colActions')} @@ -517,31 +252,25 @@ function AgentOAuthGrantsSection() { {grant.clientName}{grant.workspaceName ?? grant.orgId} -
+
{grant.scopes.map((scope) => ( - {t(agentScopeLabels[scope])} + {t(oauthResourceScopeLabels[scope])} ))}
- {formatDate(grant.createdAt)} - - {formatDate(grant.lastUsedAt) ?? t('settings.agentAccess.never')} - - - {t('settings.agentAccess.status.active')} - - + {formatDate(grant.createdAt)} + {formatDate(grant.lastUsedAt) ?? t('settings.agentAccess.never')} + @@ -550,162 +279,19 @@ function AgentOAuthGrantsSection() {
)}
- setRevokingGrant(null)} /> + setRevoking(null)} />
) } -export function AgentAccessSettingsPage() { - const { t } = useTranslation() - const queryClient = useQueryClient() - const { data: organizationData } = useListOrganizations() - const organizations = (organizationData ?? []) as Organization[] +export function OAuthAccessSettingsPage() { + const { data } = useListOrganizations() + const organizations = (data ?? []) as Organization[] const oauthQuery = oauthQueryFromLocation() - const [orgId, setOrgId] = useState('') - const [createOpen, setCreateOpen] = useState(false) - const [revealedKey, setRevealedKey] = useState(null) - const [revoking, setRevoking] = useState(null) - - useEffect(() => { - if (!orgId && organizations[0]) setOrgId(organizations[0].id) - }, [orgId, organizations]) - - const keysQuery = useQuery({ - queryKey: ['agent-api-keys', orgId], - queryFn: () => listAgentApiKeys(orgId), - enabled: !!orgId, - }) - - const rows = keysQuery.data?.items ?? [] - - async function rotate(apiKey: AgentApiKey) { - try { - const result = await rotateAgentApiKey(apiKey.orgId, apiKey.id) - queryClient.invalidateQueries({ queryKey: ['agent-api-keys', apiKey.orgId] }) - setRevealedKey({ name: result.item.name, key: result.key }) - toast.success(t('settings.agentAccess.rotateSuccess')) - } catch (err) { - toast.error(err instanceof Error ? err.message : t('common.error')) - } - } - - if (oauthQuery) return - + if (oauthQuery) return return (
- - - {t('settings.agentAccess.section')} - {t('settings.agentAccess.description')} - - - - - -
- - -
- {keysQuery.isLoading ? ( -

{t('common.loading')}

- ) : keysQuery.isError ? ( -

{t('settings.agentAccess.managementRequired')}

- ) : rows.length === 0 ? ( -

{t('settings.agentAccess.noKeys')}

- ) : ( - - - - {t('settings.agentAccess.colName')} - {t('settings.agentAccess.colWorkspace')} - {t('settings.agentAccess.colScopes')} - {t('settings.agentAccess.colCreated')} - {t('settings.agentAccess.colExpires')} - {t('settings.agentAccess.colLastUsed')} - {t('settings.agentAccess.colStatus')} - {t('settings.agentAccess.colActions')} - - - - {rows.map((row) => ( - - -
- - {row.name} -
-
- {row.workspaceName ?? row.orgId} - -
- {row.scopes.map((scope) => ( - - {t(agentScopeLabels[scope])} - - ))} -
-
- {formatDate(row.createdAt)} - {formatDate(row.expiresAt)} - {formatDate(row.lastUsedAt) ?? t('settings.agentAccess.never')} - - - {t(`settings.agentAccess.status.${row.status}`)} - - - - {row.status === 'active' ? ( - - ) : null} - - -
- ))} -
-
- )} -
-
- - setRevealedKey(null)} /> - setRevoking(null)} /> - +
) } diff --git a/workers/bootstrap.cf-test.ts b/workers/bootstrap.cf-test.ts index 6faf3727..764e338b 100644 --- a/workers/bootstrap.cf-test.ts +++ b/workers/bootstrap.cf-test.ts @@ -27,6 +27,23 @@ describe('[CF] Worker fetch handler', () => { expect(await res.json()).toEqual({ status: 'ok' }) }) + it('publishes the Arazzo workflow description from the Worker runtime', async () => { + const root = await worker.fetch(new Request('https://pan.example.com/api'), testEnv) + const workflow = await worker.fetch(new Request('https://pan.example.com/api/workflows.arazzo.json'), testEnv) + + expect(root.status).toBe(200) + expect(root.headers.get('Link')).toContain( + '; rel="describedby"; type="application/vnd.oai.workflows+json"', + ) + expect(workflow.status).toBe(200) + expect(workflow.headers.get('Content-Type')).toBe('application/vnd.oai.workflows+json; version=1.1.0') + expect(await workflow.json()).toMatchObject({ + arazzo: '1.1.0', + $self: 'https://pan.example.com/api/workflows.arazzo.json', + sourceDescriptions: [{ url: './openapi.json', type: 'openapi' }], + }) + }) + it('splits and trims TRUSTED_ORIGINS when provided', async () => { const request = new Request('http://localhost/api/health') const envWithOrigins = { ...testEnv, TRUSTED_ORIGINS: ' https://a.example.com , https://b.example.com ' } diff --git a/workers/bootstrap.ts b/workers/bootstrap.ts index 3db62a86..f006b611 100644 --- a/workers/bootstrap.ts +++ b/workers/bootstrap.ts @@ -44,6 +44,7 @@ interface WorkerRuntime { cache: CacheService authBySlot: Map appBySlot: Map> + appInitBySlot: Map>> } let cachedRuntime: WorkerRuntime | undefined @@ -71,6 +72,7 @@ function runtimeFor(env: Env): WorkerRuntime { deps: createDeps(platform, { cache }), authBySlot: new Map(), appBySlot: new Map(), + appInitBySlot: new Map(), } return cachedRuntime } @@ -93,11 +95,23 @@ async function appForRequest( const cachedAuth = runtime.authBySlot.get(slot) if (cachedApp && cachedAuth) return cachedApp - const auth = await createAuth(runtime.platform, env.BETTER_AUTH_SECRET, baseURL, trustedOrigins, waitUntil) - const app = createApp(runtime.platform, auth, runtime.deps) - runtime.authBySlot.set(slot, auth) - runtime.appBySlot.set(slot, app) - return app + const pendingApp = runtime.appInitBySlot.get(slot) + if (pendingApp) return pendingApp + + const appPromise = createAuth(runtime.platform, env.BETTER_AUTH_SECRET, baseURL, trustedOrigins, waitUntil).then( + (auth) => { + const app = createApp(runtime.platform, auth, runtime.deps) + runtime.authBySlot.set(slot, auth) + runtime.appBySlot.set(slot, app) + return app + }, + ) + runtime.appInitBySlot.set(slot, appPromise) + try { + return await appPromise + } finally { + runtime.appInitBySlot.delete(slot) + } } export default { diff --git a/wrangler.toml b/wrangler.toml index 21869786..578f7352 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -6,7 +6,7 @@ compatibility_flags = ["nodejs_compat", "global_fetch_strictly_public"] [assets] binding = "ASSETS" not_found_handling = "single-page-application" -run_worker_first = ["/api/*", "/.well-known/*", "/dav", "/dav/*", "/ih/*", "/r/*", "/s/*"] +run_worker_first = ["/api", "/api/*", "/.well-known/*", "/dav", "/dav/*", "/ih/*", "/r/*", "/s/*"] [[d1_databases]] binding = "DB"