Compare commits

..

11 Commits

Author SHA1 Message Date
github-actions[bot]
a6a378c5a6 release(services/auth): 0.43.1 (#3682)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-11-11 12:58:31 +01:00
David Barroso
a3a3cf205d fix(auth): return meaningful error if the provider's account is already linked (#3680) 2025-11-11 12:56:59 +01:00
dependabot[bot]
3fd2e63db3 chore(ci): bump Codium-ai/pr-agent from 0.30 to 0.31 (#3676)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-10 08:56:17 +01:00
github-actions[bot]
f5956f1b2e release(cli): 1.34.5 (#3655)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-11-06 15:34:31 +01:00
David Barroso
f3b397b0d8 chore(cli): bump nhost/dashboard to 2.41.0 (#3669)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-11-06 13:42:32 +01:00
David Barroso
b7940087ee chore(cli): udpate certs and schema (#3675) 2025-11-06 13:04:09 +01:00
github-actions[bot]
3dae655858 release(services/storage): 0.9.1 (#3673)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-11-06 11:42:33 +01:00
David Barroso
2aa269734b fix(storage): format date-time headers with RFC2822 (#3672) 2025-11-06 11:40:11 +01:00
David Barroso
bc91836f83 chore(ci): replace the correct string on .github/workflows/dashboard_wf_release.yaml (#3670)
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-05 10:26:16 +01:00
github-actions[bot]
6d8b243571 release(dashboard): 2.41.0 (#3647)
Co-authored-by: dbarrosop <dbarrosop@users.noreply.github.com>
2025-11-05 08:26:31 +01:00
David Barroso
c9967b1a6d feat(dashboard): get github repositories from github itself (#3640)
Co-authored-by: David BM <correodelnino@gmail.com>
2025-11-04 16:46:01 +01:00
68 changed files with 1103 additions and 720 deletions

View File

@@ -88,7 +88,7 @@ jobs:
- name: Bump version in source code - name: Bump version in source code
run: | run: |
find cli -type f -exec sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' {} + find cli -type f -exec sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' {} +
sed -i 's/"nhost\/dashboard:[^"]*"/"nhost\/dashboard:${{ inputs.VERSION }}"/g' docs/reference/cli/commands.mdx sed -i 's/nhost\/dashboard:[^)]*/nhost\/dashboard:${{ inputs.VERSION }}/g' docs/reference/cli/commands.mdx
- name: "Create Pull Request" - name: "Create Pull Request"
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7

View File

@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: PR Agent action step - name: PR Agent action step
id: pragent id: pragent
uses: Codium-ai/pr-agent@v0.30 uses: Codium-ai/pr-agent@v0.31
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_KEY: ${{ secrets.OPENAI_API_KEY }}

View File

@@ -1,3 +1,11 @@
## [cli@1.34.5] - 2025-11-06
### ⚙️ Miscellaneous Tasks
- *(nixops)* Bump go to 1.25.3 and nixpkgs due to CVEs (#3652)
- *(cli)* Udpate certs and schema (#3675)
- *(cli)* Bump nhost/dashboard to 2.41.0 (#3669)
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.

View File

@@ -56,7 +56,7 @@ func CommandCloud() *cli.Command {
&cli.StringFlag{ //nolint:exhaustruct &cli.StringFlag{ //nolint:exhaustruct
Name: flagDashboardVersion, Name: flagDashboardVersion,
Usage: "Dashboard version to use", Usage: "Dashboard version to use",
Value: "nhost/dashboard:2.40.0", Value: "nhost/dashboard:2.41.0",
Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"), Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"),
}, },
&cli.StringFlag{ //nolint:exhaustruct &cli.StringFlag{ //nolint:exhaustruct

View File

@@ -111,7 +111,7 @@ func CommandUp() *cli.Command { //nolint:funlen
&cli.StringFlag{ //nolint:exhaustruct &cli.StringFlag{ //nolint:exhaustruct
Name: flagDashboardVersion, Name: flagDashboardVersion,
Usage: "Dashboard version to use", Usage: "Dashboard version to use",
Value: "nhost/dashboard:2.40.0", Value: "nhost/dashboard:2.41.0",
Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"), Sources: cli.EnvVars("NHOST_DASHBOARD_VERSION"),
}, },
&cli.StringFlag{ //nolint:exhaustruct &cli.StringFlag{ //nolint:exhaustruct

View File

@@ -53,6 +53,7 @@ func expectedAuth() *Service {
"AUTH_PROVIDER_APPLE_ENABLED": "true", "AUTH_PROVIDER_APPLE_ENABLED": "true",
"AUTH_PROVIDER_APPLE_KEY_ID": "appleKeyId", "AUTH_PROVIDER_APPLE_KEY_ID": "appleKeyId",
"AUTH_PROVIDER_APPLE_PRIVATE_KEY": "applePrivateKey", "AUTH_PROVIDER_APPLE_PRIVATE_KEY": "applePrivateKey",
"AUTH_PROVIDER_APPLE_SCOPE": "",
"AUTH_PROVIDER_APPLE_TEAM_ID": "appleTeamId", "AUTH_PROVIDER_APPLE_TEAM_ID": "appleTeamId",
"AUTH_PROVIDER_AZUREAD_CLIENT_ID": "azureadClientId", "AUTH_PROVIDER_AZUREAD_CLIENT_ID": "azureadClientId",
"AUTH_PROVIDER_AZUREAD_CLIENT_SECRET": "azureadClientSecret", "AUTH_PROVIDER_AZUREAD_CLIENT_SECRET": "azureadClientSecret",
@@ -75,9 +76,12 @@ func expectedAuth() *Service {
"AUTH_PROVIDER_FACEBOOK_CLIENT_SECRET": "facebookClientSecret", "AUTH_PROVIDER_FACEBOOK_CLIENT_SECRET": "facebookClientSecret",
"AUTH_PROVIDER_FACEBOOK_ENABLED": "true", "AUTH_PROVIDER_FACEBOOK_ENABLED": "true",
"AUTH_PROVIDER_FACEBOOK_SCOPE": "email", "AUTH_PROVIDER_FACEBOOK_SCOPE": "email",
"AUTH_PROVIDER_GITHUB_AUDIENCE": "audience",
"AUTH_PROVIDER_GITHUB_CLIENT_ID": "githubClientId", "AUTH_PROVIDER_GITHUB_CLIENT_ID": "githubClientId",
"AUTH_PROVIDER_GITHUB_CLIENT_SECRET": "githubClientSecret", "AUTH_PROVIDER_GITHUB_CLIENT_SECRET": "githubClientSecret",
"AUTH_PROVIDER_GITHUB_ENABLED": "true", "AUTH_PROVIDER_GITHUB_ENABLED": "true",
"AUTH_PROVIDER_GITHUB_SCOPE": "user:email",
"AUTH_PROVIDER_GITLAB_AUDIENCE": "audience",
"AUTH_PROVIDER_GITLAB_CLIENT_ID": "gitlabClientId", "AUTH_PROVIDER_GITLAB_CLIENT_ID": "gitlabClientId",
"AUTH_PROVIDER_GITLAB_CLIENT_SECRET": "gitlabClientSecret", "AUTH_PROVIDER_GITLAB_CLIENT_SECRET": "gitlabClientSecret",
"AUTH_PROVIDER_GITLAB_ENABLED": "true", "AUTH_PROVIDER_GITLAB_ENABLED": "true",
@@ -97,6 +101,7 @@ func expectedAuth() *Service {
"AUTH_PROVIDER_SPOTIFY_CLIENT_SECRET": "spotifyClientSecret", "AUTH_PROVIDER_SPOTIFY_CLIENT_SECRET": "spotifyClientSecret",
"AUTH_PROVIDER_SPOTIFY_ENABLED": "true", "AUTH_PROVIDER_SPOTIFY_ENABLED": "true",
"AUTH_PROVIDER_SPOTIFY_SCOPE": "user-read-email", "AUTH_PROVIDER_SPOTIFY_SCOPE": "user-read-email",
"AUTH_PROVIDER_STRAVA_AUDIENCE": "audience",
"AUTH_PROVIDER_STRAVA_CLIENT_ID": "stravaClientId", "AUTH_PROVIDER_STRAVA_CLIENT_ID": "stravaClientId",
"AUTH_PROVIDER_STRAVA_CLIENT_SECRET": "stravaClientSecret", "AUTH_PROVIDER_STRAVA_CLIENT_SECRET": "stravaClientSecret",
"AUTH_PROVIDER_STRAVA_ENABLED": "true", "AUTH_PROVIDER_STRAVA_ENABLED": "true",

View File

@@ -223,7 +223,7 @@ import (
// Releases: // Releases:
// //
// https://github.com/nhost/hasura-storage/releases // https://github.com/nhost/hasura-storage/releases
version: string | *"0.8.2" version: string | *"0.9.1"
// Networking (custom domains at the moment) are not allowed as we need to do further // Networking (custom domains at the moment) are not allowed as we need to do further
// configurations in the CDN. We will enable it again in the future. // configurations in the CDN. We will enable it again in the future.
@@ -311,7 +311,7 @@ import (
// Releases: // Releases:
// //
// https://github.com/nhost/hasura-auth/releases // https://github.com/nhost/hasura-auth/releases
version: string | *"0.42.4" version: string | *"0.43.0"
// Resources for the service // Resources for the service
resources?: #Resources resources?: #Resources

View File

@@ -1,27 +1,27 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIERDCCA8mgAwIBAgISBmRex3kpZ4Mz1/1kq05iqja/MAoGCCqGSM49BAMDMDIx MIIERTCCA8ugAwIBAgISBWD/E+b14mP5jv4DGWRVYv8fMAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
ODAeFw0yNTEwMDIxMDUxNDBaFw0yNTEyMzExMDUxMzlaMB8xHTAbBgNVBAMTFGxv ODAeFw0yNTExMDYxMDUxMTBaFw0yNjAyMDQxMDUxMDlaMB8xHTAbBgNVBAMTFGxv
Y2FsLmF1dGgubmhvc3QucnVuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2cVM Y2FsLmF1dGgubmhvc3QucnVuMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOah5
ojf8iXZGLneNfnke5LMJIxyTEeGbNOfCv4SOR4K/N4OkpvkUVbH2bRvX99uE9jaK ZLuUQp3pdMBxBWnT6E6/amW9LerKKEEdy3Nc8iAwG9LlnPH0z3m7a9wgEhpFEdlL
515Y48PzPA/4+W1zTKOCAtAwggLMMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAU Rr+qO+NhSRnv6+UF5KOCAtIwggLOMA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUEFjAU
BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUQqan BggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUGyb1
raZoU5klAxsgkEVEMIkxmMQwHwYDVR0jBBgwFoAUjw0TovYuftFQbDMYOF1ZjiNy TVK/0vf3uHO4x3R094aG2rEwHwYDVR0jBBgwFoAUjw0TovYuftFQbDMYOF1ZjiNy
kcowMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRwOi8vZTguaS5sZW5j kcowMgYIKwYBBQUHAQEEJjAkMCIGCCsGAQUFBzAChhZodHRwOi8vZTguaS5sZW5j
ci5vcmcvMIHOBgNVHREEgcYwgcOCFGxvY2FsLmF1dGgubmhvc3QucnVughlsb2Nh ci5vcmcvMIHOBgNVHREEgcYwgcOCFGxvY2FsLmF1dGgubmhvc3QucnVughlsb2Nh
bC5kYXNoYm9hcmQubmhvc3QucnVughJsb2NhbC5kYi5uaG9zdC5ydW6CGWxvY2Fs bC5kYXNoYm9hcmQubmhvc3QucnVughJsb2NhbC5kYi5uaG9zdC5ydW6CGWxvY2Fs
LmZ1bmN0aW9ucy5uaG9zdC5ydW6CF2xvY2FsLmdyYXBocWwubmhvc3QucnVughZs LmZ1bmN0aW9ucy5uaG9zdC5ydW6CF2xvY2FsLmdyYXBocWwubmhvc3QucnVughZs
b2NhbC5oYXN1cmEubmhvc3QucnVughdsb2NhbC5tYWlsaG9nLm5ob3N0LnJ1boIX b2NhbC5oYXN1cmEubmhvc3QucnVughdsb2NhbC5tYWlsaG9nLm5ob3N0LnJ1boIX
bG9jYWwuc3RvcmFnZS5uaG9zdC5ydW4wEwYDVR0gBAwwCjAIBgZngQwBAgEwLQYD bG9jYWwuc3RvcmFnZS5uaG9zdC5ydW4wEwYDVR0gBAwwCjAIBgZngQwBAgEwLQYD
VR0fBCYwJDAioCCgHoYcaHR0cDovL2U4LmMubGVuY3Iub3JnLzY0LmNybDCCAQIG VR0fBCYwJDAioCCgHoYcaHR0cDovL2U4LmMubGVuY3Iub3JnLzMyLmNybDCCAQQG
CisGAQQB1nkCBAIEgfMEgfAA7gB1AO08S9boBsKkogBX28sk4jgB31Ev7cSGxXAP CisGAQQB1nkCBAIEgfUEgfIA8AB2ABmG1Mcoqm/+ugNveCpNAZGqzi1yMQ+uzl1w
IN23Pj/gAAABmaTCI4YAAAQDAEYwRAIgXLRFL1EAXfvN6kd5m6udqlxfz4+5B6rq QS0lTMfUAAABmlkAQokAAAQDAEcwRQIgWDtSxJfM2xcjvScVHOkn8bipzBhNhTnm
Cdhp/ZwDAZ8CIFYvalTkl5NEBEMD3vpPvrj8s1Yy2xsropEh/AvpavvLAHUAGYbU B89TDh1/4XUCIQDe08W33PCx2D+akCdW9U9mZKQpIW6deLZSI3ZWpSNKMAB2AA5X
xyiqb/66A294Kk0BkarOLXIxD67OXXBBLSVMx9QAAAGZpMIjhwAABAMARjBEAiBk lLzzrqk+MxssmQez95Dfm8I9cTIl3SGpJaxhxU4hAAABmlkAQn8AAAQDAEcwRQIg
H1vqU9HNuBcf4UYL/xZ42BeUAARHStiFaIZtnR1kEgIgbIJ0CGqIpxmWuwCunl9p KnojmNTpNk1OFTQI0EnlPa2bpwqmUgmUCLeqE6SWfgoCIQCrhZbxYPHbGLF/HpRq
ar+rGLdQrCk9BZXq/VjPPAAwCgYIKoZIzj0EAwMDaQAwZgIxAKvk5a2zQsv7JLNj vCTcOh24SRCuxlkqtaowbbfmKjAKBggqhkjOPQQDAwNoADBlAjEArstFIC+KAsfQ
NO1ly+DI8qiy5nf4HQrOrHOjtmx5RUu0HSO9P0J0u069qAqXMgIxAMLdME9JUo2c nLhtqsaNzkhftN5adDyr2CoE0WUPF1sLDi+xDnDO+JgIPL0YKAFNAjATJ4omhpc+
TJo3pwWv5MRyg/MkOJ4ImKdDJXfIZNkEIUyP3vwTqImvZe07gJDsYg== I6/kWcef2RyO9YCGQQE9pdez5CYKb9o8YAntDSHM3b5nXXj3AX/USdQ=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP MIIEVjCCAj6gAwIBAgIQY5WTY8JOcIJxWRi/w9ftVjANBgkqhkiG9w0BAQsFADBP

View File

@@ -1,5 +1,5 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfJZOkvawA0vBMw9W MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgInXN4JRnXNTjx7rM
ph8i1Z+SJQrFscPbqSYpxngzEDahRANCAATZxUyiN/yJdkYud41+eR7kswkjHJMR avurZrN1EV1iebQeNUlMlFp7VJ+hRANCAAQ5qHlku5RCnel0wHEFadPoTr9qZb0t
4Zs058K/hI5Hgr83g6Sm+RRVsfZtG9f324T2NornXljjw/M8D/j5bXNM 6sooQR3Lc1zyIDAb0uWc8fTPebtr3CASGkUR2UtGv6o742FJGe/r5QXk
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@@ -1,52 +1,52 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEWDCCA96gAwIBAgISBbvrSsjDQm4zevwwjxFGmeTMMAoGCCqGSM49BAMDMDIx MIIEVzCCA92gAwIBAgISBm54VdkoqD8s8efq7ceHaTihMAoGCCqGSM49BAMDMDIx
CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF
NzAeFw0yNTEwMDIxMDUyNTdaFw0yNTEyMzExMDUyNTZaMCExHzAdBgNVBAMMFiou ODAeFw0yNTExMDYxMDUyMjBaFw0yNjAyMDQxMDUyMTlaMCExHzAdBgNVBAMMFiou
YXV0aC5sb2NhbC5uaG9zdC5ydW4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATG YXV0aC5sb2NhbC5uaG9zdC5ydW4wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASI
x0o7t0pSrOoFc+pljtqJVxgaSW+w9D9C2WdysMeSKKOU+0MzaM4ynLUhETOpBs8E rTkZOM4ip42DCyDADXGc7oV3+OkimyTM3st2RIZWG28rFRwH0LebJV2cduq1Hdtl
612mdcoeak+G1Emj6UVwo4IC4zCCAt8wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQW VxIEr+RhvyIL7gllueXUo4IC4jCCAt4wDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQW
MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQ+ MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTw
lVsLiXSRLAECs9OgkCEBS7jMmzAfBgNVHSMEGDAWgBSuSJ7chx1EoG/aouVgdAR4 bM86O381+aljU3oTUvwhZ90PCDAfBgNVHSMEGDAWgBSPDROi9i5+0VBsMxg4XVmO
wpwAgDAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly9lNy5pLmxl I3KRyjAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly9lOC5pLmxl
bmNyLm9yZy8wgd4GA1UdEQSB1jCB04IWKi5hdXRoLmxvY2FsLm5ob3N0LnJ1boIb bmNyLm9yZy8wgd4GA1UdEQSB1jCB04IWKi5hdXRoLmxvY2FsLm5ob3N0LnJ1boIb
Ki5kYXNoYm9hcmQubG9jYWwubmhvc3QucnVughQqLmRiLmxvY2FsLm5ob3N0LnJ1 Ki5kYXNoYm9hcmQubG9jYWwubmhvc3QucnVughQqLmRiLmxvY2FsLm5ob3N0LnJ1
boIbKi5mdW5jdGlvbnMubG9jYWwubmhvc3QucnVughkqLmdyYXBocWwubG9jYWwu boIbKi5mdW5jdGlvbnMubG9jYWwubmhvc3QucnVughkqLmdyYXBocWwubG9jYWwu
bmhvc3QucnVughgqLmhhc3VyYS5sb2NhbC5uaG9zdC5ydW6CGSoubWFpbGhvZy5s bmhvc3QucnVughgqLmhhc3VyYS5sb2NhbC5uaG9zdC5ydW6CGSoubWFpbGhvZy5s
b2NhbC5uaG9zdC5ydW6CGSouc3RvcmFnZS5sb2NhbC5uaG9zdC5ydW4wEwYDVR0g b2NhbC5uaG9zdC5ydW6CGSouc3RvcmFnZS5sb2NhbC5uaG9zdC5ydW4wEwYDVR0g
BAwwCjAIBgZngQwBAgEwLQYDVR0fBCYwJDAioCCgHoYcaHR0cDovL2U3LmMubGVu BAwwCjAIBgZngQwBAgEwLQYDVR0fBCYwJDAioCCgHoYcaHR0cDovL2U4LmMubGVu
Y3Iub3JnLzc3LmNybDCCAQUGCisGAQQB1nkCBAIEgfYEgfMA8QB2AN3cyjSV1+EW Y3Iub3JnLzM0LmNybDCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AEmcm2neHXzs
BeeVMvrHn/g9HFDf2wA6FBJ2Ciysu8gqAAABmaTDUHkAAAQDAEcwRQIgWudJ8XKA /DbezYdkprhbrwqHgBnRVVL76esp3fjDAAABmlkBVgkAAAQDAEcwRQIhANH6Ml3u
BT5jq5Tl0xQLNb953pBi22Tb0TIWk+RSqHgCIQDsTrLVMFaQTV7EFCY1tFhi5qae IM4nAzwAIjIjBjn8EWbn1ZHfgwO+rlSo5rzpAiATPKE8Mx5LK1IayG5VCK1eCDyc
SCpEwwdFcnom/nz6EAB3AO08S9boBsKkogBX28sk4jgB31Ev7cSGxXAPIN23Pj/g rzt1HNbP9WSrpuHx+gB2ABmG1Mcoqm/+ugNveCpNAZGqzi1yMQ+uzl1wQS0lTMfU
AAABmaTDWAsAAAQDAEgwRgIhALxIgIiutEwgNcGw7/cAdjFqUugct4HlZezIOLLP AAABmlkBVgcAAAQDAEcwRQIgIT/DhsIj9Aw7qf/2lknJCr907dEqC3/+QN3zlcOj
rg69AiEA8YCaK41rJDYztEKUIJEq2J2ktSqGYcl9gNKC+SiR4acwCgYIKoZIzj0E iKoCIQCTguinYjJPZwU2dblaRQ2q7MTCMT2ZENExltxwYG3GzjAKBggqhkjOPQQD
AwMDaAAwZQIwVG9yOiMRfKFFyFj1R8X/5U67QD84OhZ0oM0SZsVhezLedG5b8eFf AwNoADBlAjEA5nFoNrLyeC079YpRvdah/HZIA/lUBh+LOo/NcEBD3aTGs2z8hU8z
/cWraREi8xbFAjEA/6RXweGzl08F7EtqBDoiqitScI2rbwGtP6s/evL0zXTABZD2 H4vMy3OnfQ9TAjBxigm7zE5/3CAcGoSOr/P0TL52nh+lO4SUVxcbKgYB8A2yo6o/
ih7AGxjtg80IqIRe kUkG7PiRB0uUpNw=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
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
+VUwFj9tmWxyR/M= u1igv3OefnWjSQ==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@@ -1,5 +1,5 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgrfNUSjLV/7j7LSBf MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgcrhROXQT85e+S8h8
zL/hvGEuv+uvf3/aimqjecO7vcShRANCAATGx0o7t0pSrOoFc+pljtqJVxgaSW+w RE3Z7TPo3+WA2RmzJsXJbXkbi5qhRANCAASIrTkZOM4ip42DCyDADXGc7oV3+Oki
9D9C2WdysMeSKKOU+0MzaM4ynLUhETOpBs8E612mdcoeak+G1Emj6UVw myTM3st2RIZWG28rFRwH0LebJV2cduq1HdtlVxIEr+RhvyIL7gllueXU
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

View File

@@ -1,3 +1,15 @@
## [@nhost/dashboard@2.41.0] - 2025-11-04
### 🚀 Features
- *(auth)* Added endpoints to retrieve and refresh oauth2 providers' tokens (#3614)
- *(dashboard)* Get github repositories from github itself (#3640)
### 🐛 Bug Fixes
- *(dashboard)* Update SQL editor to use correct hasura migrations API URL (#3645)
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.

View File

@@ -15,7 +15,7 @@ function getCspHeader() {
return [ return [
"default-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run", "default-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run",
"script-src 'self' 'unsafe-eval' cdn.segment.com js.stripe.com challenges.cloudflare.com googletagmanager.com", "script-src 'self' 'unsafe-eval' cdn.segment.com js.stripe.com challenges.cloudflare.com googletagmanager.com",
"connect-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run discord.com api.segment.io api.segment.com cdn.segment.com nhost.zendesk.com", "connect-src 'self' *.nhost.run wss://*.nhost.run nhost.run wss://nhost.run discord.com api.segment.io api.segment.com cdn.segment.com nhost.zendesk.com api.github.com",
"style-src 'self' 'unsafe-inline'", "style-src 'self' 'unsafe-inline'",
"img-src 'self' blob: data: github.com avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run", "img-src 'self' blob: data: github.com avatars.githubusercontent.com s.gravatar.com *.nhost.run nhost.run",
"font-src 'self' data:", "font-src 'self' data:",

View File

@@ -23,7 +23,7 @@ export default function SocialProvidersSettings() {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
return nhost.auth.signInProviderURL('github', { return nhost.auth.signInProviderURL('github', {
connect: token, connect: token,
redirectTo: `${window.location.origin}/account`, redirectTo: `${window.location.origin}/account?signinProvider=github`,
}); });
} }
return ''; return '';

View File

@@ -3,18 +3,21 @@ import {
useGithubAuthentication, useGithubAuthentication,
type UseGithubAuthenticationHookProps, type UseGithubAuthenticationHookProps,
} from '@/features/auth/AuthProviders/Github/hooks/useGithubAuthentication'; } from '@/features/auth/AuthProviders/Github/hooks/useGithubAuthentication';
import { cn } from '@/lib/utils';
import { SiGithub } from '@icons-pack/react-simple-icons'; import { SiGithub } from '@icons-pack/react-simple-icons';
interface Props extends UseGithubAuthenticationHookProps { interface Props extends UseGithubAuthenticationHookProps {
buttonText?: string; buttonText?: string;
withAnonId?: boolean; withAnonId?: boolean;
redirectTo?: string; redirectTo?: string;
className?: string;
} }
function GithubAuthButton({ function GithubAuthButton({
buttonText = 'Continue with GitHub', buttonText = 'Continue with GitHub',
withAnonId = false, withAnonId = false,
redirectTo, redirectTo,
className,
}: Props) { }: Props) {
const { mutate: signInWithGithub, isLoading } = useGithubAuthentication({ const { mutate: signInWithGithub, isLoading } = useGithubAuthentication({
withAnonId, withAnonId,
@@ -22,7 +25,10 @@ function GithubAuthButton({
}); });
return ( return (
<Button <Button
className="gap-2 !bg-white text-sm+ !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60" className={cn(
'gap-2 !bg-white text-sm+ !text-black hover:ring-2 hover:ring-white hover:ring-opacity-50 disabled:!text-black disabled:!text-opacity-60',
className,
)}
disabled={isLoading} disabled={isLoading}
loading={isLoading} loading={isLoading}
onClick={() => signInWithGithub()} onClick={() => signInWithGithub()}

View File

@@ -30,8 +30,8 @@ function useGithubAuthentication({
}; };
} }
const redirectURl = nhost.auth.signInProviderURL('github', options); const redirectURL = nhost.auth.signInProviderURL('github', options);
window.location.href = redirectURl; window.location.href = redirectURL;
}, },
{ {
onError: () => { onError: () => {

View File

@@ -2,7 +2,7 @@ import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/component
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName'; import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
function SignInWithGithub() { function SignInWithGithub() {
const redirectTo = useHostName(); const redirectTo = `${useHostName()}?signinProvider=github`;
return ( return (
<GithubAuthButton <GithubAuthButton
redirectTo={redirectTo} redirectTo={redirectTo}

View File

@@ -1,8 +1,11 @@
import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton'; import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton';
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
function SignUpWithGithub() { function SignUpWithGithub() {
const redirectTo = `${useHostName()}?signinProvider=github`;
return ( return (
<GithubAuthButton <GithubAuthButton
redirectTo={redirectTo}
buttonText="Sign Up with GitHub" buttonText="Sign Up with GitHub"
errorText="An error occurred while trying to sign up using GitHub. Please try again." errorText="An error occurred while trying to sign up using GitHub. Please try again."
/> />

View File

@@ -1,3 +1,4 @@
import { ErrorMessage } from '@/components/presentational/ErrorMessage';
import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary'; import { RetryableErrorBoundary } from '@/components/presentational/RetryableErrorBoundary';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator'; import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { Avatar } from '@/components/ui/v2/Avatar'; import { Avatar } from '@/components/ui/v2/Avatar';
@@ -11,14 +12,33 @@ import { Link } from '@/components/ui/v2/Link';
import { List } from '@/components/ui/v2/List'; import { List } from '@/components/ui/v2/List';
import { ListItem } from '@/components/ui/v2/ListItem'; import { ListItem } from '@/components/ui/v2/ListItem';
import { Text } from '@/components/ui/v2/Text'; import { Text } from '@/components/ui/v2/Text';
import { GithubAuthButton } from '@/features/auth/AuthProviders/Github/components/GithubAuthButton';
import { useHostName } from '@/features/orgs/projects/common/hooks/useHostName';
import { EditRepositorySettings } from '@/features/orgs/projects/git/common/components/EditRepositorySettings'; import { EditRepositorySettings } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
import { useGetGithubRepositoriesQuery } from '@/generated/graphql'; import {
getGitHubToken,
saveGitHubToken,
} from '@/features/orgs/projects/git/common/utils';
import { useCurrentOrg } from '@/features/orgs/projects/hooks/useCurrentOrg';
import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useGetAuthUserProvidersQuery } from '@/generated/graphql';
import { useAccessToken } from '@/hooks/useAccessToken';
import { GitHubAPIError, listGitHubInstallationRepos } from '@/lib/github';
import { isEmptyValue } from '@/lib/utils';
import { getToastStyleProps } from '@/utils/constants/settings';
import { nhost } from '@/utils/nhost';
import { Divider } from '@mui/material'; import { Divider } from '@mui/material';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import NavLink from 'next/link';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import { Fragment, useEffect, useMemo, useState } from 'react'; import { Fragment, useEffect, useMemo, useState } from 'react';
import toast from 'react-hot-toast';
export type ConnectGitHubModalState = 'CONNECTING' | 'EDITING'; export type ConnectGitHubModalState =
| 'CONNECTING'
| 'EDITING'
| 'EXPIRED_GITHUB_SESSION'
| 'GITHUB_CONNECTION_REQUIRED';
export interface ConnectGitHubModalProps { export interface ConnectGitHubModalProps {
/** /**
@@ -28,18 +48,153 @@ export interface ConnectGitHubModalProps {
close?: VoidFunction; close?: VoidFunction;
} }
interface GitHubData {
githubAppInstallations: Array<{
id: number;
accountLogin?: string;
accountAvatarUrl?: string;
}>;
githubRepositories: Array<{
id: number;
node_id: string;
name: string;
fullName: string;
githubAppInstallation: {
accountLogin?: string;
accountAvatarUrl?: string;
};
}>;
}
export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) { export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
const [filter, setFilter] = useState(''); const [filter, setFilter] = useState('');
const [ConnectGitHubModalState, setConnectGitHubModalState] = const [ConnectGitHubModalState, setConnectGitHubModalState] =
useState<ConnectGitHubModalState>('CONNECTING'); useState<ConnectGitHubModalState>('CONNECTING');
const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null); const [selectedRepoId, setSelectedRepoId] = useState<string | null>(null);
const [githubData, setGithubData] = useState<GitHubData | null>(null);
const [loading, setLoading] = useState(true);
const { project, loading: loadingProject } = useProject();
const { org, loading: loadingOrg } = useCurrentOrg();
const hostname = useHostName();
const token = useAccessToken();
const {
data,
loading: loadingGithubConnected,
error: errorGithubConnected,
} = useGetAuthUserProvidersQuery();
const { data, loading, error, startPolling } = const githubProvider = data?.authUserProviders?.find(
useGetGithubRepositoriesQuery(); (item) => item.providerId === 'github',
);
const getGitHubConnectUrl = () => {
if (typeof window !== 'undefined') {
return nhost.auth.signInProviderURL('github', {
connect: token,
redirectTo: `${window.location.origin}?signinProvider=github&state=signin-refresh:${org.slug}:${project?.subdomain}`,
});
}
return '';
};
useEffect(() => { useEffect(() => {
startPolling(2000); if (loadingGithubConnected) {
}, [startPolling]); return;
}
const fetchGitHubData = async () => {
try {
setLoading(true);
if (isEmptyValue(githubProvider)) {
setConnectGitHubModalState('GITHUB_CONNECTION_REQUIRED');
setLoading(false);
return;
}
const githubToken = getGitHubToken();
if (
!githubToken?.authUserProviderId ||
githubProvider!.id !== githubToken.authUserProviderId
) {
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
setLoading(false);
return;
}
const { refreshToken, expiresAt: expiresAtString } = githubToken;
let accessToken = githubToken?.accessToken;
const expiresAt = new Date(expiresAtString).getTime();
const currentTime = Date.now();
const expiresAtMargin = 60 * 1000;
if (expiresAt - currentTime < expiresAtMargin) {
if (!refreshToken) {
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
setLoading(false);
return;
}
const refreshResponse = await nhost.auth.refreshProviderToken(
'github',
{ refreshToken },
);
if (!refreshResponse.body) {
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
setLoading(false);
return;
}
saveGitHubToken({
...refreshResponse.body,
authUserProviderId: githubProvider!.id,
});
accessToken = refreshResponse.body.accessToken;
}
const installations = await listGitHubInstallationRepos(accessToken);
const transformedData = {
githubAppInstallations: installations.map((item) => ({
id: item.installation.id,
accountLogin: item.installation.account?.login,
accountAvatarUrl: item.installation.account?.avatar_url,
})),
githubRepositories: installations.flatMap((item) =>
item.repositories.map((repo) => ({
id: repo.id,
node_id: repo.node_id,
name: repo.name,
fullName: repo.full_name,
githubAppInstallation: {
accountLogin: item.installation.account?.login,
accountAvatarUrl: item.installation.account?.avatar_url,
},
})),
),
};
setGithubData(transformedData);
setLoading(false);
} catch (err) {
console.error('Error fetching GitHub data:', err);
if (err instanceof GitHubAPIError && err.status === 401) {
setConnectGitHubModalState('EXPIRED_GITHUB_SESSION');
setLoading(false);
return;
}
toast.error(err?.message, getToastStyleProps());
close?.();
}
};
fetchGitHubData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [githubProvider, loadingGithubConnected]);
const handleSelectAnotherRepository = () => { const handleSelectAnotherRepository = () => {
setSelectedRepoId(null); setSelectedRepoId(null);
@@ -56,13 +211,91 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]); useEffect(() => () => handleFilterChange.cancel(), [handleFilterChange]);
if (error) { if (errorGithubConnected instanceof Error) {
throw error; return (
<div className="px-1 md:w-[653px]">
<div className="flex flex-col">
<div className="mx-auto text-center">
<div className="mx-auto h-8 w-8">
<GitHubIcon className="h-8 w-8" />
</div>
</div>
<div className="flex flex-col gap-2">
<Text className="mt-2.5 text-center text-lg font-medium">
Error fetching GitHub data
</Text>
<ErrorMessage>{errorGithubConnected.message}</ErrorMessage>
</div>
</div>
</div>
);
} }
if (loading) { if (loading || loadingProject || loadingOrg || loadingGithubConnected) {
return ( return (
<ActivityIndicator delay={500} label="Loading GitHub repositories..." /> <div className="px-1 md:w-[653px]">
<div className="flex flex-col">
<div className="mx-auto text-center">
<div className="mx-auto h-8 w-8">
<GitHubIcon className="h-8 w-8" />
</div>
</div>
<div>
<Text className="mt-2.5 text-center text-lg font-medium">
Loading repositories...
</Text>
<Text className="text-center text-xs font-normal" color="secondary">
Fetching your GitHub repositories
</Text>
<div className="mb-2 mt-6 flex w-full">
<Input placeholder="Search..." fullWidth disabled value="" />
</div>
</div>
<div className="flex h-import items-center justify-center border-y">
<ActivityIndicator delay={0} label="" />
</div>
</div>
</div>
);
}
if (ConnectGitHubModalState === 'GITHUB_CONNECTION_REQUIRED') {
return (
<div className="flex flex-col items-center justify-center gap-5 px-1 py-1 md:w-[653px]">
<p className="text-center text-foreground">
You need to connect your GitHub account to continue.
</p>
<NavLink
href={getGitHubConnectUrl()}
passHref
rel="noreferrer noopener"
legacyBehavior
>
<Button
className="w-full max-w-72"
variant="outlined"
color="secondary"
startIcon={<GitHubIcon />}
>
Connect to GitHub
</Button>
</NavLink>
</div>
);
}
if (ConnectGitHubModalState === 'EXPIRED_GITHUB_SESSION') {
return (
<div className="flex w-full flex-col items-center justify-center gap-5 px-1 py-1 md:w-[653px]">
<p className="text-center text-foreground">
Please sign in with GitHub to continue.
</p>
<GithubAuthButton
redirectTo={`${hostname}?signinProvider=github&state=signin-refresh:${org.slug}:${project!.subdomain}`}
buttonText="Sign in with GitHub"
className="w-full max-w-72 gap-2 !bg-primary !text-white disabled:!text-white disabled:!text-opacity-60 dark:!bg-white dark:!text-black dark:disabled:!text-black"
/>
</div>
); );
} }
@@ -78,25 +311,27 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
); );
} }
const { githubAppInstallations } = data || {}; const { githubAppInstallations } = githubData || {};
const filteredGitHubAppInstallations = data?.githubAppInstallations.filter( const filteredGitHubAppInstallations =
(githubApp) => !!githubApp.accountLogin, githubData?.githubAppInstallations.filter(
); (githubApp) => !!githubApp.accountLogin,
);
const filteredGitHubRepositories = data?.githubRepositories.filter( const filteredGitHubRepositories = githubData?.githubRepositories.filter(
(repo) => !!repo.githubAppInstallation, (repo) => !!repo.githubAppInstallation,
); );
const filteredGitHubAppInstallationsNullValues = const filteredGitHubAppInstallationsNullValues =
data?.githubAppInstallations.filter((githubApp) => !!githubApp.accountLogin) githubData?.githubAppInstallations.filter(
.length === 0; (githubApp) => !!githubApp.accountLogin,
).length === 0;
const faultyGitHubInstallation = const faultyGitHubInstallation =
githubAppInstallations?.length === 0 || githubAppInstallations?.length === 0 ||
filteredGitHubAppInstallationsNullValues; filteredGitHubAppInstallationsNullValues;
const noRepositoriesAdded = data?.githubRepositories.length === 0; const noRepositoriesAdded = githubData?.githubRepositories.length === 0;
if (faultyGitHubInstallation) { if (faultyGitHubInstallation) {
return ( return (
@@ -115,11 +350,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
</div> </div>
<Button <Button
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL} href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
// Both `target` and `rel` are available when `href` is set. This is
// a limitation of MUI.
// @ts-ignore
target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
endIcon={<ArrowSquareOutIcon className="h-4 w-4" />} endIcon={<ArrowSquareOutIcon className="h-4 w-4" />}
> >
@@ -179,8 +410,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
</List> </List>
<Link <Link
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL} href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
underline="hover" underline="hover"
className="grid grid-flow-col items-center justify-start gap-1" className="grid grid-flow-col items-center justify-start gap-1"
@@ -199,8 +429,8 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
className="text-center text-xs font-normal" className="text-center text-xs font-normal"
color="secondary" color="secondary"
> >
Showing repositories from {data?.githubAppInstallations.length}{' '} Showing repositories from{' '}
GitHub account(s) {githubData?.githubAppInstallations.length} GitHub account(s)
</Text> </Text>
<div className="mb-2 mt-6 flex w-full"> <div className="mb-2 mt-6 flex w-full">
<Input <Input
@@ -226,7 +456,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
<Button <Button
variant="borderless" variant="borderless"
color="primary" color="primary"
onClick={() => setSelectedRepoId(repo.id)} onClick={() => setSelectedRepoId(repo.node_id)}
> >
Connect Connect
</Button> </Button>
@@ -268,8 +498,7 @@ export default function ConnectGitHubModal({ close }: ConnectGitHubModalProps) {
Do you miss a repository, or do you need to connect another GitHub Do you miss a repository, or do you need to connect another GitHub
account?{' '} account?{' '}
<Link <Link
href={process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL} href={`${process.env.NEXT_PUBLIC_GITHUB_APP_INSTALL_URL}?state=install-github-app:${org.slug}:${project!.subdomain}`}
target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
className="text-xs font-medium" className="text-xs font-medium"
underline="hover" underline="hover"

View File

@@ -6,7 +6,7 @@ import { FormProvider, useForm } from 'react-hook-form';
export interface EditRepositorySettingsProps { export interface EditRepositorySettingsProps {
close?: () => void; close?: () => void;
openConnectGithubModal?: () => void; openConnectGithubModal?: () => void;
selectedRepoId?: string; selectedRepoId: string;
connectGithubModalState?: ConnectGitHubModalState; connectGithubModalState?: ConnectGitHubModalState;
handleSelectAnotherRepository?: () => void; handleSelectAnotherRepository?: () => void;
} }

View File

@@ -6,14 +6,14 @@ import { Text } from '@/components/ui/v2/Text';
import { EditRepositoryAndBranchSettings } from '@/features/orgs/projects/git/common/components/EditRepositoryAndBranchSettings'; import { EditRepositoryAndBranchSettings } from '@/features/orgs/projects/git/common/components/EditRepositoryAndBranchSettings';
import type { EditRepositorySettingsFormData } from '@/features/orgs/projects/git/common/components/EditRepositorySettings'; import type { EditRepositorySettingsFormData } from '@/features/orgs/projects/git/common/components/EditRepositorySettings';
import { useProject } from '@/features/orgs/projects/hooks/useProject'; import { useProject } from '@/features/orgs/projects/hooks/useProject';
import { useUpdateApplicationMutation } from '@/generated/graphql'; import { useConnectGithubRepoMutation } from '@/generated/graphql';
import { analytics } from '@/lib/segment'; import { analytics } from '@/lib/segment';
import { discordAnnounce } from '@/utils/discordAnnounce'; import { discordAnnounce } from '@/utils/discordAnnounce';
import { triggerToast } from '@/utils/toast'; import { triggerToast } from '@/utils/toast';
import { useFormContext } from 'react-hook-form'; import { useFormContext } from 'react-hook-form';
export interface EditRepositorySettingsModalProps { export interface EditRepositorySettingsModalProps {
selectedRepoId?: string; selectedRepoId: string;
close?: () => void; close?: () => void;
handleSelectAnotherRepository?: () => void; handleSelectAnotherRepository?: () => void;
} }
@@ -33,45 +33,29 @@ export default function EditRepositorySettingsModal({
const { project, refetch: refetchProject } = useProject(); const { project, refetch: refetchProject } = useProject();
const [updateApp, { loading }] = useUpdateApplicationMutation(); const [connectGithubRepo, { loading }] = useConnectGithubRepoMutation();
const handleEditGitHubIntegration = async ( const handleEditGitHubIntegration = async (
data: EditRepositorySettingsFormData, data: EditRepositorySettingsFormData,
) => { ) => {
try { try {
if (!project?.githubRepository || selectedRepoId) { await connectGithubRepo({
await updateApp({ variables: {
variables: { appID: project?.id,
appId: project?.id, githubNodeID: selectedRepoId,
app: { productionBranch: data.productionBranch,
githubRepositoryId: selectedRepoId, baseFolder: data.repoBaseFolder,
repositoryProductionBranch: data.productionBranch, },
nhostBaseFolder: data.repoBaseFolder, });
},
},
});
if (selectedRepoId) { analytics.track('Project Connected to GitHub', {
analytics.track('Project Connected to GitHub', { projectId: project?.id,
projectId: project?.id, projectName: project?.name,
projectName: project?.name, projectSubdomain: project?.subdomain,
projectSubdomain: project?.subdomain, repositoryId: selectedRepoId,
repositoryId: selectedRepoId, productionBranch: data.productionBranch,
productionBranch: data.productionBranch, baseFolder: data.repoBaseFolder,
baseFolder: data.repoBaseFolder, });
});
}
} else {
await updateApp({
variables: {
appId: project.id,
app: {
repositoryProductionBranch: data.productionBranch,
nhostBaseFolder: data.repoBaseFolder,
},
},
});
}
await refetchProject(); await refetchProject();

View File

@@ -0,0 +1,13 @@
mutation ConnectGithubRepo(
$appID: uuid!
$githubNodeID: String!
$productionBranch: String!
$baseFolder: String!
) {
connectGithubRepo(
appID: $appID
githubNodeID: $githubNodeID
productionBranch: $productionBranch
baseFolder: $baseFolder
)
}

View File

@@ -2,12 +2,12 @@ import { useDialog } from '@/components/common/DialogProvider';
import { ConnectGitHubModal } from '@/features/orgs/projects/git/common/components/ConnectGitHubModal'; import { ConnectGitHubModal } from '@/features/orgs/projects/git/common/components/ConnectGitHubModal';
export default function useGitHubModal() { export default function useGitHubModal() {
const { openAlertDialog } = useDialog(); const { openAlertDialog, closeAlertDialog } = useDialog();
function openGitHubModal() { function openGitHubModal() {
openAlertDialog({ openAlertDialog({
title: 'Connect GitHub Repository', title: 'Connect GitHub Repository',
payload: <ConnectGitHubModal />, payload: <ConnectGitHubModal close={closeAlertDialog} />,
props: { props: {
hidePrimaryAction: true, hidePrimaryAction: true,
hideSecondaryAction: true, hideSecondaryAction: true,

View File

@@ -0,0 +1,23 @@
import { isNotEmptyValue } from '@/lib/utils';
import type { ProviderSession } from '@nhost/nhost-js/auth';
const githubProviderTokenKey = 'nhost_provider_tokens_github';
export type GitHubProviderToken = ProviderSession & {
authUserProviderId?: string;
};
export function saveGitHubToken(token: GitHubProviderToken) {
localStorage.setItem(githubProviderTokenKey, JSON.stringify(token));
}
export function getGitHubToken() {
const token = localStorage.getItem(githubProviderTokenKey);
return isNotEmptyValue(token)
? (JSON.parse(token) as GitHubProviderToken)
: null;
}
export function clearGitHubToken() {
localStorage.removeItem(githubProviderTokenKey);
}

View File

@@ -0,0 +1 @@
export * from './githubTokens';

View File

@@ -0,0 +1,99 @@
/**
* Custom error class for GitHub API errors that preserves HTTP status codes
*/
export class GitHubAPIError extends Error {
constructor(
message: string,
public status: number,
public statusText: string,
) {
super(message);
this.name = 'GitHubAPIError';
}
}
interface GitHubAppInstallation {
id: number;
account?: {
login: string;
avatar_url: string;
[key: string]: unknown;
}
[key: string]: unknown;
}
/**
* Lists all GitHub App installations accessible to the user
* @param accessToken - The GitHub OAuth access token
* @returns Array of app installations
*/
export async function listGitHubAppInstallations(accessToken: string): Promise<GitHubAppInstallation[]> {
const response = await fetch('https://api.github.com/user/installations', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
cache: 'no-cache',
});
if (!response.ok) {
throw new GitHubAPIError(
`Failed to list installations: ${response.statusText}`,
response.status,
response.statusText
);
}
const data = await response.json();
return data.installations;
}
interface GitHubRepo {
id: number;
node_id: string;
name: string;
full_name: string;
[key: string]: unknown;
}
/**
* Lists all repositories accessible through GitHub App installations
* @param accessToken - The GitHub OAuth access token
* @returns Array of repositories grouped by installation
*/
export async function listGitHubInstallationRepos(accessToken: string) {
const installations = await listGitHubAppInstallations(accessToken);
const reposByInstallation = await Promise.all(
installations.map(async (installation) => {
const response = await fetch(
`https://api.github.com/user/installations/${installation.id}/repositories`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github+json',
'X-GitHub-Api-Version': '2022-11-28',
},
cache: 'no-cache',
}
);
if (!response.ok) {
throw new GitHubAPIError(
`Failed to list repos for installation ${installation.id}: ${response.statusText}`,
response.status,
response.statusText
);
}
const data = await response.json();
return {
installation,
repositories: data.repositories as GitHubRepo[],
};
})
);
return reposByInstallation;
}

View File

@@ -77,12 +77,12 @@ function MyApp({
<CacheProvider value={emotionCache}> <CacheProvider value={emotionCache}>
<NhostProvider nhost={nhost}> <NhostProvider nhost={nhost}>
<AuthProvider> <NhostApolloProvider
<NhostApolloProvider fetchPolicy="cache-and-network"
fetchPolicy="cache-and-network" nhost={nhost}
nhost={nhost} connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'}
connectToDevTools={process.env.NEXT_PUBLIC_ENV === 'dev'} >
> <AuthProvider>
<UIProvider> <UIProvider>
<Toaster position="bottom-center" /> <Toaster position="bottom-center" />
<ThemeProvider <ThemeProvider
@@ -106,8 +106,8 @@ function MyApp({
</RetryableErrorBoundary> </RetryableErrorBoundary>
</ThemeProvider> </ThemeProvider>
</UIProvider> </UIProvider>
</NhostApolloProvider> </AuthProvider>
</AuthProvider> </NhostApolloProvider>
</NhostProvider> </NhostProvider>
</CacheProvider> </CacheProvider>
</QueryClientProvider> </QueryClientProvider>

View File

@@ -1,87 +0,0 @@
import { LoadingScreen } from '@/components/presentational/LoadingScreen';
import { ActivityIndicator } from '@/components/ui/v2/ActivityIndicator';
import { nhost } from '@/utils/nhost';
import { useAuth } from '@/providers/Auth';
import { useRouter } from 'next/router';
import type { ComponentType } from 'react';
import { useEffect, useState } from 'react';
export function authProtected<P extends JSX.IntrinsicAttributes>(
Comp: ComponentType<P>,
) {
return function AuthProtected(props: P) {
const router = useRouter();
const { isAuthenticated, isLoading } = useAuth();
useEffect(() => {
if (isLoading || isAuthenticated) {
return;
}
router.push('/signin');
}, [isLoading, isAuthenticated, router]);
if (isLoading) {
return <LoadingScreen />;
}
return <Comp {...props} />;
};
}
function Page() {
const [state, setState] = useState({
error: null,
loading: true,
});
const router = useRouter();
const { installation_id: installationId } = router.query;
useEffect(() => {
async function installGithubApp() {
try {
await nhost.functions.fetch('/client/github-app-installation', {
method: 'POST',
body: JSON.stringify({
installationId,
}),
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
setState({
error,
loading: false,
});
return;
}
setState({
error: null,
loading: false,
});
window.close();
}
// run in async manner
installGithubApp();
}, [installationId]);
if (state.loading) {
return <ActivityIndicator delay={500} label="Loading..." />;
}
if (state.error) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw state.error;
}
return <div>GitHub connection completed. You can close this tab.</div>;
}
export default authProtected(Page);

View File

@@ -1,12 +1,38 @@
import { Container } from '@/components/layout/Container'; import { Container } from '@/components/layout/Container';
import { OrgLayout } from '@/features/orgs/layout/OrgLayout'; import { OrgLayout } from '@/features/orgs/layout/OrgLayout';
import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout'; import { SettingsLayout } from '@/features/orgs/layout/SettingsLayout';
import { useGitHubModal } from '@/features/orgs/projects/git/common/hooks/useGitHubModal';
import { BaseDirectorySettings } from '@/features/orgs/projects/git/settings/components/BaseDirectorySettings'; import { BaseDirectorySettings } from '@/features/orgs/projects/git/settings/components/BaseDirectorySettings';
import { DeploymentBranchSettings } from '@/features/orgs/projects/git/settings/components/DeploymentBranchSettings'; import { DeploymentBranchSettings } from '@/features/orgs/projects/git/settings/components/DeploymentBranchSettings';
import { GitConnectionSettings } from '@/features/orgs/projects/git/settings/components/GitConnectionSettings'; import { GitConnectionSettings } from '@/features/orgs/projects/git/settings/components/GitConnectionSettings';
import type { ReactElement } from 'react'; import { useRouter } from 'next/router';
import { useCallback, useEffect, type ReactElement } from 'react';
export default function GitSettingsPage() { export default function GitSettingsPage() {
const router = useRouter();
const { pathname, replace, isReady: isRouterReady } = router;
const { 'github-modal': githubModal, ...remainingQuery } = router.query;
const { openGitHubModal } = useGitHubModal();
const removeQueryParamsFromURL = useCallback(() => {
replace({ pathname, query: remainingQuery }, undefined, {
shallow: true,
});
}, [replace, remainingQuery, pathname]);
useEffect(() => {
if (!isRouterReady) {
return;
}
if (typeof githubModal === 'string') {
removeQueryParamsFromURL();
openGitHubModal();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [githubModal, isRouterReady]);
return ( return (
<Container <Container
className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent" className="grid max-w-5xl grid-flow-row gap-y-6 bg-transparent"

View File

@@ -1,19 +1,27 @@
import {
clearGitHubToken,
saveGitHubToken,
type GitHubProviderToken,
} from '@/features/orgs/projects/git/common/utils';
import { isNotEmptyValue } from '@/lib/utils';
import { useNhostClient } from '@/providers/nhost/'; import { useNhostClient } from '@/providers/nhost/';
import { useGetAuthUserProvidersLazyQuery } from '@/utils/__generated__/graphql';
import { getToastStyleProps } from '@/utils/constants/settings'; import { getToastStyleProps } from '@/utils/constants/settings';
import { type Session } from '@nhost/nhost-js/auth'; import { type Session } from '@nhost/nhost-js/auth';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { import {
type PropsWithChildren,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useState, useState,
type PropsWithChildren,
} from 'react'; } from 'react';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { AuthContext, type AuthContextType } from './AuthContext'; import { AuthContext, type AuthContextType } from './AuthContext';
function AuthProvider({ children }: PropsWithChildren) { function AuthProvider({ children }: PropsWithChildren) {
const nhost = useNhostClient(); const nhost = useNhostClient();
const [getAuthUserProviders] = useGetAuthUserProvidersLazyQuery();
const { const {
query, query,
isReady: isRouterReady, isReady: isRouterReady,
@@ -21,7 +29,15 @@ function AuthProvider({ children }: PropsWithChildren) {
pathname, pathname,
push, push,
} = useRouter(); } = useRouter();
const { refreshToken, error, errorDescription, ...remainingQuery } = query; const {
refreshToken,
error,
errorDescription,
signinProvider,
state,
provider_state: providerState,
...remainingQuery
} = query;
const [session, setSession] = useState<Session | null>(null); const [session, setSession] = useState<Session | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [isSigningOut, setIsSigningOut] = useState(false); const [isSigningOut, setIsSigningOut] = useState(false);
@@ -55,7 +71,6 @@ function AuthProvider({ children }: PropsWithChildren) {
return; return;
} }
setIsLoading(true); setIsLoading(true);
// reset state if we have just signed out
setIsSigningOut(false); setIsSigningOut(false);
if (refreshToken && typeof refreshToken === 'string') { if (refreshToken && typeof refreshToken === 'string') {
const sessionResponse = await nhost.auth.refreshToken({ const sessionResponse = await nhost.auth.refreshToken({
@@ -63,24 +78,84 @@ function AuthProvider({ children }: PropsWithChildren) {
}); });
setSession(sessionResponse.body); setSession(sessionResponse.body);
removeQueryParamsFromURL(); removeQueryParamsFromURL();
if (sessionResponse.body && signinProvider === 'github') {
try {
const providerTokensResponse =
await nhost.auth.getProviderTokens(signinProvider);
if (providerTokensResponse.body) {
const { data } = await getAuthUserProviders();
const githubProvider = data?.authUserProviders?.find(
(provider) => provider.providerId === 'github',
);
const newGitHubToken: GitHubProviderToken =
providerTokensResponse.body;
if (isNotEmptyValue(githubProvider?.id)) {
newGitHubToken.authUserProviderId = githubProvider!.id;
}
saveGitHubToken(newGitHubToken);
}
} catch (err) {
console.error('Failed to fetch provider tokens:', err);
}
}
} else { } else {
const currentSession = nhost.getUserSession(); const currentSession = nhost.getUserSession();
setSession(currentSession); setSession(currentSession);
} }
// handle OAuth redirect errors (e.g., error=unverified-user) if (
state &&
typeof state === 'string' &&
state.startsWith('signin-refresh:')
) {
const [, orgSlug, projectSubdomain] = state.split(':');
removeQueryParamsFromURL();
await push(
`/orgs/${orgSlug}/projects/${projectSubdomain}/settings/git?github-modal`,
);
}
if (typeof error === 'string') { if (typeof error === 'string') {
if (error === 'unverified-user') { switch (error) {
removeQueryParamsFromURL(); case 'unverified-user': {
await push('/email/verify'); removeQueryParamsFromURL();
} else { await push('/email/verify');
const description = break;
typeof errorDescription === 'string' }
? errorDescription
: 'An error occurred during the sign-in process. Please try again.'; /*
toast.error(description, getToastStyleProps()); * If the state isn't handled by Hasura auth, it returns `invalid-state`.
removeQueryParamsFromURL(); * However, we check the provider_state search param to see if it has this format:
await push('/signin'); * `install-github-app:<org-slug>:<project-subdomain>`.
* If it has this format, that means that we connected to GitHub in `/settings/git`,
* thus we need to show the connect GitHub modal again.
* Otherwise, we fall through to default error handling.
*/
case 'invalid-state': {
if (
isNotEmptyValue(providerState) &&
typeof providerState === 'string' &&
providerState.startsWith('install-github-app:')
) {
const [, orgSlug, projectSubdomain] = providerState.split(':');
removeQueryParamsFromURL();
await push(
`/orgs/${orgSlug}/projects/${projectSubdomain}/settings/git?github-modal`,
);
break;
}
// Fall through to default error handling if state search param is invalid
}
default: {
const description =
typeof errorDescription === 'string'
? errorDescription
: 'An error occurred during the sign-in process. Please try again.';
toast.error(description, getToastStyleProps());
removeQueryParamsFromURL();
await push('/signin');
}
} }
} }
@@ -103,6 +178,7 @@ function AuthProvider({ children }: PropsWithChildren) {
nhost.auth.signOut({ nhost.auth.signOut({
refreshToken: session!.refreshToken, refreshToken: session!.refreshToken,
}); });
clearGitHubToken();
await push('/signin'); await push('/signin');
}, },

View File

@@ -108,16 +108,16 @@ function Providers({ children }: PropsWithChildren<{}>) {
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<CacheProvider value={emotionCache}> <CacheProvider value={emotionCache}>
<NhostProvider nhost={nhost}> <NhostProvider nhost={nhost}>
<AuthProvider> <ApolloProvider client={mockClient}>
<ApolloProvider client={mockClient}> <AuthProvider>
<UIProvider> <UIProvider>
<Toaster position="bottom-center" /> <Toaster position="bottom-center" />
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<DialogProvider>{children}</DialogProvider> <DialogProvider>{children}</DialogProvider>
</ThemeProvider> </ThemeProvider>
</UIProvider> </UIProvider>
</ApolloProvider> </AuthProvider>
</AuthProvider> </ApolloProvider>
</NhostProvider> </NhostProvider>
</CacheProvider> </CacheProvider>
</QueryClientProvider> </QueryClientProvider>

View File

@@ -3118,7 +3118,9 @@ export type ConfigSystemConfigPostgres = {
database: Scalars['String']; database: Scalars['String'];
disk?: Maybe<ConfigSystemConfigPostgresDisk>; disk?: Maybe<ConfigSystemConfigPostgresDisk>;
enabled?: Maybe<Scalars['Boolean']>; enabled?: Maybe<Scalars['Boolean']>;
encryptColumnKey?: Maybe<Scalars['String']>;
majorVersion?: Maybe<Scalars['String']>; majorVersion?: Maybe<Scalars['String']>;
oldEncryptColumnKey?: Maybe<Scalars['String']>;
}; };
export type ConfigSystemConfigPostgresComparisonExp = { export type ConfigSystemConfigPostgresComparisonExp = {
@@ -3129,7 +3131,9 @@ export type ConfigSystemConfigPostgresComparisonExp = {
database?: InputMaybe<ConfigStringComparisonExp>; database?: InputMaybe<ConfigStringComparisonExp>;
disk?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>; disk?: InputMaybe<ConfigSystemConfigPostgresDiskComparisonExp>;
enabled?: InputMaybe<ConfigBooleanComparisonExp>; enabled?: InputMaybe<ConfigBooleanComparisonExp>;
encryptColumnKey?: InputMaybe<ConfigStringComparisonExp>;
majorVersion?: InputMaybe<ConfigStringComparisonExp>; majorVersion?: InputMaybe<ConfigStringComparisonExp>;
oldEncryptColumnKey?: InputMaybe<ConfigStringComparisonExp>;
}; };
export type ConfigSystemConfigPostgresConnectionString = { export type ConfigSystemConfigPostgresConnectionString = {
@@ -3193,7 +3197,9 @@ export type ConfigSystemConfigPostgresInsertInput = {
database: Scalars['String']; database: Scalars['String'];
disk?: InputMaybe<ConfigSystemConfigPostgresDiskInsertInput>; disk?: InputMaybe<ConfigSystemConfigPostgresDiskInsertInput>;
enabled?: InputMaybe<Scalars['Boolean']>; enabled?: InputMaybe<Scalars['Boolean']>;
encryptColumnKey?: InputMaybe<Scalars['String']>;
majorVersion?: InputMaybe<Scalars['String']>; majorVersion?: InputMaybe<Scalars['String']>;
oldEncryptColumnKey?: InputMaybe<Scalars['String']>;
}; };
export type ConfigSystemConfigPostgresUpdateInput = { export type ConfigSystemConfigPostgresUpdateInput = {
@@ -3201,7 +3207,9 @@ export type ConfigSystemConfigPostgresUpdateInput = {
database?: InputMaybe<Scalars['String']>; database?: InputMaybe<Scalars['String']>;
disk?: InputMaybe<ConfigSystemConfigPostgresDiskUpdateInput>; disk?: InputMaybe<ConfigSystemConfigPostgresDiskUpdateInput>;
enabled?: InputMaybe<Scalars['Boolean']>; enabled?: InputMaybe<Scalars['Boolean']>;
encryptColumnKey?: InputMaybe<Scalars['String']>;
majorVersion?: InputMaybe<Scalars['String']>; majorVersion?: InputMaybe<Scalars['String']>;
oldEncryptColumnKey?: InputMaybe<Scalars['String']>;
}; };
export type ConfigSystemConfigUpdateInput = { export type ConfigSystemConfigUpdateInput = {
@@ -4426,6 +4434,7 @@ export type Apps = {
billingDedicatedCompute?: Maybe<Billing_Dedicated_Compute>; billingDedicatedCompute?: Maybe<Billing_Dedicated_Compute>;
/** An object relationship */ /** An object relationship */
billingSubscriptions?: Maybe<Billing_Subscriptions>; billingSubscriptions?: Maybe<Billing_Subscriptions>;
/** main entrypoint to the configuration */
config?: Maybe<ConfigConfig>; config?: Maybe<ConfigConfig>;
createdAt: Scalars['timestamptz']; createdAt: Scalars['timestamptz'];
/** An object relationship */ /** An object relationship */
@@ -11094,6 +11103,7 @@ export type Deployments = {
commitSHA: Scalars['String']; commitSHA: Scalars['String'];
commitUserAvatarUrl?: Maybe<Scalars['String']>; commitUserAvatarUrl?: Maybe<Scalars['String']>;
commitUserName?: Maybe<Scalars['String']>; commitUserName?: Maybe<Scalars['String']>;
createdAt: Scalars['timestamptz'];
deploymentEndedAt?: Maybe<Scalars['timestamptz']>; deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
/** An array relationship */ /** An array relationship */
deploymentLogs: Array<DeploymentLogs>; deploymentLogs: Array<DeploymentLogs>;
@@ -11191,6 +11201,7 @@ export type Deployments_Bool_Exp = {
commitSHA?: InputMaybe<String_Comparison_Exp>; commitSHA?: InputMaybe<String_Comparison_Exp>;
commitUserAvatarUrl?: InputMaybe<String_Comparison_Exp>; commitUserAvatarUrl?: InputMaybe<String_Comparison_Exp>;
commitUserName?: InputMaybe<String_Comparison_Exp>; commitUserName?: InputMaybe<String_Comparison_Exp>;
createdAt?: InputMaybe<Timestamptz_Comparison_Exp>;
deploymentEndedAt?: InputMaybe<Timestamptz_Comparison_Exp>; deploymentEndedAt?: InputMaybe<Timestamptz_Comparison_Exp>;
deploymentLogs?: InputMaybe<DeploymentLogs_Bool_Exp>; deploymentLogs?: InputMaybe<DeploymentLogs_Bool_Exp>;
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Bool_Exp>; deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Bool_Exp>;
@@ -11222,6 +11233,7 @@ export type Deployments_Insert_Input = {
commitSHA?: InputMaybe<Scalars['String']>; commitSHA?: InputMaybe<Scalars['String']>;
commitUserAvatarUrl?: InputMaybe<Scalars['String']>; commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
commitUserName?: InputMaybe<Scalars['String']>; commitUserName?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>; deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
deploymentLogs?: InputMaybe<DeploymentLogs_Arr_Rel_Insert_Input>; deploymentLogs?: InputMaybe<DeploymentLogs_Arr_Rel_Insert_Input>;
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>; deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
@@ -11246,6 +11258,7 @@ export type Deployments_Max_Fields = {
commitSHA?: Maybe<Scalars['String']>; commitSHA?: Maybe<Scalars['String']>;
commitUserAvatarUrl?: Maybe<Scalars['String']>; commitUserAvatarUrl?: Maybe<Scalars['String']>;
commitUserName?: Maybe<Scalars['String']>; commitUserName?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['timestamptz']>;
deploymentEndedAt?: Maybe<Scalars['timestamptz']>; deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
deploymentStartedAt?: Maybe<Scalars['timestamptz']>; deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
deploymentStatus?: Maybe<Scalars['String']>; deploymentStatus?: Maybe<Scalars['String']>;
@@ -11268,6 +11281,7 @@ export type Deployments_Max_Order_By = {
commitSHA?: InputMaybe<Order_By>; commitSHA?: InputMaybe<Order_By>;
commitUserAvatarUrl?: InputMaybe<Order_By>; commitUserAvatarUrl?: InputMaybe<Order_By>;
commitUserName?: InputMaybe<Order_By>; commitUserName?: InputMaybe<Order_By>;
createdAt?: InputMaybe<Order_By>;
deploymentEndedAt?: InputMaybe<Order_By>; deploymentEndedAt?: InputMaybe<Order_By>;
deploymentStartedAt?: InputMaybe<Order_By>; deploymentStartedAt?: InputMaybe<Order_By>;
deploymentStatus?: InputMaybe<Order_By>; deploymentStatus?: InputMaybe<Order_By>;
@@ -11291,6 +11305,7 @@ export type Deployments_Min_Fields = {
commitSHA?: Maybe<Scalars['String']>; commitSHA?: Maybe<Scalars['String']>;
commitUserAvatarUrl?: Maybe<Scalars['String']>; commitUserAvatarUrl?: Maybe<Scalars['String']>;
commitUserName?: Maybe<Scalars['String']>; commitUserName?: Maybe<Scalars['String']>;
createdAt?: Maybe<Scalars['timestamptz']>;
deploymentEndedAt?: Maybe<Scalars['timestamptz']>; deploymentEndedAt?: Maybe<Scalars['timestamptz']>;
deploymentStartedAt?: Maybe<Scalars['timestamptz']>; deploymentStartedAt?: Maybe<Scalars['timestamptz']>;
deploymentStatus?: Maybe<Scalars['String']>; deploymentStatus?: Maybe<Scalars['String']>;
@@ -11313,6 +11328,7 @@ export type Deployments_Min_Order_By = {
commitSHA?: InputMaybe<Order_By>; commitSHA?: InputMaybe<Order_By>;
commitUserAvatarUrl?: InputMaybe<Order_By>; commitUserAvatarUrl?: InputMaybe<Order_By>;
commitUserName?: InputMaybe<Order_By>; commitUserName?: InputMaybe<Order_By>;
createdAt?: InputMaybe<Order_By>;
deploymentEndedAt?: InputMaybe<Order_By>; deploymentEndedAt?: InputMaybe<Order_By>;
deploymentStartedAt?: InputMaybe<Order_By>; deploymentStartedAt?: InputMaybe<Order_By>;
deploymentStatus?: InputMaybe<Order_By>; deploymentStatus?: InputMaybe<Order_By>;
@@ -11359,6 +11375,7 @@ export type Deployments_Order_By = {
commitSHA?: InputMaybe<Order_By>; commitSHA?: InputMaybe<Order_By>;
commitUserAvatarUrl?: InputMaybe<Order_By>; commitUserAvatarUrl?: InputMaybe<Order_By>;
commitUserName?: InputMaybe<Order_By>; commitUserName?: InputMaybe<Order_By>;
createdAt?: InputMaybe<Order_By>;
deploymentEndedAt?: InputMaybe<Order_By>; deploymentEndedAt?: InputMaybe<Order_By>;
deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Order_By>; deploymentLogs_aggregate?: InputMaybe<DeploymentLogs_Aggregate_Order_By>;
deploymentStartedAt?: InputMaybe<Order_By>; deploymentStartedAt?: InputMaybe<Order_By>;
@@ -11393,6 +11410,8 @@ export enum Deployments_Select_Column {
/** column name */ /** column name */
CommitUserName = 'commitUserName', CommitUserName = 'commitUserName',
/** column name */ /** column name */
CreatedAt = 'createdAt',
/** column name */
DeploymentEndedAt = 'deploymentEndedAt', DeploymentEndedAt = 'deploymentEndedAt',
/** column name */ /** column name */
DeploymentStartedAt = 'deploymentStartedAt', DeploymentStartedAt = 'deploymentStartedAt',
@@ -11427,6 +11446,7 @@ export type Deployments_Set_Input = {
commitSHA?: InputMaybe<Scalars['String']>; commitSHA?: InputMaybe<Scalars['String']>;
commitUserAvatarUrl?: InputMaybe<Scalars['String']>; commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
commitUserName?: InputMaybe<Scalars['String']>; commitUserName?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>; deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>; deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
deploymentStatus?: InputMaybe<Scalars['String']>; deploymentStatus?: InputMaybe<Scalars['String']>;
@@ -11457,6 +11477,7 @@ export type Deployments_Stream_Cursor_Value_Input = {
commitSHA?: InputMaybe<Scalars['String']>; commitSHA?: InputMaybe<Scalars['String']>;
commitUserAvatarUrl?: InputMaybe<Scalars['String']>; commitUserAvatarUrl?: InputMaybe<Scalars['String']>;
commitUserName?: InputMaybe<Scalars['String']>; commitUserName?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['timestamptz']>;
deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>; deploymentEndedAt?: InputMaybe<Scalars['timestamptz']>;
deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>; deploymentStartedAt?: InputMaybe<Scalars['timestamptz']>;
deploymentStatus?: InputMaybe<Scalars['String']>; deploymentStatus?: InputMaybe<Scalars['String']>;
@@ -11485,6 +11506,8 @@ export enum Deployments_Update_Column {
/** column name */ /** column name */
CommitUserName = 'commitUserName', CommitUserName = 'commitUserName',
/** column name */ /** column name */
CreatedAt = 'createdAt',
/** column name */
DeploymentEndedAt = 'deploymentEndedAt', DeploymentEndedAt = 'deploymentEndedAt',
/** column name */ /** column name */
DeploymentStartedAt = 'deploymentStartedAt', DeploymentStartedAt = 'deploymentStartedAt',
@@ -13067,6 +13090,7 @@ export type Mutation_Root = {
billingUpgradeFreeOrganization: Scalars['String']; billingUpgradeFreeOrganization: Scalars['String'];
billingUploadReports: Scalars['Boolean']; billingUploadReports: Scalars['Boolean'];
changeDatabaseVersion: Scalars['Boolean']; changeDatabaseVersion: Scalars['Boolean'];
connectGithubRepo: Scalars['Boolean'];
/** delete single row from the table: "announcements_read" */ /** delete single row from the table: "announcements_read" */
deleteAnnouncementRead?: Maybe<Announcements_Read>; deleteAnnouncementRead?: Maybe<Announcements_Read>;
/** delete data from the table: "announcements_read" */ /** delete data from the table: "announcements_read" */
@@ -13968,6 +13992,15 @@ export type Mutation_RootChangeDatabaseVersionArgs = {
}; };
/** mutation root */
export type Mutation_RootConnectGithubRepoArgs = {
appID: Scalars['uuid'];
baseFolder: Scalars['String'];
githubNodeID: Scalars['String'];
productionBranch: Scalars['String'];
};
/** mutation root */ /** mutation root */
export type Mutation_RootDeleteAnnouncementReadArgs = { export type Mutation_RootDeleteAnnouncementReadArgs = {
id: Scalars['uuid']; id: Scalars['uuid'];
@@ -27517,6 +27550,16 @@ export type ResetDatabasePasswordMutationVariables = Exact<{
export type ResetDatabasePasswordMutation = { __typename?: 'mutation_root', resetPostgresPassword: boolean }; export type ResetDatabasePasswordMutation = { __typename?: 'mutation_root', resetPostgresPassword: boolean };
export type ConnectGithubRepoMutationVariables = Exact<{
appID: Scalars['uuid'];
githubNodeID: Scalars['String'];
productionBranch: Scalars['String'];
baseFolder: Scalars['String'];
}>;
export type ConnectGithubRepoMutation = { __typename?: 'mutation_root', connectGithubRepo: boolean };
export type GetHasuraSettingsQueryVariables = Exact<{ export type GetHasuraSettingsQueryVariables = Exact<{
appId: Scalars['uuid']; appId: Scalars['uuid'];
}>; }>;
@@ -29446,6 +29489,45 @@ export function useResetDatabasePasswordMutation(baseOptions?: Apollo.MutationHo
export type ResetDatabasePasswordMutationHookResult = ReturnType<typeof useResetDatabasePasswordMutation>; export type ResetDatabasePasswordMutationHookResult = ReturnType<typeof useResetDatabasePasswordMutation>;
export type ResetDatabasePasswordMutationResult = Apollo.MutationResult<ResetDatabasePasswordMutation>; export type ResetDatabasePasswordMutationResult = Apollo.MutationResult<ResetDatabasePasswordMutation>;
export type ResetDatabasePasswordMutationOptions = Apollo.BaseMutationOptions<ResetDatabasePasswordMutation, ResetDatabasePasswordMutationVariables>; export type ResetDatabasePasswordMutationOptions = Apollo.BaseMutationOptions<ResetDatabasePasswordMutation, ResetDatabasePasswordMutationVariables>;
export const ConnectGithubRepoDocument = gql`
mutation ConnectGithubRepo($appID: uuid!, $githubNodeID: String!, $productionBranch: String!, $baseFolder: String!) {
connectGithubRepo(
appID: $appID
githubNodeID: $githubNodeID
productionBranch: $productionBranch
baseFolder: $baseFolder
)
}
`;
export type ConnectGithubRepoMutationFn = Apollo.MutationFunction<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>;
/**
* __useConnectGithubRepoMutation__
*
* To run a mutation, you first call `useConnectGithubRepoMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useConnectGithubRepoMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [connectGithubRepoMutation, { data, loading, error }] = useConnectGithubRepoMutation({
* variables: {
* appID: // value for 'appID'
* githubNodeID: // value for 'githubNodeID'
* productionBranch: // value for 'productionBranch'
* baseFolder: // value for 'baseFolder'
* },
* });
*/
export function useConnectGithubRepoMutation(baseOptions?: Apollo.MutationHookOptions<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>(ConnectGithubRepoDocument, options);
}
export type ConnectGithubRepoMutationHookResult = ReturnType<typeof useConnectGithubRepoMutation>;
export type ConnectGithubRepoMutationResult = Apollo.MutationResult<ConnectGithubRepoMutation>;
export type ConnectGithubRepoMutationOptions = Apollo.BaseMutationOptions<ConnectGithubRepoMutation, ConnectGithubRepoMutationVariables>;
export const GetHasuraSettingsDocument = gql` export const GetHasuraSettingsDocument = gql`
query GetHasuraSettings($appId: uuid!) { query GetHasuraSettings($appId: uuid!) {
config(appID: $appId, resolve: false) { config(appID: $appId, resolve: false) {

View File

@@ -1665,6 +1665,7 @@ components:
- oauth-provider-error - oauth-provider-error
- invalid-otp - invalid-otp
- cannot-send-sms - cannot-send-sms
- provider-account-already-linked
required: required:
- status - status
- message - message

View File

@@ -254,7 +254,7 @@ Start local development environment
**--ca-certificates**="": Mounts and everrides path to CA certificates in the containers **--ca-certificates**="": Mounts and everrides path to CA certificates in the containers
**--dashboard-version**="": Dashboard version to use (default: nhost/dashboard:2.40.0) **--dashboard-version**="": Dashboard version to use (default: nhost/dashboard:2.41.0)
**--disable-tls**: Disable TLS **--disable-tls**: Disable TLS
@@ -284,7 +284,7 @@ Start local development environment connected to an Nhost Cloud project (BETA)
**--ca-certificates**="": Mounts and everrides path to CA certificates in the containers **--ca-certificates**="": Mounts and everrides path to CA certificates in the containers
**--dashboard-version**="": Dashboard version to use (default: nhost/dashboard:2.40.0) **--dashboard-version**="": Dashboard version to use (default: nhost/dashboard:2.41.0)
**--disable-tls**: Disable TLS **--disable-tls**: Disable TLS

View File

@@ -3203,6 +3203,7 @@ type ErrorResponseError =
| 'oauth-provider-error' | 'oauth-provider-error'
| 'invalid-otp' | 'invalid-otp'
| 'cannot-send-sms' | 'cannot-send-sms'
| 'provider-account-already-linked'
``` ```
Error code identifying the specific application error Error code identifying the specific application error

View File

@@ -206,8 +206,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -248,8 +247,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -391,8 +389,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Accept-Ranges: Accept-Ranges:
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges" description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
schema: schema:
@@ -675,8 +672,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -717,8 +713,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:

4
go.mod
View File

@@ -28,7 +28,7 @@ require (
github.com/jackc/pgx/v5 v5.7.2 github.com/jackc/pgx/v5 v5.7.2
github.com/lmittmann/tint v1.0.7 github.com/lmittmann/tint v1.0.7
github.com/mark3labs/mcp-go v0.41.1 github.com/mark3labs/mcp-go v0.41.1
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48 github.com/nhost/be v0.0.0-20251106114258-352de15d30f5
github.com/oapi-codegen/runtime v1.1.1 github.com/oapi-codegen/runtime v1.1.1
github.com/pb33f/libopenapi v0.21.12 github.com/pb33f/libopenapi v0.21.12
github.com/pelletier/go-toml/v2 v2.2.4 github.com/pelletier/go-toml/v2 v2.2.4
@@ -154,7 +154,7 @@ require (
github.com/prometheus/common v0.63.0 // indirect github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.54.0 // indirect github.com/quic-go/quic-go v0.54.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/cors v1.11.0 // indirect github.com/rs/cors v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect

8
go.sum
View File

@@ -336,8 +336,8 @@ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48 h1:+Oh4Rbr1psWlBaQTakoBYFNB8jBioiXuimNMaNPLTHk= github.com/nhost/be v0.0.0-20251106114258-352de15d30f5 h1:D1n4dI9LBk6W61/RIQClauPailPHXIp2V7okg6KQMlk=
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48/go.mod h1:feVvqP3dft8hWbp9zNZExdGKbFEYv8aLYohfyAeINNQ= github.com/nhost/be v0.0.0-20251106114258-352de15d30f5/go.mod h1:5aMnG2R3UQWFLlb3BcA0btBleWIn1ez3pSwg37YthuA=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
@@ -381,8 +381,8 @@ github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= github.com/quic-go/quic-go v0.54.1 h1:4ZAWm0AhCb6+hE+l5Q1NAL0iRn/ZrMwqHRGQiFwj2eg=
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= github.com/quic-go/quic-go v0.54.1/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=

View File

@@ -337,7 +337,8 @@ export type ErrorResponseError =
| "oauth-profile-fetch-failed" | "oauth-profile-fetch-failed"
| "oauth-provider-error" | "oauth-provider-error"
| "invalid-otp" | "invalid-otp"
| "cannot-send-sms"; | "cannot-send-sms"
| "provider-account-already-linked";
/** /**
* Standardized error response * Standardized error response

View File

@@ -1,3 +1,9 @@
## [auth@0.43.1] - 2025-11-11
### 🐛 Bug Fixes
- *(auth)* Return meaningful error if the provider's account is already linked (#3680)
## [auth@0.43.0] - 2025-11-04 ## [auth@0.43.0] - 2025-11-04
### 🚀 Features ### 🚀 Features

View File

@@ -1665,6 +1665,7 @@ components:
- oauth-provider-error - oauth-provider-error
- invalid-otp - invalid-otp
- cannot-send-sms - cannot-send-sms
- provider-account-already-linked
required: required:
- status - status
- message - message

View File

@@ -3785,147 +3785,147 @@ var swaggerSpec = []string{
"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",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View File

@@ -84,6 +84,7 @@ const (
OauthTokenEchangeFailed ErrorResponseError = "oauth-token-echange-failed" OauthTokenEchangeFailed ErrorResponseError = "oauth-token-echange-failed"
PasswordInHibpDatabase ErrorResponseError = "password-in-hibp-database" PasswordInHibpDatabase ErrorResponseError = "password-in-hibp-database"
PasswordTooShort ErrorResponseError = "password-too-short" PasswordTooShort ErrorResponseError = "password-too-short"
ProviderAccountAlreadyLinked ErrorResponseError = "provider-account-already-linked"
RedirectToNotAllowed ErrorResponseError = "redirectTo-not-allowed" RedirectToNotAllowed ErrorResponseError = "redirectTo-not-allowed"
RoleNotAllowed ErrorResponseError = "role-not-allowed" RoleNotAllowed ErrorResponseError = "role-not-allowed"
SignupDisabled ErrorResponseError = "signup-disabled" SignupDisabled ErrorResponseError = "signup-disabled"

View File

@@ -30,7 +30,7 @@ var (
ErrInvalidOTP = &APIError{api.InvalidRequest} ErrInvalidOTP = &APIError{api.InvalidRequest}
ErrUserProviderNotFound = &APIError{api.InvalidRequest} ErrUserProviderNotFound = &APIError{api.InvalidRequest}
ErrSecurityKeyNotFound = &APIError{api.InvalidRequest} ErrSecurityKeyNotFound = &APIError{api.InvalidRequest}
ErrUserProviderAlreadyLinked = &APIError{api.InvalidRequest} ErrProviderAccountAlreadyLinked = &APIError{api.ProviderAccountAlreadyLinked}
ErrEmailAlreadyInUse = &APIError{api.EmailAlreadyInUse} ErrEmailAlreadyInUse = &APIError{api.EmailAlreadyInUse}
ErrForbiddenAnonymous = &APIError{api.ForbiddenAnonymous} ErrForbiddenAnonymous = &APIError{api.ForbiddenAnonymous}
ErrInternalServerError = &APIError{api.InternalServerError} ErrInternalServerError = &APIError{api.InternalServerError}
@@ -271,14 +271,17 @@ func isSensitive(err api.ErrorResponseError) bool {
api.OauthTokenEchangeFailed, api.OauthTokenEchangeFailed,
api.OauthProfileFetchFailed, api.OauthProfileFetchFailed,
api.CannotSendSms, api.CannotSendSms,
api.OauthProviderError: api.OauthProviderError,
api.ProviderAccountAlreadyLinked:
return false return false
} }
return false return false
} }
func (ctrl *Controller) getError(err *APIError) ErrorResponse { //nolint:gocyclo,cyclop,funlen func (ctrl *Controller) getError( //nolint:gocyclo,cyclop,funlen,maintidx
err *APIError,
) ErrorResponse {
invalidRequest := ErrorResponse{ invalidRequest := ErrorResponse{
Status: http.StatusBadRequest, Status: http.StatusBadRequest,
Error: api.InvalidRequest, Error: api.InvalidRequest,
@@ -471,6 +474,12 @@ func (ctrl *Controller) getError(err *APIError) ErrorResponse { //nolint:gocyclo
Error: err.t, Error: err.t,
Message: "Invalid or expired OTP", Message: "Invalid or expired OTP",
} }
case api.ProviderAccountAlreadyLinked:
return ErrorResponse{
Status: http.StatusBadRequest,
Error: err.t,
Message: "This provider account is already linked to a user",
}
} }
return invalidRequest return invalidRequest

View File

@@ -332,8 +332,8 @@ func TestLinkIdToken(t *testing.T) { //nolint:maintidx
}, },
}, },
expectedResponse: controller.ErrorResponse{ expectedResponse: controller.ErrorResponse{
Error: "invalid-request", Error: "provider-account-already-linked",
Message: "The request payload is incorrect", Message: "This provider account is already linked to a user",
Status: 400, Status: 400,
}, },

View File

@@ -1006,7 +1006,7 @@ func TestSignInProviderCallback(t *testing.T) { //nolint:maintidx
}, },
expectedResponse: controller.ErrorRedirectResponse{ expectedResponse: controller.ErrorRedirectResponse{
Headers: struct{ Location string }{ Headers: struct{ Location string }{
Location: `^http://localhost:3000/connect-success\?error=invalid-request&errorDescription=The\+request\+payload\+is\+incorrect&state=some-random-state$`, //nolint:lll Location: `^http://localhost:3000/connect-success\?error=provider-account-already-linked&errorDescription=This\+provider\+account\+is\+already\+linked\+to\+a\+user&state=some-random-state$`, //nolint:lll
}, },
}, },
expectedJWT: nil, expectedJWT: nil,

View File

@@ -1180,7 +1180,7 @@ func (wf *Workflows) InsertUserProvider(
if err != nil { if err != nil {
if sqlIsDuplcateError(err, "user_providers_provider_id_provider_user_id_key") { if sqlIsDuplcateError(err, "user_providers_provider_id_provider_user_id_key") {
logger.ErrorContext(ctx, "user provider id already in use", logError(err)) logger.ErrorContext(ctx, "user provider id already in use", logError(err))
return sql.AuthUserProvider{}, ErrUserProviderAlreadyLinked return sql.AuthUserProvider{}, ErrProviderAccountAlreadyLinked
} }
logger.ErrorContext(ctx, "error inserting user provider", logError(err)) logger.ErrorContext(ctx, "error inserting user provider", logError(err))

View File

@@ -1,3 +1,9 @@
## [storage@0.9.1] - 2025-11-06
### 🐛 Bug Fixes
- *(storage)* Format date-time headers with RFC2822 (#3672)
## [storage@0.9.0] - 2025-11-04 ## [storage@0.9.0] - 2025-11-04
### 🚀 Features ### 🚀 Features

View File

@@ -6,7 +6,7 @@ import (
const format = time.RFC1123 const format = time.RFC1123
type Time time.Time type Time time.Time //nolint:recvcheck
func (dt *Time) UnmarshalText(text []byte) error { func (dt *Time) UnmarshalText(text []byte) error {
if len(text) == 0 || string(text) == `""` { if len(text) == 0 || string(text) == `""` {
@@ -24,6 +24,14 @@ func (dt *Time) UnmarshalText(text []byte) error {
return nil return nil
} }
func (dt Time) String() string {
if time.Time(dt).IsZero() {
return ""
}
return time.Time(dt).Format(format)
}
func Date(year int, month time.Month, day, hour, minute, sec, nsec int, loc *time.Location) Time { func Date(year int, month time.Month, day, hour, minute, sec, nsec int, loc *time.Location) Time {
return Time(time.Date(year, month, day, hour, minute, sec, nsec, loc)) return Time(time.Date(year, month, day, hour, minute, sec, nsec, loc))
} }

View File

@@ -16,7 +16,6 @@ import (
"net/url" "net/url"
"path" "path"
"strings" "strings"
"time"
"github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -998,7 +997,7 @@ type GetFile200ResponseHeaders struct {
ContentDisposition string ContentDisposition string
ContentType string ContentType string
Etag string Etag string
LastModified time.Time LastModified RFC2822Date
SurrogateControl string SurrogateControl string
SurrogateKey string SurrogateKey string
} }
@@ -1037,7 +1036,7 @@ type GetFile206ResponseHeaders struct {
ContentRange string ContentRange string
ContentType string ContentType string
Etag string Etag string
LastModified time.Time LastModified RFC2822Date
SurrogateControl string SurrogateControl string
SurrogateKey string SurrogateKey string
} }
@@ -1138,7 +1137,7 @@ type GetFileMetadataHeaders200ResponseHeaders struct {
ContentLength int ContentLength int
ContentType string ContentType string
Etag string Etag string
LastModified time.Time LastModified RFC2822Date
SurrogateControl string SurrogateControl string
SurrogateKey string SurrogateKey string
} }
@@ -1287,7 +1286,7 @@ type GetFileWithPresignedURL200ResponseHeaders struct {
ContentDisposition string ContentDisposition string
ContentType string ContentType string
Etag string Etag string
LastModified time.Time LastModified RFC2822Date
SurrogateControl string SurrogateControl string
SurrogateKey string SurrogateKey string
} }
@@ -1326,7 +1325,7 @@ type GetFileWithPresignedURL206ResponseHeaders struct {
ContentRange string ContentRange string
ContentType string ContentType string
Etag string Etag string
LastModified time.Time LastModified RFC2822Date
SurrogateControl string SurrogateControl string
SurrogateKey string SurrogateKey string
} }
@@ -2117,33 +2116,33 @@ var swaggerSpec = []string{
"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",
} }
// GetSwagger returns the content of the embedded swagger specification file // GetSwagger returns the content of the embedded swagger specification file

View File

@@ -272,7 +272,7 @@ func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
), ),
ContentType: file.mimeType, ContentType: file.mimeType,
Etag: file.fileMetadata.Etag, Etag: file.fileMetadata.Etag,
LastModified: file.fileMetadata.UpdatedAt, LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
SurrogateControl: file.cacheControl, SurrogateControl: file.cacheControl,
SurrogateKey: file.fileMetadata.Id, SurrogateKey: file.fileMetadata.Id,
}, },
@@ -290,7 +290,7 @@ func (ctrl *Controller) getFileResponse( //nolint: ireturn,dupl
ContentRange: file.extraHeaders.Get("Content-Range"), ContentRange: file.extraHeaders.Get("Content-Range"),
ContentType: file.mimeType, ContentType: file.mimeType,
Etag: file.fileMetadata.Etag, Etag: file.fileMetadata.Etag,
LastModified: file.fileMetadata.UpdatedAt, LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
SurrogateControl: file.cacheControl, SurrogateControl: file.cacheControl,
SurrogateKey: file.fileMetadata.Id, SurrogateKey: file.fileMetadata.Id,
}, },

View File

@@ -115,7 +115,7 @@ func (ctrl *Controller) getFileMetadataHeadersResponseObject( //nolint:ireturn
CacheControl: bucketMetadata.CacheControl, CacheControl: bucketMetadata.CacheControl,
ContentType: fileMetadata.MimeType, ContentType: fileMetadata.MimeType,
Etag: fileMetadata.Etag, Etag: fileMetadata.Etag,
LastModified: fileMetadata.UpdatedAt, LastModified: api.RFC2822Date(fileMetadata.UpdatedAt),
SurrogateControl: bucketMetadata.CacheControl, SurrogateControl: bucketMetadata.CacheControl,
SurrogateKey: fileMetadata.Id, SurrogateKey: fileMetadata.Id,
ContentDisposition: fmt.Sprintf( ContentDisposition: fmt.Sprintf(

View File

@@ -35,7 +35,7 @@ func TestGetFileInfo(t *testing.T) {
ContentLength: 64, ContentLength: 64,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -57,7 +57,7 @@ func TestGetFileInfo(t *testing.T) {
ContentLength: 64, ContentLength: 64,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -111,7 +111,7 @@ func TestGetFileInfo(t *testing.T) {
ContentLength: 64, ContentLength: 64,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -133,7 +133,7 @@ func TestGetFileInfo(t *testing.T) {
ContentLength: 64, ContentLength: 64,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -171,7 +171,7 @@ func TestGetFileInfo(t *testing.T) {
ContentLength: 64, ContentLength: 64,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },

View File

@@ -40,7 +40,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
ContentDisposition: `inline; filename="my-file.txt"`, ContentDisposition: `inline; filename="my-file.txt"`,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -62,7 +62,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
ContentDisposition: `inline; filename="my-file.txt"`, ContentDisposition: `inline; filename="my-file.txt"`,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -116,7 +116,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
ContentDisposition: `inline; filename="my-file.txt"`, ContentDisposition: `inline; filename="my-file.txt"`,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -138,7 +138,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
ContentDisposition: `inline; filename="my-file.txt"`, ContentDisposition: `inline; filename="my-file.txt"`,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },
@@ -176,7 +176,7 @@ func TestGetFile(t *testing.T) { //nolint:maintidx
ContentDisposition: `inline; filename="my-file.txt"`, ContentDisposition: `inline; filename="my-file.txt"`,
ContentType: "text/plain; charset=utf-8", ContentType: "text/plain; charset=utf-8",
Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`, Etag: `"55af1e60-0f28-454e-885e-ea6aab2bb288"`,
LastModified: time.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC), LastModified: api.Date(2021, 12, 27, 9, 58, 11, 0, time.UTC),
SurrogateControl: "max-age=3600", SurrogateControl: "max-age=3600",
SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288", SurrogateKey: "55af1e60-0f28-454e-885e-ea6aab2bb288",
}, },

View File

@@ -99,7 +99,7 @@ func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn
), ),
ContentType: file.mimeType, ContentType: file.mimeType,
Etag: file.fileMetadata.Etag, Etag: file.fileMetadata.Etag,
LastModified: file.fileMetadata.UpdatedAt, LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
SurrogateControl: file.cacheControl, SurrogateControl: file.cacheControl,
SurrogateKey: file.fileMetadata.Id, SurrogateKey: file.fileMetadata.Id,
}, },
@@ -117,7 +117,7 @@ func (ctrl *Controller) getFileWithPresignedURLResponseObject( //nolint: ireturn
ContentRange: file.extraHeaders.Get("Content-Range"), ContentRange: file.extraHeaders.Get("Content-Range"),
ContentType: file.mimeType, ContentType: file.mimeType,
Etag: file.fileMetadata.Etag, Etag: file.fileMetadata.Etag,
LastModified: file.fileMetadata.UpdatedAt, LastModified: api.RFC2822Date(file.fileMetadata.UpdatedAt),
SurrogateControl: file.cacheControl, SurrogateControl: file.cacheControl,
SurrogateKey: file.fileMetadata.Id, SurrogateKey: file.fileMetadata.Id,
}, },

View File

@@ -64,6 +64,10 @@ func FileMetadataMatcher(v api.FileMetadata) gomock.Matcher {
func assert(t *testing.T, got, wanted interface{}, opts ...cmp.Option) { func assert(t *testing.T, got, wanted interface{}, opts ...cmp.Option) {
t.Helper() t.Helper()
opts = append(opts, cmpopts.IgnoreUnexported(
api.Time{},
))
if !cmp.Equal(got, wanted, opts...) { if !cmp.Equal(got, wanted, opts...) {
t.Error(cmp.Diff(got, wanted, opts...)) t.Error(cmp.Diff(got, wanted, opts...))
} }

View File

@@ -206,8 +206,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -248,8 +247,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -391,8 +389,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Accept-Ranges: Accept-Ranges:
description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges" description: "Always set to bytes. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Ranges"
schema: schema:
@@ -675,8 +672,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:
@@ -717,8 +713,7 @@ paths:
Last-Modified: Last-Modified:
description: "Date and time the file was last modified" description: "Date and time the file was last modified"
schema: schema:
type: string $ref: '#/components/schemas/RFC2822Date'
format: date-time
Surrogate-Key: Surrogate-Key:
description: "Cache key for surrogate caching" description: "Cache key for surrogate caching"
schema: schema:

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
URL=http://localhost:8000/v1/files URL=http://localhost:8000/v1/files
AUTH="Authorization: Bearer $(make dev-jwt)"
# token can be generated using build/dev/jwt-gen
AUTH="Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwNzc3NzY0NjYsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtYWxsb3dlZC1yb2xlcyI6WyJhZG1pbiJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJhZG1pbiIsIngtaGFzdXJhLXVzZXItaWQiOiJhYjViYTU4ZS05MzJhLTQwZGMtODdlOC03MzM5OTg3OTRlYzIiLCJ4LWhhc3VyYS11c2VyLWlzQW5vbnltb3VzIjoiZmFsc2UifSwiaWF0IjoxNzYyNDE2NDY2LCJpc3MiOiJoYXN1cmEtYXV0aCIsInN1YiI6ImFiNWJhNThlLTkzMmEtNDBkYy04N2U4LTczMzk5ODc5NGVjMiJ9.msexXYDRzox0giNGRHqPrefYH_uWXMtdCbEZ_Vg-IV8"
BUCKET=default BUCKET=default
FILE_ID=55af1e60-0f28-454e-885e-ea6aab2bb288 FILE_ID=55af1e60-0f28-454e-885e-ea6aab2bb288
@@ -11,14 +13,12 @@ ETAG=\"588be441fe7a59460850b0aa3e1c5a65\"
# lead to a JWTIssuedAtFuture error # lead to a JWTIssuedAtFuture error
sleep 1 sleep 1
output=`curl $URL/ \ output=`curl $URL \
-v \ -v \
-H "$AUTH" \ -H "$AUTH" \
-F "bucket-id=$BUCKET" \ -F "bucket-id=$BUCKET" \
-F "metadata[]={};type=application/json" \
-F "file[]=@go.mod" \
-F "metadata[]={\"id\":\"7982873d-8e89-4321-ab86-00f80a168c5a\", \"name\":\"config.yaml\",\"metadata\":{\"num\":123,\"list\":[1,2,3]}};type=application/json" \ -F "metadata[]={\"id\":\"7982873d-8e89-4321-ab86-00f80a168c5a\", \"name\":\"config.yaml\",\"metadata\":{\"num\":123,\"list\":[1,2,3]}};type=application/json" \
-F "file[]=@storage.yaml" \ -F "file[]=@vacuum.yaml" \
-F "metadata[]={\"id\":\"faa80d51-07c7-4268-942d-8f092c98c71a\", \"name\":\"docs.md\"};type=application/json" \ -F "metadata[]={\"id\":\"faa80d51-07c7-4268-942d-8f092c98c71a\", \"name\":\"docs.md\"};type=application/json" \
-F "file[]=@README.md" \ -F "file[]=@README.md" \
-F "metadata[]={\"id\":\"$FILE_ID\", \"name\":\"logo.jpg\"};type=application/json" \ -F "metadata[]={\"id\":\"$FILE_ID\", \"name\":\"logo.jpg\"};type=application/json" \

View File

@@ -43,6 +43,24 @@ func nameMustBeUnique(svcs Services, serviceID, name string) error {
return nil return nil
} }
func (r *mutationResolver) checkAppLive(ctx context.Context, appID string) error {
appIDUUID, err := uuid.Parse(appID)
if err != nil {
return fmt.Errorf("invalid app ID: %w", err)
}
desiredState, err := r.nhost.GetAppDesiredState(ctx, appIDUUID)
if err != nil {
return fmt.Errorf("failed to get app desired state: %w", err)
}
if desiredState != appLive {
return ErrAppMustBeLive
}
return nil
}
func (r *mutationResolver) insertRunServiceConfig( func (r *mutationResolver) insertRunServiceConfig(
ctx context.Context, ctx context.Context,
appID string, appID string,
@@ -58,6 +76,10 @@ func (r *mutationResolver) insertRunServiceConfig(
app := r.data[i] app := r.data[i]
if err := r.checkAppLive(ctx, appID); err != nil {
return nil, err
}
serviceID := uuid.NewString() serviceID := uuid.NewString()
if _, err := app.IndexService(serviceID); err == nil { if _, err := app.IndexService(serviceID); err == nil {

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.30.0
package nhost package nhost

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.30.0
package nhost package nhost
@@ -309,6 +309,7 @@ type Deployment struct {
CommitUserName pgtype.Text CommitUserName pgtype.Text
CommitUserAvatarUrl pgtype.Text CommitUserAvatarUrl pgtype.Text
CommitMessage pgtype.Text CommitMessage pgtype.Text
CreatedAt pgtype.Timestamptz
} }
type DeploymentLog struct { type DeploymentLog struct {

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.30.0
package nhost package nhost

View File

@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT. // Code generated by sqlc. DO NOT EDIT.
// versions: // versions:
// sqlc v1.29.0 // sqlc v1.30.0
// source: query.sql // source: query.sql
package nhost package nhost

View File

@@ -42,7 +42,11 @@ func IsJWTSecretCompatibleWithHasuraAuth(
} }
func getOauthSettings(c oauthsettings, provider string) []EnvVar { func getOauthSettings(c oauthsettings, provider string) []EnvVar {
return []EnvVar{ if !unptr(c.GetEnabled()) {
return []EnvVar{}
}
env := []EnvVar{
{ {
Name: fmt.Sprintf("AUTH_PROVIDER_%s_ENABLED", provider), Name: fmt.Sprintf("AUTH_PROVIDER_%s_ENABLED", provider),
Value: Stringify(unptr(c.GetEnabled())), Value: Stringify(unptr(c.GetEnabled())),
@@ -61,22 +65,30 @@ func getOauthSettings(c oauthsettings, provider string) []EnvVar {
Value: unptr(c.GetClientSecret()), Value: unptr(c.GetClientSecret()),
IsSecret: false, IsSecret: false,
}, },
{ }
if c.GetAudience() != nil {
env = append(env, EnvVar{
Name: fmt.Sprintf("AUTH_PROVIDER_%s_AUDIENCE", provider), Name: fmt.Sprintf("AUTH_PROVIDER_%s_AUDIENCE", provider),
Value: unptr(c.GetAudience()), Value: unptr(c.GetAudience()),
IsSecret: false, IsSecret: false,
SecretName: "", SecretName: "",
}, })
{ }
if c.GetScope() != nil {
env = append(env, EnvVar{
Name: fmt.Sprintf("AUTH_PROVIDER_%s_SCOPE", provider), Name: fmt.Sprintf("AUTH_PROVIDER_%s_SCOPE", provider),
Value: Stringify(c.GetScope()), Value: Stringify(c.GetScope()),
IsSecret: false, IsSecret: false,
SecretName: "", SecretName: "",
}, })
} }
return env
} }
func HasuraAuthEnv( //nolint:funlen,cyclop,maintidx,gocyclo,gocognit func HasuraAuthEnv( //nolint:funlen,cyclop,maintidx
config *model.ConfigConfig, config *model.ConfigConfig,
hasuraGraphqlURL, hasuraGraphqlURL,
authServerURL, authServerURL,
@@ -584,116 +596,45 @@ func HasuraAuthEnv( //nolint:funlen,cyclop,maintidx,gocyclo,gocognit
}...) }...)
} }
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetGithub().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetGithub(),
) { "GITHUB")...,
env = append(env, []EnvVar{ )
{
Name: "AUTH_PROVIDER_GITHUB_ENABLED",
Value: Stringify(
unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetGithub().
GetEnabled(),
),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_GITHUB_CLIENT_ID",
Value: unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetGithub().
GetClientId(),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_GITHUB_CLIENT_SECRET",
SecretName: "",
Value: unptr(
config.GetAuth().GetMethod().GetOauth().GetGithub().GetClientSecret(),
),
IsSecret: false,
},
}...)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetGoogle().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetGoogle(),
) { "GOOGLE")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetGoogle(),
"GOOGLE")...,
)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetFacebook().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetFacebook(),
) { "FACEBOOK")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetFacebook(),
"FACEBOOK")...,
)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetSpotify().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetSpotify(),
) { "SPOTIFY")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetSpotify(),
"SPOTIFY")...,
)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetLinkedin().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetLinkedin(),
) { "LINKEDIN")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetLinkedin(),
"LINKEDIN")...,
)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetDiscord().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetDiscord(),
) { "DISCORD")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetDiscord(),
"DISCORD")...,
)
}
if unptr( env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetTwitch().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetTwitch(),
) { "TWITCH")...,
env = append(env, getOauthSettings( )
config.GetAuth().GetMethod().GetOauth().GetTwitch(),
"TWITCH")...,
)
}
if unptr( env = append(env, getOauthSettings(
config. config.GetAuth().GetMethod().GetOauth().GetWindowslive(),
GetAuth(). "WINDOWS_LIVE")...,
GetMethod(). )
GetOauth().
GetWindowslive().
GetEnabled(),
) {
env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetWindowslive(),
"WINDOWS_LIVE")...,
)
}
if unptr( if unptr(
config.GetAuth().GetMethod().GetOauth().GetWorkos().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetWorkos().GetEnabled(),
@@ -876,6 +817,17 @@ func HasuraAuthEnv( //nolint:funlen,cyclop,maintidx,gocyclo,gocognit
SecretName: "", SecretName: "",
}, },
}...) }...)
if config.GetAuth().GetMethod().GetOauth().GetApple().GetScope() != nil {
env = append(env, EnvVar{
Name: "AUTH_PROVIDER_APPLE_SCOPE",
Value: Stringify(
config.GetAuth().GetMethod().GetOauth().GetApple().GetScope(),
),
IsSecret: false,
SecretName: "",
})
}
} }
if unptr( //nolint:dupl if unptr( //nolint:dupl
@@ -990,117 +942,15 @@ func HasuraAuthEnv( //nolint:funlen,cyclop,maintidx,gocyclo,gocognit
}...) }...)
} }
if unptr( //nolint:dupl env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetGitlab().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetGitlab(),
) { "GITLAB")...,
env = append(env, []EnvVar{ )
{
Name: "AUTH_PROVIDER_GITLAB_ENABLED",
Value: Stringify(
unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetGitlab().
GetEnabled(),
),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_GITLAB_CLIENT_ID",
Value: unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetGitlab().
GetClientId(),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_GITLAB_CLIENT_SECRET",
SecretName: "",
Value: unptr(
config.GetAuth().GetMethod().GetOauth().GetGitlab().GetClientSecret(),
),
IsSecret: false,
},
{
Name: "AUTH_PROVIDER_GITLAB_SCOPE",
Value: Stringify(
config.
GetAuth().
GetMethod().
GetOauth().
GetGitlab().
GetScope(),
),
IsSecret: false,
SecretName: "",
},
}...)
}
if unptr( //nolint:dupl env = append(env, getOauthSettings(
config.GetAuth().GetMethod().GetOauth().GetStrava().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetStrava(),
) { "STRAVA")...,
env = append(env, []EnvVar{ )
{
Name: "AUTH_PROVIDER_STRAVA_ENABLED",
Value: Stringify(
unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetStrava().
GetEnabled(),
),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_STRAVA_CLIENT_ID",
Value: unptr(
config.
GetAuth().
GetMethod().
GetOauth().
GetStrava().
GetClientId(),
),
IsSecret: false,
SecretName: "",
},
{
Name: "AUTH_PROVIDER_STRAVA_CLIENT_SECRET",
SecretName: "",
Value: unptr(
config.GetAuth().GetMethod().GetOauth().GetStrava().GetClientSecret(),
),
IsSecret: false,
},
{
Name: "AUTH_PROVIDER_STRAVA_SCOPE",
Value: Stringify(
config.
GetAuth().
GetMethod().
GetOauth().
GetStrava().
GetScope(),
),
IsSecret: false,
SecretName: "",
},
}...)
}
if unptr( if unptr(
config.GetAuth().GetMethod().GetOauth().GetBitbucket().GetEnabled(), config.GetAuth().GetMethod().GetOauth().GetBitbucket().GetEnabled(),

View File

@@ -223,7 +223,7 @@ import (
// Releases: // Releases:
// //
// https://github.com/nhost/hasura-storage/releases // https://github.com/nhost/hasura-storage/releases
version: string | *"0.8.2" version: string | *"0.9.1"
// Networking (custom domains at the moment) are not allowed as we need to do further // Networking (custom domains at the moment) are not allowed as we need to do further
// configurations in the CDN. We will enable it again in the future. // configurations in the CDN. We will enable it again in the future.
@@ -311,7 +311,7 @@ import (
// Releases: // Releases:
// //
// https://github.com/nhost/hasura-auth/releases // https://github.com/nhost/hasura-auth/releases
version: string | *"0.42.4" version: string | *"0.43.0"
// Resources for the service // Resources for the service
resources?: #Resources resources?: #Resources

View File

@@ -1,4 +1,4 @@
package types //nolint: dupl package types //nolint: dupl,revive,nolintlint
import ( import (
"encoding/json" "encoding/json"

View File

@@ -845,6 +845,9 @@ func (c *Conn) handleHandshakeComplete(now time.Time) error {
} }
func (c *Conn) handleHandshakeConfirmed(now time.Time) error { func (c *Conn) handleHandshakeConfirmed(now time.Time) error {
if err := c.dropEncryptionLevel(protocol.EncryptionInitial, now); err != nil {
return err
}
if err := c.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil { if err := c.dropEncryptionLevel(protocol.EncryptionHandshake, now); err != nil {
return err return err
} }

6
vendor/modules.txt vendored
View File

@@ -716,8 +716,8 @@ github.com/muesli/termenv
# github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 # github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
## explicit ## explicit
github.com/munnerz/goautoneg github.com/munnerz/goautoneg
# github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48 # github.com/nhost/be v0.0.0-20251106114258-352de15d30f5
## explicit; go 1.24.2 ## explicit; go 1.25.3
github.com/nhost/be/lib/graphql github.com/nhost/be/lib/graphql
github.com/nhost/be/lib/graphql/context github.com/nhost/be/lib/graphql/context
github.com/nhost/be/lib/graphql/handler github.com/nhost/be/lib/graphql/handler
@@ -811,7 +811,7 @@ github.com/prometheus/procfs/internal/util
# github.com/quic-go/qpack v0.5.1 # github.com/quic-go/qpack v0.5.1
## explicit; go 1.22 ## explicit; go 1.22
github.com/quic-go/qpack github.com/quic-go/qpack
# github.com/quic-go/quic-go v0.54.0 # github.com/quic-go/quic-go v0.54.1
## explicit; go 1.23 ## explicit; go 1.23
github.com/quic-go/quic-go github.com/quic-go/quic-go
github.com/quic-go/quic-go/http3 github.com/quic-go/quic-go/http3