Árni Björn Vigfússon hai 5 horas
achega
ee7983d6c1
Modificáronse 100 ficheiros con 11820 adicións e 0 borrados
  1. 14 0
      Dockerfile
  2. 1 0
      backend/cmd/api/.env
  3. 13 0
      backend/cmd/api/credentials.json
  4. 39 0
      backend/cmd/api/main.go
  5. 13 0
      backend/credentials.json
  6. BIN=BIN
      backend/dimesheet
  7. 39 0
      backend/go.mod
  8. 168 0
      backend/go.sum
  9. 2 0
      backend/helpful.txt
  10. 41 0
      backend/internal/handlers/handlers.go
  11. 97 0
      backend/internal/services/calendar.go
  12. 9 0
      backend/models/client.go
  13. 21 0
      backend/models/models.go
  14. 1 0
      backend/read.me
  15. 16 0
      backend/readme.md
  16. 281 0
      backend/src/main.go
  17. 4 0
      backend/src/readme.md.md
  18. 2 0
      backend/src/run.sh
  19. BIN=BIN
      backend/src/src
  20. 29 0
      backend/static/index.html
  21. 329 0
      backend/vendor/cloud.google.com/go/auth/CHANGES.md
  22. 202 0
      backend/vendor/cloud.google.com/go/auth/LICENSE
  23. 40 0
      backend/vendor/cloud.google.com/go/auth/README.md
  24. 608 0
      backend/vendor/cloud.google.com/go/auth/auth.go
  25. 86 0
      backend/vendor/cloud.google.com/go/auth/credentials/compute.go
  26. 262 0
      backend/vendor/cloud.google.com/go/auth/credentials/detect.go
  27. 45 0
      backend/vendor/cloud.google.com/go/auth/credentials/doc.go
  28. 225 0
      backend/vendor/cloud.google.com/go/auth/credentials/filetypes.go
  29. 520 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
  30. 284 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
  31. 407 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
  32. 78 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
  33. 74 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
  34. 30 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
  35. 88 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
  36. 63 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
  37. 110 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
  38. 184 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
  39. 146 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
  40. 161 0
      backend/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
  41. 85 0
      backend/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
  42. 232 0
      backend/vendor/cloud.google.com/go/auth/httptransport/httptransport.go
  43. 93 0
      backend/vendor/cloud.google.com/go/auth/httptransport/trace.go
  44. 248 0
      backend/vendor/cloud.google.com/go/auth/httptransport/transport.go
  45. 107 0
      backend/vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go
  46. 157 0
      backend/vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go
  47. 98 0
      backend/vendor/cloud.google.com/go/auth/internal/credsfile/parse.go
  48. 216 0
      backend/vendor/cloud.google.com/go/auth/internal/internal.go
  49. 171 0
      backend/vendor/cloud.google.com/go/auth/internal/jwt/jwt.go
  50. 364 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/cba.go
  51. 65 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go
  52. 54 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go
  53. 124 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go
  54. 114 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go
  55. 134 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/s2a.go
  56. 105 0
      backend/vendor/cloud.google.com/go/auth/internal/transport/transport.go
  57. 61 0
      backend/vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md
  58. 202 0
      backend/vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE
  59. 195 0
      backend/vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go
  60. 368 0
      backend/vendor/cloud.google.com/go/auth/threelegged.go
  61. 59 0
      backend/vendor/cloud.google.com/go/compute/metadata/CHANGES.md
  62. 202 0
      backend/vendor/cloud.google.com/go/compute/metadata/LICENSE
  63. 27 0
      backend/vendor/cloud.google.com/go/compute/metadata/README.md
  64. 839 0
      backend/vendor/cloud.google.com/go/compute/metadata/metadata.go
  65. 114 0
      backend/vendor/cloud.google.com/go/compute/metadata/retry.go
  66. 31 0
      backend/vendor/cloud.google.com/go/compute/metadata/retry_linux.go
  67. 26 0
      backend/vendor/cloud.google.com/go/compute/metadata/syscheck.go
  68. 28 0
      backend/vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go
  69. 38 0
      backend/vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go
  70. 3 0
      backend/vendor/github.com/a-h/templ/.dockerignore
  71. 1 0
      backend/vendor/github.com/a-h/templ/.envrc
  72. 31 0
      backend/vendor/github.com/a-h/templ/.gitignore
  73. 72 0
      backend/vendor/github.com/a-h/templ/.goreleaser.yaml
  74. 7 0
      backend/vendor/github.com/a-h/templ/.ignore
  75. 1 0
      backend/vendor/github.com/a-h/templ/.version
  76. 128 0
      backend/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md
  77. 244 0
      backend/vendor/github.com/a-h/templ/CONTRIBUTING.md
  78. 21 0
      backend/vendor/github.com/a-h/templ/LICENSE
  79. 183 0
      backend/vendor/github.com/a-h/templ/README.md
  80. 9 0
      backend/vendor/github.com/a-h/templ/SECURITY.md
  81. 4 0
      backend/vendor/github.com/a-h/templ/cosign.pub
  82. 140 0
      backend/vendor/github.com/a-h/templ/flake.lock
  83. 93 0
      backend/vendor/github.com/a-h/templ/flake.nix
  84. 36 0
      backend/vendor/github.com/a-h/templ/flush.go
  85. 90 0
      backend/vendor/github.com/a-h/templ/gomod2nix.toml
  86. 102 0
      backend/vendor/github.com/a-h/templ/handler.go
  87. BIN=BIN
      backend/vendor/github.com/a-h/templ/ide-demo.gif
  88. 19 0
      backend/vendor/github.com/a-h/templ/join.go
  89. 85 0
      backend/vendor/github.com/a-h/templ/jsonscript.go
  90. 14 0
      backend/vendor/github.com/a-h/templ/jsonstring.go
  91. 64 0
      backend/vendor/github.com/a-h/templ/once.go
  92. 14 0
      backend/vendor/github.com/a-h/templ/push-tag.sh
  93. 730 0
      backend/vendor/github.com/a-h/templ/runtime.go
  94. 62 0
      backend/vendor/github.com/a-h/templ/runtime/buffer.go
  95. 38 0
      backend/vendor/github.com/a-h/templ/runtime/bufferpool.go
  96. 8 0
      backend/vendor/github.com/a-h/templ/runtime/builder.go
  97. 21 0
      backend/vendor/github.com/a-h/templ/runtime/runtime.go
  98. 168 0
      backend/vendor/github.com/a-h/templ/safehtml/style.go
  99. 143 0
      backend/vendor/github.com/a-h/templ/scripttemplate.go
  100. BIN=BIN
      backend/vendor/github.com/a-h/templ/templ.png

+ 14 - 0
Dockerfile

@@ -0,0 +1,14 @@
+FROM nginx:latest
+COPY ./dimesheet.html /usr/share/nginx/html/index2.html
+COPY ./index.html /usr/share/nginx/html/index.html
+COPY ./dimesheet_lucinity.html /usr/share/nginx/html/lucinity.html
+
+COPY ./dimesheet.html /etc/nginx/html/index.html
+COPY ./dimesheet_lucinity.html /etc/nginx/html/lucinity.html
+COPY ./dimesheet_marel.html /etc/nginx/html/marel.html
+# COPY ./sheets.html /etc/nginx/html/sheets.html
+#COPY nginx.conf /etc/nginx/sites-enabled/dime.asterisk.io.conf
+#CMD [ "ln","-s","/etc/nginx/sites-available/dime.asterisk.io.conf", "/etc/nginx/sites-enabled/" ]
+COPY nginx.conf /etc/nginx/nginx.conf
+EXPOSE 8000
+

+ 1 - 0
backend/cmd/api/.env

@@ -0,0 +1 @@
+PORT=8765

+ 13 - 0
backend/cmd/api/credentials.json

@@ -0,0 +1,13 @@
+{
+  "type": "service_account",
+  "project_id": "dimesheet-asterisk-is",
+  "private_key_id": "bdefb62307cea792fd15d158deab8231dba8f8d5",
+  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCufTHXNXtTrpEb\n3DNnS+7k22zZ52iDlhqC8tfayuWSOhUkNtj5PVQYHhlrseMm8qqevCpfp93r0TzG\n3/J3TbzYn6RkqeD/Pyb1ncMpDDwLq+CHjPR811SYEIW0mFTWX9uKh8Zw9TmklK4D\nvJ9JpHoE7sp5BzGBsXX8IvPszj+iaTBe/tZeeMUj/k+pyJYEmyhQH+bsDq2CCVhi\n+TNjniMO/kdbgxNRLyA9C7/S3Ffmij5f53oqtnZprQbH+WALHVxIInxjNkhRIV5X\nuhNPalDVjkZD68ck4C0weEwSnF5zYdiFTqvvYy8gPDK44ahg56JSZcuDj17YHVGS\nhQRTbCkxAgMBAAECggEACfqlfXwfdJVPfbh9z8V3NmtOgfYlffRj9ENssQ85CsBG\nd2wpQ7SBnnZ8ODKefdqo2OFZ4Aodm5q7adlNczbE2zO4HGddyONE+GJAfX9ntg2A\npBKdjz/mZI2krpkmrZ2iVJLNeHuB2NswSjeWTt54pFcUHntY62SpjX+3PmlmQ29o\nnPitXP+lnPym3M3TiajQO3t1BbqZ1JNZ3rIJDBK5ZjA8tLZH2/4Bt55mTAVsf+Jq\nNGzxEZ60RDMIRYnQD8uDJmrmCmEAD96eL4/F1OGU1fqHcx60XjHKN9WCI1sWWCa2\npdRP6gO6dnToRhTADwQiIggsgRFiDwTdnt+bVW0j1QKBgQDZa0NnbgZAqlOf+4sy\n93dbXX6Qqh9tEANMpVV+FZhSl5LlluQU6pU4ranZUG4lyWQnSLHmaLGT/6VYHP+A\nJlC3xT208PcsakcGltt4XnkTnvTT97NXy5JZMRNir81GJ7HI0u/Ou5EKWQHcNl17\nb/qWBchV2Pdi/+Sj1c7WOU6/PQKBgQDNc75ex5Rb5R2QDefw6igmm4hjtLlaIEnT\n2Bn3m1gl6AW6WOea1pom7IshwsXEskqHWAR5c65pODkz8qiT1plXK5V3fGuw8dfF\nofdGbK6qFmvn1aZWtdMluqUCR6BqB9lURjkzrx5F5FDvilFw0gSM+U5YzFNGblG8\nXaYWK+LxBQKBgQCy1HA3lZ7c93wbHdqLzFJHOyqMMPHyUfZ0x5T/RSHp5TR1VVw6\n/AxK90S6M1a2C+UuDrQNEVXmpqLIS+m37Uv9K+YqevzDcpMDD3hrlEFAa0FzS80e\nzJmOdTg9tt4/8wxfY2jOASc2fKl0CN9xVVSmqxDx06j/q/zS6VzchXyI/QKBgQCD\nz2QtbqHyKxZCW4mY+MF1s4dXYJUiC2ESNqzCPLna5/b0zSEaJ+CSZGQG7uzu+uvx\n1ThZqywUeWvJtj8NSjrqwB2OoRZ+WUpzkywP1J5ipSvpexrSuqiSpiJDSizSmeKG\nvN7qF8m2UXrTk8wYM8WTpEbtlXk/0HtD/FkYFGPu+QKBgBPOwLwmiPzgEesAaFXH\nayvs+2FYb78zt1fLNwmQUM5r/s23ovOFvUWXU1hN6tx50CGv1crOanQwDXmtXtqk\n+dObBWm7439HuuBo3WXstq6+bmyZqX+U5irdkLY877PBQdS7IVcgAW+7V5qON57o\nzHIOMCRI0ErWS3nENGHKw57J\n-----END PRIVATE KEY-----\n",
+  "client_email": "[email protected]",
+  "client_id": "102952992735066039371",
+  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+  "token_uri": "https://oauth2.googleapis.com/token",
+  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dimesheet-api%40dimesheet-asterisk-is.iam.gserviceaccount.com",
+  "universe_domain": "googleapis.com"
+}

+ 39 - 0
backend/cmd/api/main.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+
+	"asterisk.is/dimesheet/internal/handlers"
+	"github.com/gorilla/mux"
+	"github.com/joho/godotenv"
+)
+
+func getEnv(key, defaultVal string) string {
+	if val, ok := os.LookupEnv(key); ok && val != "" {
+		return val
+	}
+	return defaultVal
+}
+
+func main() {
+
+	// Load .env file
+	err := godotenv.Load()
+	if err != nil {
+		fmt.Println("Error loading .env file")
+	}
+
+	r := mux.NewRouter()
+
+	r.HandleFunc("/api", handlers.RootHandler)
+	r.HandleFunc("/api/clients", handlers.GetClients)
+	r.HandleFunc("/api/events", handlers.EventsHandler)
+
+	// Start the server
+	port := ":" + getEnv("PORT", "8321")
+	fmt.Printf("Starting server at %s\n", port)
+	log.Fatal(http.ListenAndServe(port, r))
+}

+ 13 - 0
backend/credentials.json

@@ -0,0 +1,13 @@
+{
+  "type": "service_account",
+  "project_id": "dimesheet-asterisk-is",
+  "private_key_id": "bdefb62307cea792fd15d158deab8231dba8f8d5",
+  "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCufTHXNXtTrpEb\n3DNnS+7k22zZ52iDlhqC8tfayuWSOhUkNtj5PVQYHhlrseMm8qqevCpfp93r0TzG\n3/J3TbzYn6RkqeD/Pyb1ncMpDDwLq+CHjPR811SYEIW0mFTWX9uKh8Zw9TmklK4D\nvJ9JpHoE7sp5BzGBsXX8IvPszj+iaTBe/tZeeMUj/k+pyJYEmyhQH+bsDq2CCVhi\n+TNjniMO/kdbgxNRLyA9C7/S3Ffmij5f53oqtnZprQbH+WALHVxIInxjNkhRIV5X\nuhNPalDVjkZD68ck4C0weEwSnF5zYdiFTqvvYy8gPDK44ahg56JSZcuDj17YHVGS\nhQRTbCkxAgMBAAECggEACfqlfXwfdJVPfbh9z8V3NmtOgfYlffRj9ENssQ85CsBG\nd2wpQ7SBnnZ8ODKefdqo2OFZ4Aodm5q7adlNczbE2zO4HGddyONE+GJAfX9ntg2A\npBKdjz/mZI2krpkmrZ2iVJLNeHuB2NswSjeWTt54pFcUHntY62SpjX+3PmlmQ29o\nnPitXP+lnPym3M3TiajQO3t1BbqZ1JNZ3rIJDBK5ZjA8tLZH2/4Bt55mTAVsf+Jq\nNGzxEZ60RDMIRYnQD8uDJmrmCmEAD96eL4/F1OGU1fqHcx60XjHKN9WCI1sWWCa2\npdRP6gO6dnToRhTADwQiIggsgRFiDwTdnt+bVW0j1QKBgQDZa0NnbgZAqlOf+4sy\n93dbXX6Qqh9tEANMpVV+FZhSl5LlluQU6pU4ranZUG4lyWQnSLHmaLGT/6VYHP+A\nJlC3xT208PcsakcGltt4XnkTnvTT97NXy5JZMRNir81GJ7HI0u/Ou5EKWQHcNl17\nb/qWBchV2Pdi/+Sj1c7WOU6/PQKBgQDNc75ex5Rb5R2QDefw6igmm4hjtLlaIEnT\n2Bn3m1gl6AW6WOea1pom7IshwsXEskqHWAR5c65pODkz8qiT1plXK5V3fGuw8dfF\nofdGbK6qFmvn1aZWtdMluqUCR6BqB9lURjkzrx5F5FDvilFw0gSM+U5YzFNGblG8\nXaYWK+LxBQKBgQCy1HA3lZ7c93wbHdqLzFJHOyqMMPHyUfZ0x5T/RSHp5TR1VVw6\n/AxK90S6M1a2C+UuDrQNEVXmpqLIS+m37Uv9K+YqevzDcpMDD3hrlEFAa0FzS80e\nzJmOdTg9tt4/8wxfY2jOASc2fKl0CN9xVVSmqxDx06j/q/zS6VzchXyI/QKBgQCD\nz2QtbqHyKxZCW4mY+MF1s4dXYJUiC2ESNqzCPLna5/b0zSEaJ+CSZGQG7uzu+uvx\n1ThZqywUeWvJtj8NSjrqwB2OoRZ+WUpzkywP1J5ipSvpexrSuqiSpiJDSizSmeKG\nvN7qF8m2UXrTk8wYM8WTpEbtlXk/0HtD/FkYFGPu+QKBgBPOwLwmiPzgEesAaFXH\nayvs+2FYb78zt1fLNwmQUM5r/s23ovOFvUWXU1hN6tx50CGv1crOanQwDXmtXtqk\n+dObBWm7439HuuBo3WXstq6+bmyZqX+U5irdkLY877PBQdS7IVcgAW+7V5qON57o\nzHIOMCRI0ErWS3nENGHKw57J\n-----END PRIVATE KEY-----\n",
+  "client_email": "[email protected]",
+  "client_id": "102952992735066039371",
+  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+  "token_uri": "https://oauth2.googleapis.com/token",
+  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/dimesheet-api%40dimesheet-asterisk-is.iam.gserviceaccount.com",
+  "universe_domain": "googleapis.com"
+}

BIN=BIN
backend/dimesheet


+ 39 - 0
backend/go.mod

@@ -0,0 +1,39 @@
+//module dimesheet
+module asterisk.is/dimesheet
+
+go 1.23.2
+
+require (
+	github.com/a-h/templ v0.2.793
+	github.com/gorilla/mux v1.8.1
+	github.com/jung-kurt/gofpdf v1.16.2
+	golang.org/x/oauth2 v0.24.0
+	google.golang.org/api v0.206.0
+)
+
+require (
+	cloud.google.com/go/auth v0.10.2 // indirect
+	cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect
+	cloud.google.com/go/compute/metadata v0.5.2 // indirect
+	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/go-logr/logr v1.4.2 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+	github.com/google/s2a-go v0.1.8 // indirect
+	github.com/google/uuid v1.6.0 // indirect
+	github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
+	github.com/googleapis/gax-go/v2 v2.14.0 // indirect
+	github.com/joho/godotenv v1.5.1 // indirect
+	go.opencensus.io v0.24.0 // indirect
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
+	go.opentelemetry.io/otel v1.29.0 // indirect
+	go.opentelemetry.io/otel/metric v1.29.0 // indirect
+	go.opentelemetry.io/otel/trace v1.29.0 // indirect
+	golang.org/x/crypto v0.29.0 // indirect
+	golang.org/x/net v0.31.0 // indirect
+	golang.org/x/sys v0.27.0 // indirect
+	golang.org/x/text v0.20.0 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect
+	google.golang.org/grpc v1.67.1 // indirect
+	google.golang.org/protobuf v1.35.1 // indirect
+)

+ 168 - 0
backend/go.sum

@@ -0,0 +1,168 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo=
+cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI=
+cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk=
+cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8=
+cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo=
+cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/a-h/templ v0.2.793 h1:Io+/ocnfGWYO4VHdR0zBbf39PQlnzVCVVD+wEEs6/qY=
+github.com/a-h/templ v0.2.793/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
+github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM=
+github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
+github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
+github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o=
+github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
+github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
+github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
+github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8=
+go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
+go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
+go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
+go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
+go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
+go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
+golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
+golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
+golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
+golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
+golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
+golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.206.0 h1:A27GClesCSheW5P2BymVHjpEeQ2XHH8DI8Srs2HI2L8=
+google.golang.org/api v0.206.0/go.mod h1:BtB8bfjTYIrai3d8UyvPmV9REGgox7coh+ZRwm0b+W8=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 h1:KJjNNclfpIkVqrZlTWcgOOaVQ00LdBnoEaRfkUx760s=
+google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g=
+google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
+google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
+google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 2 - 0
backend/helpful.txt

@@ -0,0 +1,2 @@
+https://adrianhesketh.com/2021/05/28/templ-hot-reload-with-air/
+https://developers.google.com/sheets/api/quickstart/go

+ 41 - 0
backend/internal/handlers/handlers.go

@@ -0,0 +1,41 @@
+package handlers
+
+import (
+	"encoding/json"
+	"net/http"
+
+	"asterisk.is/dimesheet/internal/services"
+)
+
+func write_json(w http.ResponseWriter, data interface{}) error {
+	w.Header().Set("Content-Type", "application/json")
+	return json.NewEncoder(w).Encode(data)
+}
+
+// EventsHandler serves the /api endpoint.
+func RootHandler(w http.ResponseWriter, r *http.Request) {
+	write_json(w, map[string]interface{}{})
+}
+
+// EventsHandler serves the /api/clients endpoint.
+func GetClients(w http.ResponseWriter, r *http.Request) {
+	clients, err := services.GetClients()
+	if err != nil {
+		http.Error(w, "Unable to retrieve clients, "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := write_json(w, clients); err != nil {
+		http.Error(w, "Unable to encode clients to JSON", http.StatusInternalServerError)
+	}
+}
+
+func EventsHandler(w http.ResponseWriter, r *http.Request) {
+	events, err := services.GetEvents()
+	if err != nil {
+		http.Error(w, "Unable to retrieve events, "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+	if err := write_json(w, events); err != nil {
+		http.Error(w, "Unable to encode events to JSON", http.StatusInternalServerError)
+	}
+}

+ 97 - 0
backend/internal/services/calendar.go

@@ -0,0 +1,97 @@
+package services
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"os"
+	"time"
+
+	"asterisk.is/dimesheet/models"
+
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/calendar/v3"
+	"google.golang.org/api/option"
+)
+
+func get_calendar_client() (*http.Client, *context.Context, error) {
+
+	// Load service account credentials from JSON file
+	credentialsFile := "credentials.json"
+	b, err := os.ReadFile(credentialsFile)
+	if err != nil {
+		//http.Error(w, "Unable to read service account file", http.StatusInternalServerError)
+		return nil, nil, errors.New("Unable to read service account file")
+	}
+
+	// Create a Google Calendar service with service account credentials
+	ctx := context.Background()
+	config, err := google.JWTConfigFromJSON(b, calendar.CalendarReadonlyScope)
+	if err != nil {
+		//http.Error(w, "Unable to parse service account file to config", http.StatusInternalServerError)
+		return nil, nil, errors.New("Unable to parse service account file to config")
+	}
+	client := config.Client(ctx)
+	return client, &ctx, nil
+}
+
+func GetEvents() ([]*calendar.Event, error) {
+
+	// Create a new Google Calendar service
+	cal_client, ctx, err := get_calendar_client()
+	if err != nil {
+		return nil, errors.New("Something went wrong:" + err.Error())
+	}
+
+	// Create a new Google Calendar service
+	srv, err := calendar.NewService(*ctx, option.WithHTTPClient(cal_client))
+	if err != nil {
+		return nil, errors.New("Unable to retrieve Calendar client")
+	}
+
+	// Call the Google Calendar API
+	events, err := srv.Events.List("c_9f1bb79a8eaa9761c51ce066f845131ab5c17794c863ba3d58e9124645c6451a@group.calendar.google.com").MaxResults(10).OrderBy("startTime").SingleEvents(true).TimeMin(time.Now().Format(time.RFC3339)).Do()
+	if err != nil {
+		return nil, errors.New("Unable to retrieve events")
+	}
+
+	return events.Items, nil
+}
+
+func GetClients() ([]models.Client, error) {
+
+	// Create a new Google Calendar service
+	cal_client, ctx, err := get_calendar_client()
+	if err != nil {
+		return nil, errors.New("Something went wrong:" + err.Error())
+	}
+
+	srv, err := calendar.NewService(*ctx, option.WithHTTPClient(cal_client))
+	if err != nil {
+		return nil, errors.New("Unable to retrieve Calendar client")
+	}
+
+	// Call the Google Calendar API
+
+	events, err := srv.CalendarList.List().Do() //srv.Events.List("primary").MaxResults(10).OrderBy("startTime").SingleEvents(true).TimeMin(time.Now().Format(time.RFC3339)).Do()
+	if err != nil {
+		return nil, errors.New("Unable to retrieve events")
+	}
+
+	array := make([]models.Client, 0, len(events.Items))
+
+	for _, calendar_item := range events.Items {
+		c := models.Client{}
+		s := calendar_item.Description
+
+		err := json.Unmarshal([]byte(s), &c)
+		if err != nil {
+			panic(err)
+		}
+
+		c.CalendarListEntry = calendar_item
+		array = append(array, c)
+	}
+	return array, nil
+}

+ 9 - 0
backend/models/client.go

@@ -0,0 +1,9 @@
+package models
+
+import "google.golang.org/api/calendar/v3"
+
+type Client struct {
+	CalendarListEntry *calendar.CalendarListEntry
+	Client            string `json:"client"`
+	Ticker            string `json:"ticker"`
+}

+ 21 - 0
backend/models/models.go

@@ -0,0 +1,21 @@
+package models
+
+type Event struct {
+	Project     string
+	Summary     string `json:"summary"`
+	Description string `json:"description"`
+	StartTime   string
+	EndTime     string
+	Date        string
+	Duration    float64
+}
+
+type Project struct {
+	Name       string
+	TotalHours float64
+}
+
+type Projects struct {
+	Projects   []Project
+	TotalHours float64
+}

+ 1 - 0
backend/read.me

@@ -0,0 +1 @@
+https://github.com/jritsema/go-htmx-tailwind-example/blob/main/templates/company-add.html

+ 16 - 0
backend/readme.md

@@ -0,0 +1,16 @@
+
+-- Build a new image
+docker image build . -t registry.asterisk.is/dimesheet
+
+-- Log in to docker registry
+docker login registry.asterisk.is
+
+-- Push
+docker push registry.asterisk.is/dimesheet
+
+-- Run dimesheet
+docker compose up
+
+-- Start on http://localhost:8000
+
+docker run -it --rm -d -p 8000:80 --name web -v .:/usr/share/nginx/html nginx

+ 281 - 0
backend/src/main.go

@@ -0,0 +1,281 @@
+package main
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+	"time"
+
+	"asterisk.is/dimesheet/models"
+	"asterisk.is/dimesheet/views"
+
+	"github.com/a-h/templ"
+	"github.com/gorilla/mux"
+	"github.com/jung-kurt/gofpdf"
+	"golang.org/x/oauth2/google"
+	"google.golang.org/api/calendar/v3"
+	"google.golang.org/api/option"
+)
+
+func eventsHandler(w http.ResponseWriter, r *http.Request) {
+	// Load service account credentials from JSON file
+	credentialsFile := "credentials.json"
+	b, err := os.ReadFile(credentialsFile)
+	if err != nil {
+		http.Error(w, "Unable to read service account file", http.StatusInternalServerError)
+		return
+	}
+
+	// Create a Google Calendar service with service account credentials
+	ctx := context.Background()
+	config, err := google.JWTConfigFromJSON(b, calendar.CalendarReadonlyScope)
+	if err != nil {
+		http.Error(w, "Unable to parse service account file to config", http.StatusInternalServerError)
+		return
+	}
+	client := config.Client(ctx)
+
+	// Create a new Google Calendar service
+	srv, err := calendar.NewService(ctx, option.WithHTTPClient(client))
+	if err != nil {
+		http.Error(w, "Unable to retrieve Calendar client", http.StatusInternalServerError)
+		return
+	}
+
+	// Call the Google Calendar API
+	events, err := srv.Events.List("primary").MaxResults(10).OrderBy("startTime").SingleEvents(true).TimeMin(time.Now().Format(time.RFC3339)).Do()
+	if err != nil {
+		http.Error(w, "Unable to retrieve events", http.StatusInternalServerError)
+		return
+	}
+
+	// Encode events into JSON and write to response
+	w.Header().Set("Content-Type", "application/json")
+	if err := json.NewEncoder(w).Encode(events.Items); err != nil {
+		http.Error(w, "Unable to encode events to JSON", http.StatusInternalServerError)
+	}
+}
+
+func get_clients() ([]models.Client, error) {
+
+	// Create a new Google Calendar service
+	cal_client, ctx, err := get_calendar_client()
+	if err != nil {
+		return nil, errors.New("Something went wrong")
+	}
+
+	srv, err := calendar.NewService(*ctx, option.WithHTTPClient(cal_client))
+	if err != nil {
+		return nil, errors.New("Unable to retrieve Calendar client")
+	}
+
+	// Call the Google Calendar API
+
+	events, err := srv.CalendarList.List().Do() //srv.Events.List("primary").MaxResults(10).OrderBy("startTime").SingleEvents(true).TimeMin(time.Now().Format(time.RFC3339)).Do()
+	if err != nil {
+		return nil, errors.New("Unable to retrieve events")
+	}
+
+	array := make([]models.Client, 0, len(events.Items))
+
+	for _, calendar_item := range events.Items {
+		c := models.Client{}
+		s := calendar_item.Description
+
+		err := json.Unmarshal([]byte(s), &c)
+		if err != nil {
+			panic(err)
+		}
+
+		c.CalendarListEntry = calendar_item
+		array = append(array, c)
+	}
+
+	// Encode events into JSON and write to response
+	//w.Header().Set("Content-Type", "application/json")
+	//enc := json.NewEncoder(w)
+	//enc.SetIndent("", "    ")
+	//if err := enc.Encode(events.Items); err != nil {
+	//http.Error(w, "Unable to encode events to JSON", http.StatusInternalServerError)
+	//}
+	return array, nil
+}
+
+func generatePDF(w http.ResponseWriter, r *http.Request) {
+	// Create a new PDF
+	pdf := gofpdf.New("P", "mm", "A4", "")
+	pdf.AddPage()
+	pdf.SetFont("Arial", "B", 16)
+
+	// Add content to the PDF
+	pdf.Cell(40, 10, "Hello, World! This is your PDF.")
+
+	// Write PDF to a buffer
+	var buf bytes.Buffer
+	err := pdf.Output(&buf)
+	if err != nil {
+		http.Error(w, "Failed to generate PDF", http.StatusInternalServerError)
+		return
+	}
+
+	// Set response headers
+	w.Header().Set("Content-Type", "application/pdf")
+	w.Header().Set("Content-Disposition", "attachment; filename=example.pdf")
+	w.Header().Set("Content-Length", fmt.Sprint(buf.Len()))
+
+	// Write the PDF to the response
+	_, _ = w.Write(buf.Bytes())
+}
+
+func get_calendar_events(client models.Client) ([]models.Event, *models.Projects, error) {
+
+	// Create a new Google Calendar service
+	cal_client, ctx, err := get_calendar_client()
+	srv, err := calendar.NewService(*ctx, option.WithHTTPClient(cal_client))
+	if err != nil {
+		return nil, nil, errors.New("Unable to retrieve Calendar client")
+	}
+
+	fmt.Printf("Getting calendar events for client: %+v\n", client.Ticker)
+
+	now := time.Now()
+	startDate := time.Date(now.Year(), now.Month()-1, 1, 0, 0, 0, 0, time.UTC)
+	endDate := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC).Add(time.Second * -1)
+
+	// Call the Google Calendar API
+	events, err := srv.Events.List(client.CalendarListEntry.Id).MaxResults(100).OrderBy("startTime").SingleEvents(true).TimeMin(startDate.Format(time.RFC3339)).TimeMax(endDate.Format(time.RFC3339)).Do()
+	if err != nil {
+		return nil, nil, errors.New("Could not retrieve events")
+	}
+
+	array := make([]models.Event, 0, len(events.Items))
+
+	m := make(map[string]float64)
+
+	for _, event := range events.Items {
+
+		startTime, _ := time.Parse(time.RFC3339, event.Start.DateTime)
+		endTime, _ := time.Parse(time.RFC3339, event.End.DateTime)
+
+		c := models.Event{}
+
+		parts := strings.Split(event.Summary, ".")
+
+		c.Summary = event.Summary
+		c.Description = event.Description
+
+		c.Date = startTime.Format("2006-01-02")
+		c.StartTime = startTime.Format("15:04")
+		c.EndTime = endTime.Format("15:04")
+		c.Project = parts[0]
+
+		c.Duration = endTime.Sub(startTime).Hours()
+
+		array = append(array, c)
+		m[c.Project] = m[c.Project] + c.Duration
+
+	}
+
+	projects := make([]models.Project, 0, len(m))
+	projects_total_hours := 0.00
+	for k, v := range m {
+		p := models.Project{}
+		p.Name = k
+		p.TotalHours = v
+		projects = append(projects, p)
+		projects_total_hours += v
+	}
+
+	return array, &models.Projects{Projects: projects, TotalHours: projects_total_hours}, nil
+
+}
+
+func get_calendar_client() (*http.Client, *context.Context, error) {
+
+	// Load service account credentials from JSON file
+	credentialsFile := "credentials.json"
+	b, err := os.ReadFile(credentialsFile)
+	if err != nil {
+		//http.Error(w, "Unable to read service account file", http.StatusInternalServerError)
+		return nil, nil, errors.New("Unable to read service account file")
+	}
+
+	// Create a Google Calendar service with service account credentials
+	ctx := context.Background()
+	config, err := google.JWTConfigFromJSON(b, calendar.CalendarReadonlyScope)
+	if err != nil {
+		//http.Error(w, "Unable to parse service account file to config", http.StatusInternalServerError)
+		return nil, nil, errors.New("Unable to parse service account file to config")
+	}
+	client := config.Client(ctx)
+	return client, &ctx, nil
+}
+
+func client_handler(w http.ResponseWriter, r *http.Request) {
+
+	vars := mux.Vars(r)
+	client_id := vars["client_id"]
+
+	clients, err := get_clients()
+	if err != nil {
+		log.Fatal("Could not list clients")
+	}
+
+	var foundClient *models.Client
+	for _, client := range clients {
+		if client.Ticker == client_id {
+			foundClient = &client
+			break
+		}
+	}
+
+	// Check if the person was found
+	if foundClient != nil {
+		fmt.Printf("Found client: %+v\n", *foundClient)
+	} else {
+		fmt.Println("Client not found")
+	}
+
+	events, projects, err := get_calendar_events(*foundClient)
+
+	client_view := views.Client_page(*foundClient, events, *projects)
+
+	client_view.Render(r.Context(), w)
+
+}
+
+func main() {
+
+	r := mux.NewRouter()
+
+	clients, err := get_clients()
+	if err != nil {
+		log.Fatal("FAILED")
+	}
+
+	// Print each client entry
+	for i, client := range clients {
+		fmt.Printf("Client %d: %+v\n", i+1, client.CalendarListEntry)
+	}
+
+	clients_view := views.Page(clients)
+
+	// fs := http.FileServer(http.Dir("static"))
+	// http.Handle("/app/", http.StripPrefix("/app/", fs))
+	r.Handle("/", templ.Handler(clients_view))
+	r.HandleFunc("/client/{client_id}", client_handler)
+	r.HandleFunc("/pdf", generatePDF)
+	// Set up the /events endpoint
+	r.HandleFunc("/api/events", eventsHandler)
+	//http.HandleFunc("/api/clients", clientHandler)
+
+	port := ":8765"
+	fmt.Printf("Starting server at %s\n", port)
+	log.Fatal(http.ListenAndServe(port, r))
+}

+ 4 - 0
backend/src/readme.md.md

@@ -0,0 +1,4 @@
+- Building a new docker image for Dimesheet
+	- docker image build . -t asterisk/dimesheet
+- Running Dimesheet on port 8000
+	- docker-compose up

+ 2 - 0
backend/src/run.sh

@@ -0,0 +1,2 @@
+templ generate
+go run main.go

BIN=BIN
backend/src/src


+ 29 - 0
backend/static/index.html

@@ -0,0 +1,29 @@
+<!-- templates/index.html -->
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>HTMX JSON List</title>
+    <script src="https://unpkg.com/[email protected]"></script>
+</head>
+<body>
+    <h1>Item List</h1>
+    <button hx-get="/api/clients" hx-trigger="click" hx-target="#item-list" hx-swap="innerHTML">
+        Load Items
+    </button>
+    <ul id="item-list">
+        <!-- Items will be populated here -->
+    </ul>
+
+    <script>
+        document.addEventListener("htmx:afterRequest", function(event) {
+            if (event.detail.path === "/api/clients") {
+                const data = event.detail.xhr.response;
+                const itemList = document.getElementById("item-list");
+                itemList.innerHTML = data.map(item => `<li>${item.summary}</li>`).join("");
+            }
+        });
+    </script>
+</body>
+</html>

+ 329 - 0
backend/vendor/cloud.google.com/go/auth/CHANGES.md

@@ -0,0 +1,329 @@
+# Changelog
+
+## [0.10.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.1...auth/v0.10.2) (2024-11-12)
+
+
+### Bug Fixes
+
+* **auth:** Restore use of grpc.Dial ([#11118](https://github.com/googleapis/google-cloud-go/issues/11118)) ([2456b94](https://github.com/googleapis/google-cloud-go/commit/2456b943b7b8aaabd4d8bfb7572c0f477ae0db45)), refs [#7556](https://github.com/googleapis/google-cloud-go/issues/7556)
+
+## [0.10.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.10.0...auth/v0.10.1) (2024-11-06)
+
+
+### Bug Fixes
+
+* **auth:** Restore Application Default Credentials support to idtoken ([#11083](https://github.com/googleapis/google-cloud-go/issues/11083)) ([8771f2e](https://github.com/googleapis/google-cloud-go/commit/8771f2ea9807ab822083808e0678392edff3b4f2))
+* **auth:** Skip impersonate universe domain check if empty ([#11086](https://github.com/googleapis/google-cloud-go/issues/11086)) ([87159c1](https://github.com/googleapis/google-cloud-go/commit/87159c1059d4a18d1367ce62746a838a94964ab6))
+
+## [0.10.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.9...auth/v0.10.0) (2024-10-30)
+
+
+### Features
+
+* **auth:** Add universe domain support to credentials/impersonate ([#10953](https://github.com/googleapis/google-cloud-go/issues/10953)) ([e06cb64](https://github.com/googleapis/google-cloud-go/commit/e06cb6499f7eda3aef08ab18ff197016f667684b))
+
+## [0.9.9](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.8...auth/v0.9.9) (2024-10-22)
+
+
+### Bug Fixes
+
+* **auth:** Fallback cert lookups for missing files ([#11013](https://github.com/googleapis/google-cloud-go/issues/11013)) ([bd76695](https://github.com/googleapis/google-cloud-go/commit/bd766957ec238b7c40ddbabb369e612dc9b07313)), refs [#10844](https://github.com/googleapis/google-cloud-go/issues/10844)
+* **auth:** Replace MDS endpoint universe_domain with universe-domain ([#11000](https://github.com/googleapis/google-cloud-go/issues/11000)) ([6a1586f](https://github.com/googleapis/google-cloud-go/commit/6a1586f2ce9974684affaea84e7b629313b4d114))
+
+## [0.9.8](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.7...auth/v0.9.8) (2024-10-09)
+
+
+### Bug Fixes
+
+* **auth:** Restore OpenTelemetry handling in transports ([#10968](https://github.com/googleapis/google-cloud-go/issues/10968)) ([08c6d04](https://github.com/googleapis/google-cloud-go/commit/08c6d04901c1a20e219b2d86df41dbaa6d7d7b55)), refs [#10962](https://github.com/googleapis/google-cloud-go/issues/10962)
+* **auth:** Try talk to plaintext S2A if credentials can not be found for mTLS-S2A ([#10941](https://github.com/googleapis/google-cloud-go/issues/10941)) ([0f0bf2d](https://github.com/googleapis/google-cloud-go/commit/0f0bf2d18c97dd8b65bcf0099f0802b5631c6287))
+
+## [0.9.7](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.6...auth/v0.9.7) (2024-10-01)
+
+
+### Bug Fixes
+
+* **auth:** Restore support for non-default service accounts for DirectPath ([#10937](https://github.com/googleapis/google-cloud-go/issues/10937)) ([a38650e](https://github.com/googleapis/google-cloud-go/commit/a38650edbf420223077498cafa537aec74b37aad)), refs [#10907](https://github.com/googleapis/google-cloud-go/issues/10907)
+
+## [0.9.6](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.5...auth/v0.9.6) (2024-09-30)
+
+
+### Bug Fixes
+
+* **auth:** Make aws credentials provider retrieve fresh credentials ([#10920](https://github.com/googleapis/google-cloud-go/issues/10920)) ([250fbf8](https://github.com/googleapis/google-cloud-go/commit/250fbf87d858d865e399a241b7e537c4ff0c3dd8))
+
+## [0.9.5](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.4...auth/v0.9.5) (2024-09-25)
+
+
+### Bug Fixes
+
+* **auth:** Restore support for GOOGLE_CLOUD_UNIVERSE_DOMAIN env ([#10915](https://github.com/googleapis/google-cloud-go/issues/10915)) ([94caaaa](https://github.com/googleapis/google-cloud-go/commit/94caaaa061362d0e00ef6214afcc8a0a3e7ebfb2))
+* **auth:** Skip directpath credentials overwrite when it's not on GCE ([#10833](https://github.com/googleapis/google-cloud-go/issues/10833)) ([7e5e8d1](https://github.com/googleapis/google-cloud-go/commit/7e5e8d10b761b0a6e43e19a028528db361bc07b1))
+* **auth:** Use new context for non-blocking token refresh ([#10919](https://github.com/googleapis/google-cloud-go/issues/10919)) ([cf7102d](https://github.com/googleapis/google-cloud-go/commit/cf7102d33a21be1e5a9d47a49456b3a57c43b350))
+
+## [0.9.4](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.3...auth/v0.9.4) (2024-09-11)
+
+
+### Bug Fixes
+
+* **auth:** Enable self-signed JWT for non-GDU universe domain ([#10831](https://github.com/googleapis/google-cloud-go/issues/10831)) ([f9869f7](https://github.com/googleapis/google-cloud-go/commit/f9869f7903cfd34d1b97c25d0dc5669d2c5138e6))
+
+## [0.9.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.2...auth/v0.9.3) (2024-09-03)
+
+
+### Bug Fixes
+
+* **auth:** Choose quota project envvar over file when both present ([#10807](https://github.com/googleapis/google-cloud-go/issues/10807)) ([2d8dd77](https://github.com/googleapis/google-cloud-go/commit/2d8dd7700eff92d4b95027be55e26e1e7aa79181)), refs [#10804](https://github.com/googleapis/google-cloud-go/issues/10804)
+
+## [0.9.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.1...auth/v0.9.2) (2024-08-30)
+
+
+### Bug Fixes
+
+* **auth:** Handle non-Transport DefaultTransport ([#10733](https://github.com/googleapis/google-cloud-go/issues/10733)) ([98d91dc](https://github.com/googleapis/google-cloud-go/commit/98d91dc8316b247498fab41ab35e57a0446fe556)), refs [#10742](https://github.com/googleapis/google-cloud-go/issues/10742)
+* **auth:** Make sure quota option takes precedence over env/file ([#10797](https://github.com/googleapis/google-cloud-go/issues/10797)) ([f1b050d](https://github.com/googleapis/google-cloud-go/commit/f1b050d56d804b245cab048c2980d32b0eaceb4e)), refs [#10795](https://github.com/googleapis/google-cloud-go/issues/10795)
+
+
+### Documentation
+
+* **auth:** Fix Go doc comment link ([#10751](https://github.com/googleapis/google-cloud-go/issues/10751)) ([015acfa](https://github.com/googleapis/google-cloud-go/commit/015acfab4d172650928bb1119bc2cd6307b9a437))
+
+## [0.9.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.9.0...auth/v0.9.1) (2024-08-22)
+
+
+### Bug Fixes
+
+* **auth:** Setting expireEarly to default when the value is 0 ([#10732](https://github.com/googleapis/google-cloud-go/issues/10732)) ([5e67869](https://github.com/googleapis/google-cloud-go/commit/5e67869a31e9e8ecb4eeebd2cfa11a761c3b1948))
+
+## [0.9.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.1...auth/v0.9.0) (2024-08-16)
+
+
+### Features
+
+* **auth:** Auth library can talk to S2A over mTLS ([#10634](https://github.com/googleapis/google-cloud-go/issues/10634)) ([5250a13](https://github.com/googleapis/google-cloud-go/commit/5250a13ec95b8d4eefbe0158f82857ff2189cb45))
+
+## [0.8.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.8.0...auth/v0.8.1) (2024-08-13)
+
+
+### Bug Fixes
+
+* **auth:** Make default client creation more lenient ([#10669](https://github.com/googleapis/google-cloud-go/issues/10669)) ([1afb9ee](https://github.com/googleapis/google-cloud-go/commit/1afb9ee1ee9de9810722800018133304a0ca34d1)), refs [#10638](https://github.com/googleapis/google-cloud-go/issues/10638)
+
+## [0.8.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.3...auth/v0.8.0) (2024-08-07)
+
+
+### Features
+
+* **auth:** Adds support for X509 workload identity federation ([#10373](https://github.com/googleapis/google-cloud-go/issues/10373)) ([5d07505](https://github.com/googleapis/google-cloud-go/commit/5d075056cbe27bb1da4072a26070c41f8999eb9b))
+
+## [0.7.3](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.2...auth/v0.7.3) (2024-08-01)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
+* **auth:** Disable automatic universe domain check for MDS ([#10620](https://github.com/googleapis/google-cloud-go/issues/10620)) ([7cea5ed](https://github.com/googleapis/google-cloud-go/commit/7cea5edd5a0c1e6bca558696f5607879141910e8))
+* **auth:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
+
+## [0.7.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.1...auth/v0.7.2) (2024-07-22)
+
+
+### Bug Fixes
+
+* **auth:** Use default client for universe metadata lookup ([#10551](https://github.com/googleapis/google-cloud-go/issues/10551)) ([d9046fd](https://github.com/googleapis/google-cloud-go/commit/d9046fdd1435d1ce48f374806c1def4cb5ac6cd3)), refs [#10544](https://github.com/googleapis/google-cloud-go/issues/10544)
+
+## [0.7.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.7.0...auth/v0.7.1) (2024-07-10)
+
+
+### Bug Fixes
+
+* **auth:** Bump google.golang.org/[email protected] ([8ecc4e9](https://github.com/googleapis/google-cloud-go/commit/8ecc4e9622e5bbe9b90384d5848ab816027226c5))
+
+## [0.7.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.1...auth/v0.7.0) (2024-07-09)
+
+
+### Features
+
+* **auth:** Add workload X509 cert provider as a default cert provider ([#10479](https://github.com/googleapis/google-cloud-go/issues/10479)) ([c51ee6c](https://github.com/googleapis/google-cloud-go/commit/c51ee6cf65ce05b4d501083e49d468c75ac1ea63))
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Bump google.golang.org/[email protected] ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
+* **auth:** Bump google.golang.org/[email protected] ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
+* **auth:** Check len of slices, not non-nil ([#10483](https://github.com/googleapis/google-cloud-go/issues/10483)) ([0a966a1](https://github.com/googleapis/google-cloud-go/commit/0a966a183e5f0e811977216d736d875b7233e942))
+
+## [0.6.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.6.0...auth/v0.6.1) (2024-07-01)
+
+
+### Bug Fixes
+
+* **auth:** Support gRPC API keys ([#10460](https://github.com/googleapis/google-cloud-go/issues/10460)) ([daa6646](https://github.com/googleapis/google-cloud-go/commit/daa6646d2af5d7fb5b30489f4934c7db89868c7c))
+* **auth:** Update http and grpc transports to support token exchange over mTLS ([#10397](https://github.com/googleapis/google-cloud-go/issues/10397)) ([c6dfdcf](https://github.com/googleapis/google-cloud-go/commit/c6dfdcf893c3f971eba15026c12db0a960ae81f2))
+
+## [0.6.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.2...auth/v0.6.0) (2024-06-25)
+
+
+### Features
+
+* **auth:** Add non-blocking token refresh for compute MDS ([#10263](https://github.com/googleapis/google-cloud-go/issues/10263)) ([9ac350d](https://github.com/googleapis/google-cloud-go/commit/9ac350da11a49b8e2174d3fc5b1a5070fec78b4e))
+
+
+### Bug Fixes
+
+* **auth:** Return error if envvar detected file returns an error ([#10431](https://github.com/googleapis/google-cloud-go/issues/10431)) ([e52b9a7](https://github.com/googleapis/google-cloud-go/commit/e52b9a7c45468827f5d220ab00965191faeb9d05))
+
+## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.1...auth/v0.5.2) (2024-06-24)
+
+
+### Bug Fixes
+
+* **auth:** Fetch initial token when CachedTokenProviderOptions.DisableAutoRefresh is true ([#10415](https://github.com/googleapis/google-cloud-go/issues/10415)) ([3266763](https://github.com/googleapis/google-cloud-go/commit/32667635ca2efad05cd8c087c004ca07d7406913)), refs [#10414](https://github.com/googleapis/google-cloud-go/issues/10414)
+
+## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.5.0...auth/v0.5.1) (2024-05-31)
+
+
+### Bug Fixes
+
+* **auth:** Pass through client to 2LO and 3LO flows ([#10290](https://github.com/googleapis/google-cloud-go/issues/10290)) ([685784e](https://github.com/googleapis/google-cloud-go/commit/685784ea84358c15e9214bdecb307d37aa3b6d2f))
+
+## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.2...auth/v0.5.0) (2024-05-28)
+
+
+### Features
+
+* **auth:** Adds X509 workload certificate provider ([#10233](https://github.com/googleapis/google-cloud-go/issues/10233)) ([17a9db7](https://github.com/googleapis/google-cloud-go/commit/17a9db73af35e3d1a7a25ac4fd1377a103de6150))
+
+## [0.4.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.1...auth/v0.4.2) (2024-05-16)
+
+
+### Bug Fixes
+
+* **auth:** Enable client certificates by default only for GDU ([#10151](https://github.com/googleapis/google-cloud-go/issues/10151)) ([7c52978](https://github.com/googleapis/google-cloud-go/commit/7c529786275a39b7e00525f7d5e7be0d963e9e15))
+* **auth:** Handle non-Transport DefaultTransport ([#10162](https://github.com/googleapis/google-cloud-go/issues/10162)) ([fa3bfdb](https://github.com/googleapis/google-cloud-go/commit/fa3bfdb23aaa45b34394a8b61e753b3587506782)), refs [#10159](https://github.com/googleapis/google-cloud-go/issues/10159)
+* **auth:** Have refresh time match docs ([#10147](https://github.com/googleapis/google-cloud-go/issues/10147)) ([bcb5568](https://github.com/googleapis/google-cloud-go/commit/bcb5568c07a54dd3d2e869d15f502b0741a609e8))
+* **auth:** Update compute token fetching error with named prefix ([#10180](https://github.com/googleapis/google-cloud-go/issues/10180)) ([4573504](https://github.com/googleapis/google-cloud-go/commit/4573504828d2928bebedc875d87650ba227829ea))
+
+## [0.4.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.4.0...auth/v0.4.1) (2024-05-09)
+
+
+### Bug Fixes
+
+* **auth:** Don't try to detect default creds it opt configured ([#10143](https://github.com/googleapis/google-cloud-go/issues/10143)) ([804632e](https://github.com/googleapis/google-cloud-go/commit/804632e7c5b0b85ff522f7951114485e256eb5bc))
+
+## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.3.0...auth/v0.4.0) (2024-05-07)
+
+
+### Features
+
+* **auth:** Enable client certificates by default ([#10102](https://github.com/googleapis/google-cloud-go/issues/10102)) ([9013e52](https://github.com/googleapis/google-cloud-go/commit/9013e5200a6ec0f178ed91acb255481ffb073a2c))
+
+
+### Bug Fixes
+
+* **auth:** Get s2a logic up to date ([#10093](https://github.com/googleapis/google-cloud-go/issues/10093)) ([4fe9ae4](https://github.com/googleapis/google-cloud-go/commit/4fe9ae4b7101af2a5221d6d6b2e77b479305bb06))
+
+## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.2...auth/v0.3.0) (2024-04-23)
+
+
+### Features
+
+* **auth/httptransport:** Add ability to customize transport ([#10023](https://github.com/googleapis/google-cloud-go/issues/10023)) ([72c7f6b](https://github.com/googleapis/google-cloud-go/commit/72c7f6bbec3136cc7a62788fc7186bc33ef6c3b3)), refs [#9812](https://github.com/googleapis/google-cloud-go/issues/9812) [#9814](https://github.com/googleapis/google-cloud-go/issues/9814)
+
+
+### Bug Fixes
+
+* **auth/credentials:** Error on bad file name if explicitly set ([#10018](https://github.com/googleapis/google-cloud-go/issues/10018)) ([55beaa9](https://github.com/googleapis/google-cloud-go/commit/55beaa993aaf052d8be39766afc6777c3c2a0bdd)), refs [#9809](https://github.com/googleapis/google-cloud-go/issues/9809)
+
+## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.1...auth/v0.2.2) (2024-04-19)
+
+
+### Bug Fixes
+
+* **auth:** Add internal opt to skip validation on transports ([#9999](https://github.com/googleapis/google-cloud-go/issues/9999)) ([9e20ef8](https://github.com/googleapis/google-cloud-go/commit/9e20ef89f6287d6bd03b8697d5898dc43b4a77cf)), refs [#9823](https://github.com/googleapis/google-cloud-go/issues/9823)
+* **auth:** Set secure flag for gRPC conn pools ([#10002](https://github.com/googleapis/google-cloud-go/issues/10002)) ([14e3956](https://github.com/googleapis/google-cloud-go/commit/14e3956dfd736399731b5ee8d9b178ae085cf7ba)), refs [#9833](https://github.com/googleapis/google-cloud-go/issues/9833)
+
+## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.2.0...auth/v0.2.1) (2024-04-18)
+
+
+### Bug Fixes
+
+* **auth:** Default gRPC token type to Bearer if not set ([#9800](https://github.com/googleapis/google-cloud-go/issues/9800)) ([5284066](https://github.com/googleapis/google-cloud-go/commit/5284066670b6fe65d79089cfe0199c9660f87fc7))
+
+## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.1...auth/v0.2.0) (2024-04-15)
+
+### Breaking Changes
+
+In the below mentioned commits there were a few large breaking changes since the
+last release of the module.
+
+1. The `Credentials` type has been moved to the root of the module as it is
+   becoming the core abstraction for the whole module.
+2. Because of the above mentioned change many functions that previously
+   returned a `TokenProvider` now return `Credentials`. Similarly, these
+   functions have been renamed to be more specific.
+3. Most places that used to take an optional `TokenProvider` now accept
+   `Credentials`. You can make a `Credentials` from a `TokenProvider` using the
+   constructor found in the `auth` package.
+4. The `detect` package has been renamed to `credentials`. With this change some
+   function signatures were also updated for better readability.
+5. Derivative auth flows like `impersonate` and `downscope` have been moved to
+   be under the new `credentials` package.
+
+Although these changes are disruptive we think that they are for the best of the
+long-term health of the module. We do not expect any more large breaking changes
+like these in future revisions, even before 1.0.0. This version will be the
+first version of the auth library that our client libraries start to use and
+depend on.
+
+### Features
+
+* **auth/credentials/externalaccount:** Add default TokenURL ([#9700](https://github.com/googleapis/google-cloud-go/issues/9700)) ([81830e6](https://github.com/googleapis/google-cloud-go/commit/81830e6848ceefd055aa4d08f933d1154455a0f6))
+* **auth:** Add downscope.Options.UniverseDomain ([#9634](https://github.com/googleapis/google-cloud-go/issues/9634)) ([52cf7d7](https://github.com/googleapis/google-cloud-go/commit/52cf7d780853594291c4e34302d618299d1f5a1d))
+* **auth:** Add universe domain to grpctransport and httptransport ([#9663](https://github.com/googleapis/google-cloud-go/issues/9663)) ([67d353b](https://github.com/googleapis/google-cloud-go/commit/67d353beefe3b607c08c891876fbd95ab89e5fe3)), refs [#9670](https://github.com/googleapis/google-cloud-go/issues/9670)
+* **auth:** Add UniverseDomain to DetectOptions ([#9536](https://github.com/googleapis/google-cloud-go/issues/9536)) ([3618d3f](https://github.com/googleapis/google-cloud-go/commit/3618d3f7061615c0e189f376c75abc201203b501))
+* **auth:** Make package externalaccount public ([#9633](https://github.com/googleapis/google-cloud-go/issues/9633)) ([a0978d8](https://github.com/googleapis/google-cloud-go/commit/a0978d8e96968399940ebd7d092539772bf9caac))
+* **auth:** Move credentials to base auth package ([#9590](https://github.com/googleapis/google-cloud-go/issues/9590)) ([1a04baf](https://github.com/googleapis/google-cloud-go/commit/1a04bafa83c27342b9308d785645e1e5423ea10d))
+* **auth:** Refactor public sigs to use Credentials ([#9603](https://github.com/googleapis/google-cloud-go/issues/9603)) ([69cb240](https://github.com/googleapis/google-cloud-go/commit/69cb240c530b1f7173a9af2555c19e9a1beb56c5))
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
+* **auth:** Fix uint32 conversion ([9221c7f](https://github.com/googleapis/google-cloud-go/commit/9221c7fa12cef9d5fb7ddc92f41f1d6204971c7b))
+* **auth:** Port sts expires fix ([#9618](https://github.com/googleapis/google-cloud-go/issues/9618)) ([7bec97b](https://github.com/googleapis/google-cloud-go/commit/7bec97b2f51ed3ac4f9b88bf100d301da3f5d1bd))
+* **auth:** Read universe_domain from all credentials files ([#9632](https://github.com/googleapis/google-cloud-go/issues/9632)) ([16efbb5](https://github.com/googleapis/google-cloud-go/commit/16efbb52e39ea4a319e5ee1e95c0e0305b6d9824))
+* **auth:** Remove content-type header from idms get requests ([#9508](https://github.com/googleapis/google-cloud-go/issues/9508)) ([8589f41](https://github.com/googleapis/google-cloud-go/commit/8589f41599d265d7c3d46a3d86c9fab2329cbdd9))
+* **auth:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
+
+## [0.1.1](https://github.com/googleapis/google-cloud-go/compare/auth/v0.1.0...auth/v0.1.1) (2024-03-10)
+
+
+### Bug Fixes
+
+* **auth/impersonate:** Properly send default detect params ([#9529](https://github.com/googleapis/google-cloud-go/issues/9529)) ([5b6b8be](https://github.com/googleapis/google-cloud-go/commit/5b6b8bef577f82707e51f5cc5d258d5bdf90218f)), refs [#9136](https://github.com/googleapis/google-cloud-go/issues/9136)
+* **auth:** Update grpc-go to v1.56.3 ([343cea8](https://github.com/googleapis/google-cloud-go/commit/343cea8c43b1e31ae21ad50ad31d3b0b60143f8c))
+* **auth:** Update grpc-go to v1.59.0 ([81a97b0](https://github.com/googleapis/google-cloud-go/commit/81a97b06cb28b25432e4ece595c55a9857e960b7))
+
+## 0.1.0 (2023-10-18)
+
+
+### Features
+
+* **auth:** Add base auth package ([#8465](https://github.com/googleapis/google-cloud-go/issues/8465)) ([6a45f26](https://github.com/googleapis/google-cloud-go/commit/6a45f26b809b64edae21f312c18d4205f96b180e))
+* **auth:** Add cert support to httptransport ([#8569](https://github.com/googleapis/google-cloud-go/issues/8569)) ([37e3435](https://github.com/googleapis/google-cloud-go/commit/37e3435f8e98595eafab481bdfcb31a4c56fa993))
+* **auth:** Add Credentials.UniverseDomain() ([#8654](https://github.com/googleapis/google-cloud-go/issues/8654)) ([af0aa1e](https://github.com/googleapis/google-cloud-go/commit/af0aa1ed8015bc8fe0dd87a7549ae029107cbdb8))
+* **auth:** Add detect package ([#8491](https://github.com/googleapis/google-cloud-go/issues/8491)) ([d977419](https://github.com/googleapis/google-cloud-go/commit/d977419a3269f6acc193df77a2136a6eb4b4add7))
+* **auth:** Add downscope package ([#8532](https://github.com/googleapis/google-cloud-go/issues/8532)) ([dda9bff](https://github.com/googleapis/google-cloud-go/commit/dda9bff8ec70e6d104901b4105d13dcaa4e2404c))
+* **auth:** Add grpctransport package ([#8625](https://github.com/googleapis/google-cloud-go/issues/8625)) ([69a8347](https://github.com/googleapis/google-cloud-go/commit/69a83470bdcc7ed10c6c36d1abc3b7cfdb8a0ee5))
+* **auth:** Add httptransport package ([#8567](https://github.com/googleapis/google-cloud-go/issues/8567)) ([6898597](https://github.com/googleapis/google-cloud-go/commit/6898597d2ea95d630fcd00fd15c58c75ea843bff))
+* **auth:** Add idtoken package ([#8580](https://github.com/googleapis/google-cloud-go/issues/8580)) ([a79e693](https://github.com/googleapis/google-cloud-go/commit/a79e693e97e4e3e1c6742099af3dbc58866d88fe))
+* **auth:** Add impersonate package ([#8578](https://github.com/googleapis/google-cloud-go/issues/8578)) ([e29ba0c](https://github.com/googleapis/google-cloud-go/commit/e29ba0cb7bd3888ab9e808087027dc5a32474c04))
+* **auth:** Add support for external accounts in detect ([#8508](https://github.com/googleapis/google-cloud-go/issues/8508)) ([62210d5](https://github.com/googleapis/google-cloud-go/commit/62210d5d3e56e8e9f35db8e6ac0defec19582507))
+* **auth:** Port external account changes ([#8697](https://github.com/googleapis/google-cloud-go/issues/8697)) ([5823db5](https://github.com/googleapis/google-cloud-go/commit/5823db5d633069999b58b9131a7f9cd77e82c899))
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))
+* **auth:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))

+ 202 - 0
backend/vendor/cloud.google.com/go/auth/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 40 - 0
backend/vendor/cloud.google.com/go/auth/README.md

@@ -0,0 +1,40 @@
+# Google Auth Library for Go
+
+[![Go Reference](https://pkg.go.dev/badge/cloud.google.com/go/auth.svg)](https://pkg.go.dev/cloud.google.com/go/auth)
+
+## Install
+
+``` bash
+go get cloud.google.com/go/auth@latest
+```
+
+## Usage
+
+The most common way this library is used is transitively, by default, from any
+of our Go client libraries.
+
+### Notable use-cases
+
+- To create a credential directly please see examples in the
+  [credentials](https://pkg.go.dev/cloud.google.com/go/auth/credentials)
+  package.
+- To create a authenticated HTTP client please see examples in the
+  [httptransport](https://pkg.go.dev/cloud.google.com/go/auth/httptransport)
+  package.
+- To create a authenticated gRPC connection please see examples in the
+  [grpctransport](https://pkg.go.dev/cloud.google.com/go/auth/grpctransport)
+  package.
+- To create an ID token please see examples in the
+  [idtoken](https://pkg.go.dev/cloud.google.com/go/auth/credentials/idtoken)
+  package.
+
+## Contributing
+
+Contributions are welcome. Please, see the
+[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
+document for details.
+
+Please note that this project is released with a Contributor Code of Conduct.
+By participating in this project you agree to abide by its terms.
+See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
+for more information.

+ 608 - 0
backend/vendor/cloud.google.com/go/auth/auth.go

@@ -0,0 +1,608 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package auth provides utilities for managing Google Cloud credentials,
+// including functionality for creating, caching, and refreshing OAuth2 tokens.
+// It offers customizable options for different OAuth2 flows, such as 2-legged
+// (2LO) and 3-legged (3LO) OAuth, along with support for PKCE and automatic
+// token management.
+package auth
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"sync"
+	"time"
+
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/jwt"
+)
+
+const (
+	// Parameter keys for AuthCodeURL method to support PKCE.
+	codeChallengeKey       = "code_challenge"
+	codeChallengeMethodKey = "code_challenge_method"
+
+	// Parameter key for Exchange method to support PKCE.
+	codeVerifierKey = "code_verifier"
+
+	// 3 minutes and 45 seconds before expiration. The shortest MDS cache is 4 minutes,
+	// so we give it 15 seconds to refresh it's cache before attempting to refresh a token.
+	defaultExpiryDelta = 225 * time.Second
+
+	universeDomainDefault = "googleapis.com"
+)
+
+// tokenState represents different states for a [Token].
+type tokenState int
+
+const (
+	// fresh indicates that the [Token] is valid. It is not expired or close to
+	// expired, or the token has no expiry.
+	fresh tokenState = iota
+	// stale indicates that the [Token] is close to expired, and should be
+	// refreshed. The token can be used normally.
+	stale
+	// invalid indicates that the [Token] is expired or invalid. The token
+	// cannot be used for a normal operation.
+	invalid
+)
+
+var (
+	defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
+	defaultHeader    = &jwt.Header{Algorithm: jwt.HeaderAlgRSA256, Type: jwt.HeaderType}
+
+	// for testing
+	timeNow = time.Now
+)
+
+// TokenProvider specifies an interface for anything that can return a token.
+type TokenProvider interface {
+	// Token returns a Token or an error.
+	// The Token returned must be safe to use
+	// concurrently.
+	// The returned Token must not be modified.
+	// The context provided must be sent along to any requests that are made in
+	// the implementing code.
+	Token(context.Context) (*Token, error)
+}
+
+// Token holds the credential token used to authorized requests. All fields are
+// considered read-only.
+type Token struct {
+	// Value is the token used to authorize requests. It is usually an access
+	// token but may be other types of tokens such as ID tokens in some flows.
+	Value string
+	// Type is the type of token Value is. If uninitialized, it should be
+	// assumed to be a "Bearer" token.
+	Type string
+	// Expiry is the time the token is set to expire.
+	Expiry time.Time
+	// Metadata  may include, but is not limited to, the body of the token
+	// response returned by the server.
+	Metadata map[string]interface{} // TODO(codyoss): maybe make a method to flatten metadata to avoid []string for url.Values
+}
+
+// IsValid reports that a [Token] is non-nil, has a [Token.Value], and has not
+// expired. A token is considered expired if [Token.Expiry] has passed or will
+// pass in the next 225 seconds.
+func (t *Token) IsValid() bool {
+	return t.isValidWithEarlyExpiry(defaultExpiryDelta)
+}
+
+// MetadataString is a convenience method for accessing string values in the
+// token's metadata. Returns an empty string if the metadata is nil or the value
+// for the given key cannot be cast to a string.
+func (t *Token) MetadataString(k string) string {
+	if t.Metadata == nil {
+		return ""
+	}
+	s, ok := t.Metadata[k].(string)
+	if !ok {
+		return ""
+	}
+	return s
+}
+
+func (t *Token) isValidWithEarlyExpiry(earlyExpiry time.Duration) bool {
+	if t.isEmpty() {
+		return false
+	}
+	if t.Expiry.IsZero() {
+		return true
+	}
+	return !t.Expiry.Round(0).Add(-earlyExpiry).Before(timeNow())
+}
+
+func (t *Token) isEmpty() bool {
+	return t == nil || t.Value == ""
+}
+
+// Credentials holds Google credentials, including
+// [Application Default Credentials].
+//
+// [Application Default Credentials]: https://developers.google.com/accounts/docs/application-default-credentials
+type Credentials struct {
+	json           []byte
+	projectID      CredentialsPropertyProvider
+	quotaProjectID CredentialsPropertyProvider
+	// universeDomain is the default service domain for a given Cloud universe.
+	universeDomain CredentialsPropertyProvider
+
+	TokenProvider
+}
+
+// JSON returns the bytes associated with the the file used to source
+// credentials if one was used.
+func (c *Credentials) JSON() []byte {
+	return c.json
+}
+
+// ProjectID returns the associated project ID from the underlying file or
+// environment.
+func (c *Credentials) ProjectID(ctx context.Context) (string, error) {
+	if c.projectID == nil {
+		return internal.GetProjectID(c.json, ""), nil
+	}
+	v, err := c.projectID.GetProperty(ctx)
+	if err != nil {
+		return "", err
+	}
+	return internal.GetProjectID(c.json, v), nil
+}
+
+// QuotaProjectID returns the associated quota project ID from the underlying
+// file or environment.
+func (c *Credentials) QuotaProjectID(ctx context.Context) (string, error) {
+	if c.quotaProjectID == nil {
+		return internal.GetQuotaProject(c.json, ""), nil
+	}
+	v, err := c.quotaProjectID.GetProperty(ctx)
+	if err != nil {
+		return "", err
+	}
+	return internal.GetQuotaProject(c.json, v), nil
+}
+
+// UniverseDomain returns the default service domain for a given Cloud universe.
+// The default value is "googleapis.com".
+func (c *Credentials) UniverseDomain(ctx context.Context) (string, error) {
+	if c.universeDomain == nil {
+		return universeDomainDefault, nil
+	}
+	v, err := c.universeDomain.GetProperty(ctx)
+	if err != nil {
+		return "", err
+	}
+	if v == "" {
+		return universeDomainDefault, nil
+	}
+	return v, err
+}
+
+// CredentialsPropertyProvider provides an implementation to fetch a property
+// value for [Credentials].
+type CredentialsPropertyProvider interface {
+	GetProperty(context.Context) (string, error)
+}
+
+// CredentialsPropertyFunc is a type adapter to allow the use of ordinary
+// functions as a [CredentialsPropertyProvider].
+type CredentialsPropertyFunc func(context.Context) (string, error)
+
+// GetProperty loads the properly value provided the given context.
+func (p CredentialsPropertyFunc) GetProperty(ctx context.Context) (string, error) {
+	return p(ctx)
+}
+
+// CredentialsOptions are used to configure [Credentials].
+type CredentialsOptions struct {
+	// TokenProvider is a means of sourcing a token for the credentials. Required.
+	TokenProvider TokenProvider
+	// JSON is the raw contents of the credentials file if sourced from a file.
+	JSON []byte
+	// ProjectIDProvider resolves the project ID associated with the
+	// credentials.
+	ProjectIDProvider CredentialsPropertyProvider
+	// QuotaProjectIDProvider resolves the quota project ID associated with the
+	// credentials.
+	QuotaProjectIDProvider CredentialsPropertyProvider
+	// UniverseDomainProvider resolves the universe domain with the credentials.
+	UniverseDomainProvider CredentialsPropertyProvider
+}
+
+// NewCredentials returns new [Credentials] from the provided options.
+func NewCredentials(opts *CredentialsOptions) *Credentials {
+	creds := &Credentials{
+		TokenProvider:  opts.TokenProvider,
+		json:           opts.JSON,
+		projectID:      opts.ProjectIDProvider,
+		quotaProjectID: opts.QuotaProjectIDProvider,
+		universeDomain: opts.UniverseDomainProvider,
+	}
+
+	return creds
+}
+
+// CachedTokenProviderOptions provides options for configuring a cached
+// [TokenProvider].
+type CachedTokenProviderOptions struct {
+	// DisableAutoRefresh makes the TokenProvider always return the same token,
+	// even if it is expired. The default is false. Optional.
+	DisableAutoRefresh bool
+	// ExpireEarly configures the amount of time before a token expires, that it
+	// should be refreshed. If unset, the default value is 3 minutes and 45
+	// seconds. Optional.
+	ExpireEarly time.Duration
+	// DisableAsyncRefresh configures a synchronous workflow that refreshes
+	// tokens in a blocking manner. The default is false. Optional.
+	DisableAsyncRefresh bool
+}
+
+func (ctpo *CachedTokenProviderOptions) autoRefresh() bool {
+	if ctpo == nil {
+		return true
+	}
+	return !ctpo.DisableAutoRefresh
+}
+
+func (ctpo *CachedTokenProviderOptions) expireEarly() time.Duration {
+	if ctpo == nil || ctpo.ExpireEarly == 0 {
+		return defaultExpiryDelta
+	}
+	return ctpo.ExpireEarly
+}
+
+func (ctpo *CachedTokenProviderOptions) blockingRefresh() bool {
+	if ctpo == nil {
+		return false
+	}
+	return ctpo.DisableAsyncRefresh
+}
+
+// NewCachedTokenProvider wraps a [TokenProvider] to cache the tokens returned
+// by the underlying provider. By default it will refresh tokens asynchronously
+// a few minutes before they expire.
+func NewCachedTokenProvider(tp TokenProvider, opts *CachedTokenProviderOptions) TokenProvider {
+	if ctp, ok := tp.(*cachedTokenProvider); ok {
+		return ctp
+	}
+	return &cachedTokenProvider{
+		tp:              tp,
+		autoRefresh:     opts.autoRefresh(),
+		expireEarly:     opts.expireEarly(),
+		blockingRefresh: opts.blockingRefresh(),
+	}
+}
+
+type cachedTokenProvider struct {
+	tp              TokenProvider
+	autoRefresh     bool
+	expireEarly     time.Duration
+	blockingRefresh bool
+
+	mu          sync.Mutex
+	cachedToken *Token
+	// isRefreshRunning ensures that the non-blocking refresh will only be
+	// attempted once, even if multiple callers enter the Token method.
+	isRefreshRunning bool
+	// isRefreshErr ensures that the non-blocking refresh will only be attempted
+	// once per refresh window if an error is encountered.
+	isRefreshErr bool
+}
+
+func (c *cachedTokenProvider) Token(ctx context.Context) (*Token, error) {
+	if c.blockingRefresh {
+		return c.tokenBlocking(ctx)
+	}
+	return c.tokenNonBlocking(ctx)
+}
+
+func (c *cachedTokenProvider) tokenNonBlocking(ctx context.Context) (*Token, error) {
+	switch c.tokenState() {
+	case fresh:
+		c.mu.Lock()
+		defer c.mu.Unlock()
+		return c.cachedToken, nil
+	case stale:
+		// Call tokenAsync with a new Context because the user-provided context
+		// may have a short timeout incompatible with async token refresh.
+		c.tokenAsync(context.Background())
+		// Return the stale token immediately to not block customer requests to Cloud services.
+		c.mu.Lock()
+		defer c.mu.Unlock()
+		return c.cachedToken, nil
+	default: // invalid
+		return c.tokenBlocking(ctx)
+	}
+}
+
+// tokenState reports the token's validity.
+func (c *cachedTokenProvider) tokenState() tokenState {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	t := c.cachedToken
+	now := timeNow()
+	if t == nil || t.Value == "" {
+		return invalid
+	} else if t.Expiry.IsZero() {
+		return fresh
+	} else if now.After(t.Expiry.Round(0)) {
+		return invalid
+	} else if now.After(t.Expiry.Round(0).Add(-c.expireEarly)) {
+		return stale
+	}
+	return fresh
+}
+
+// tokenAsync uses a bool to ensure that only one non-blocking token refresh
+// happens at a time, even if multiple callers have entered this function
+// concurrently. This avoids creating an arbitrary number of concurrent
+// goroutines. Retries should be attempted and managed within the Token method.
+// If the refresh attempt fails, no further attempts are made until the refresh
+// window expires and the token enters the invalid state, at which point the
+// blocking call to Token should likely return the same error on the main goroutine.
+func (c *cachedTokenProvider) tokenAsync(ctx context.Context) {
+	fn := func() {
+		c.mu.Lock()
+		c.isRefreshRunning = true
+		c.mu.Unlock()
+		t, err := c.tp.Token(ctx)
+		c.mu.Lock()
+		defer c.mu.Unlock()
+		c.isRefreshRunning = false
+		if err != nil {
+			// Discard errors from the non-blocking refresh, but prevent further
+			// attempts.
+			c.isRefreshErr = true
+			return
+		}
+		c.cachedToken = t
+	}
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	if !c.isRefreshRunning && !c.isRefreshErr {
+		go fn()
+	}
+}
+
+func (c *cachedTokenProvider) tokenBlocking(ctx context.Context) (*Token, error) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	c.isRefreshErr = false
+	if c.cachedToken.IsValid() || (!c.autoRefresh && !c.cachedToken.isEmpty()) {
+		return c.cachedToken, nil
+	}
+	t, err := c.tp.Token(ctx)
+	if err != nil {
+		return nil, err
+	}
+	c.cachedToken = t
+	return t, nil
+}
+
+// Error is a error associated with retrieving a [Token]. It can hold useful
+// additional details for debugging.
+type Error struct {
+	// Response is the HTTP response associated with error. The body will always
+	// be already closed and consumed.
+	Response *http.Response
+	// Body is the HTTP response body.
+	Body []byte
+	// Err is the underlying wrapped error.
+	Err error
+
+	// code returned in the token response
+	code string
+	// description returned in the token response
+	description string
+	// uri returned in the token response
+	uri string
+}
+
+func (e *Error) Error() string {
+	if e.code != "" {
+		s := fmt.Sprintf("auth: %q", e.code)
+		if e.description != "" {
+			s += fmt.Sprintf(" %q", e.description)
+		}
+		if e.uri != "" {
+			s += fmt.Sprintf(" %q", e.uri)
+		}
+		return s
+	}
+	return fmt.Sprintf("auth: cannot fetch token: %v\nResponse: %s", e.Response.StatusCode, e.Body)
+}
+
+// Temporary returns true if the error is considered temporary and may be able
+// to be retried.
+func (e *Error) Temporary() bool {
+	if e.Response == nil {
+		return false
+	}
+	sc := e.Response.StatusCode
+	return sc == http.StatusInternalServerError || sc == http.StatusServiceUnavailable || sc == http.StatusRequestTimeout || sc == http.StatusTooManyRequests
+}
+
+func (e *Error) Unwrap() error {
+	return e.Err
+}
+
+// Style describes how the token endpoint wants to receive the ClientID and
+// ClientSecret.
+type Style int
+
+const (
+	// StyleUnknown means the value has not been initiated. Sending this in
+	// a request will cause the token exchange to fail.
+	StyleUnknown Style = iota
+	// StyleInParams sends client info in the body of a POST request.
+	StyleInParams
+	// StyleInHeader sends client info using Basic Authorization header.
+	StyleInHeader
+)
+
+// Options2LO is the configuration settings for doing a 2-legged JWT OAuth2 flow.
+type Options2LO struct {
+	// Email is the OAuth2 client ID. This value is set as the "iss" in the
+	// JWT.
+	Email string
+	// PrivateKey contains the contents of an RSA private key or the
+	// contents of a PEM file that contains a private key. It is used to sign
+	// the JWT created.
+	PrivateKey []byte
+	// TokenURL is th URL the JWT is sent to. Required.
+	TokenURL string
+	// PrivateKeyID is the ID of the key used to sign the JWT. It is used as the
+	// "kid" in the JWT header. Optional.
+	PrivateKeyID string
+	// Subject is the used for to impersonate a user. It is used as the "sub" in
+	// the JWT.m Optional.
+	Subject string
+	// Scopes specifies requested permissions for the token. Optional.
+	Scopes []string
+	// Expires specifies the lifetime of the token. Optional.
+	Expires time.Duration
+	// Audience specifies the "aud" in the JWT. Optional.
+	Audience string
+	// PrivateClaims allows specifying any custom claims for the JWT. Optional.
+	PrivateClaims map[string]interface{}
+
+	// Client is the client to be used to make the underlying token requests.
+	// Optional.
+	Client *http.Client
+	// UseIDToken requests that the token returned be an ID token if one is
+	// returned from the server. Optional.
+	UseIDToken bool
+}
+
+func (o *Options2LO) client() *http.Client {
+	if o.Client != nil {
+		return o.Client
+	}
+	return internal.DefaultClient()
+}
+
+func (o *Options2LO) validate() error {
+	if o == nil {
+		return errors.New("auth: options must be provided")
+	}
+	if o.Email == "" {
+		return errors.New("auth: email must be provided")
+	}
+	if len(o.PrivateKey) == 0 {
+		return errors.New("auth: private key must be provided")
+	}
+	if o.TokenURL == "" {
+		return errors.New("auth: token URL must be provided")
+	}
+	return nil
+}
+
+// New2LOTokenProvider returns a [TokenProvider] from the provided options.
+func New2LOTokenProvider(opts *Options2LO) (TokenProvider, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+	return tokenProvider2LO{opts: opts, Client: opts.client()}, nil
+}
+
+type tokenProvider2LO struct {
+	opts   *Options2LO
+	Client *http.Client
+}
+
+func (tp tokenProvider2LO) Token(ctx context.Context) (*Token, error) {
+	pk, err := internal.ParseKey(tp.opts.PrivateKey)
+	if err != nil {
+		return nil, err
+	}
+	claimSet := &jwt.Claims{
+		Iss:              tp.opts.Email,
+		Scope:            strings.Join(tp.opts.Scopes, " "),
+		Aud:              tp.opts.TokenURL,
+		AdditionalClaims: tp.opts.PrivateClaims,
+		Sub:              tp.opts.Subject,
+	}
+	if t := tp.opts.Expires; t > 0 {
+		claimSet.Exp = time.Now().Add(t).Unix()
+	}
+	if aud := tp.opts.Audience; aud != "" {
+		claimSet.Aud = aud
+	}
+	h := *defaultHeader
+	h.KeyID = tp.opts.PrivateKeyID
+	payload, err := jwt.EncodeJWS(&h, claimSet, pk)
+	if err != nil {
+		return nil, err
+	}
+	v := url.Values{}
+	v.Set("grant_type", defaultGrantType)
+	v.Set("assertion", payload)
+	req, err := http.NewRequestWithContext(ctx, "POST", tp.opts.TokenURL, strings.NewReader(v.Encode()))
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	resp, body, err := internal.DoRequest(tp.Client, req)
+	if err != nil {
+		return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
+	}
+	if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
+		return nil, &Error{
+			Response: resp,
+			Body:     body,
+		}
+	}
+	// tokenRes is the JSON response body.
+	var tokenRes struct {
+		AccessToken string `json:"access_token"`
+		TokenType   string `json:"token_type"`
+		IDToken     string `json:"id_token"`
+		ExpiresIn   int64  `json:"expires_in"`
+	}
+	if err := json.Unmarshal(body, &tokenRes); err != nil {
+		return nil, fmt.Errorf("auth: cannot fetch token: %w", err)
+	}
+	token := &Token{
+		Value: tokenRes.AccessToken,
+		Type:  tokenRes.TokenType,
+	}
+	token.Metadata = make(map[string]interface{})
+	json.Unmarshal(body, &token.Metadata) // no error checks for optional fields
+
+	if secs := tokenRes.ExpiresIn; secs > 0 {
+		token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
+	}
+	if v := tokenRes.IDToken; v != "" {
+		// decode returned id token to get expiry
+		claimSet, err := jwt.DecodeJWS(v)
+		if err != nil {
+			return nil, fmt.Errorf("auth: error decoding JWT token: %w", err)
+		}
+		token.Expiry = time.Unix(claimSet.Exp, 0)
+	}
+	if tp.opts.UseIDToken {
+		if tokenRes.IDToken == "" {
+			return nil, fmt.Errorf("auth: response doesn't have JWT token")
+		}
+		token.Value = tokenRes.IDToken
+	}
+	return token, nil
+}

+ 86 - 0
backend/vendor/cloud.google.com/go/auth/credentials/compute.go

@@ -0,0 +1,86 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credentials
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/url"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/compute/metadata"
+)
+
+var (
+	computeTokenMetadata = map[string]interface{}{
+		"auth.google.tokenSource":    "compute-metadata",
+		"auth.google.serviceAccount": "default",
+	}
+	computeTokenURI = "instance/service-accounts/default/token"
+)
+
+// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
+// uses the metadata service to retrieve tokens.
+func computeTokenProvider(opts *DetectOptions) auth.TokenProvider {
+	return auth.NewCachedTokenProvider(computeProvider{scopes: opts.Scopes}, &auth.CachedTokenProviderOptions{
+		ExpireEarly:         opts.EarlyTokenRefresh,
+		DisableAsyncRefresh: opts.DisableAsyncRefresh,
+	})
+}
+
+// computeProvider fetches tokens from the google cloud metadata service.
+type computeProvider struct {
+	scopes []string
+}
+
+type metadataTokenResp struct {
+	AccessToken  string `json:"access_token"`
+	ExpiresInSec int    `json:"expires_in"`
+	TokenType    string `json:"token_type"`
+}
+
+func (cs computeProvider) Token(ctx context.Context) (*auth.Token, error) {
+	tokenURI, err := url.Parse(computeTokenURI)
+	if err != nil {
+		return nil, err
+	}
+	if len(cs.scopes) > 0 {
+		v := url.Values{}
+		v.Set("scopes", strings.Join(cs.scopes, ","))
+		tokenURI.RawQuery = v.Encode()
+	}
+	tokenJSON, err := metadata.GetWithContext(ctx, tokenURI.String())
+	if err != nil {
+		return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+	}
+	var res metadataTokenResp
+	if err := json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res); err != nil {
+		return nil, fmt.Errorf("credentials: invalid token JSON from metadata: %w", err)
+	}
+	if res.ExpiresInSec == 0 || res.AccessToken == "" {
+		return nil, errors.New("credentials: incomplete token received from metadata")
+	}
+	return &auth.Token{
+		Value:    res.AccessToken,
+		Type:     res.TokenType,
+		Expiry:   time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
+		Metadata: computeTokenMetadata,
+	}, nil
+
+}

+ 262 - 0
backend/vendor/cloud.google.com/go/auth/credentials/detect.go

@@ -0,0 +1,262 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credentials
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+	"cloud.google.com/go/compute/metadata"
+)
+
+const (
+	// jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
+	jwtTokenURL = "https://oauth2.googleapis.com/token"
+
+	// Google's OAuth 2.0 default endpoints.
+	googleAuthURL  = "https://accounts.google.com/o/oauth2/auth"
+	googleTokenURL = "https://oauth2.googleapis.com/token"
+
+	// GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
+	GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
+
+	// Help on default credentials
+	adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
+)
+
+var (
+	// for testing
+	allowOnGCECheck = true
+)
+
+// OnGCE reports whether this process is running in Google Cloud.
+func OnGCE() bool {
+	// TODO(codyoss): once all libs use this auth lib move metadata check here
+	return allowOnGCECheck && metadata.OnGCE()
+}
+
+// DetectDefault searches for "Application Default Credentials" and returns
+// a credential based on the [DetectOptions] provided.
+//
+// It looks for credentials in the following places, preferring the first
+// location found:
+//
+//   - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
+//     environment variable. For workload identity federation, refer to
+//     https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
+//     on how to generate the JSON configuration file for on-prem/non-Google
+//     cloud platforms.
+//   - A JSON file in a location known to the gcloud command-line tool. On
+//     Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
+//     other systems, $HOME/.config/gcloud/application_default_credentials.json.
+//   - On Google Compute Engine, Google App Engine standard second generation
+//     runtimes, and Google App Engine flexible environment, it fetches
+//     credentials from the metadata server.
+func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+	if len(opts.CredentialsJSON) > 0 {
+		return readCredentialsFileJSON(opts.CredentialsJSON, opts)
+	}
+	if opts.CredentialsFile != "" {
+		return readCredentialsFile(opts.CredentialsFile, opts)
+	}
+	if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
+		creds, err := readCredentialsFile(filename, opts)
+		if err != nil {
+			return nil, err
+		}
+		return creds, nil
+	}
+
+	fileName := credsfile.GetWellKnownFileName()
+	if b, err := os.ReadFile(fileName); err == nil {
+		return readCredentialsFileJSON(b, opts)
+	}
+
+	if OnGCE() {
+		return auth.NewCredentials(&auth.CredentialsOptions{
+			TokenProvider: computeTokenProvider(opts),
+			ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
+				return metadata.ProjectIDWithContext(ctx)
+			}),
+			UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{},
+		}), nil
+	}
+
+	return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
+}
+
+// DetectOptions provides configuration for [DetectDefault].
+type DetectOptions struct {
+	// Scopes that credentials tokens should have. Example:
+	// https://www.googleapis.com/auth/cloud-platform. Required if Audience is
+	// not provided.
+	Scopes []string
+	// Audience that credentials tokens should have. Only applicable for 2LO
+	// flows with service accounts. If specified, scopes should not be provided.
+	Audience string
+	// Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
+	// Optional.
+	Subject string
+	// EarlyTokenRefresh configures how early before a token expires that it
+	// should be refreshed. Once the token’s time until expiration has entered
+	// this refresh window the token is considered valid but stale. If unset,
+	// the default value is 3 minutes and 45 seconds. Optional.
+	EarlyTokenRefresh time.Duration
+	// DisableAsyncRefresh configures a synchronous workflow that refreshes
+	// stale tokens while blocking. The default is false. Optional.
+	DisableAsyncRefresh bool
+	// AuthHandlerOptions configures an authorization handler and other options
+	// for 3LO flows. It is required, and only used, for client credential
+	// flows.
+	AuthHandlerOptions *auth.AuthorizationHandlerOptions
+	// TokenURL allows to set the token endpoint for user credential flows. If
+	// unset the default value is: https://oauth2.googleapis.com/token.
+	// Optional.
+	TokenURL string
+	// STSAudience is the audience sent to when retrieving an STS token.
+	// Currently this only used for GDCH auth flow, for which it is required.
+	STSAudience string
+	// CredentialsFile overrides detection logic and sources a credential file
+	// from the provided filepath. If provided, CredentialsJSON must not be.
+	// Optional.
+	CredentialsFile string
+	// CredentialsJSON overrides detection logic and uses the JSON bytes as the
+	// source for the credential. If provided, CredentialsFile must not be.
+	// Optional.
+	CredentialsJSON []byte
+	// UseSelfSignedJWT directs service account based credentials to create a
+	// self-signed JWT with the private key found in the file, skipping any
+	// network requests that would normally be made. Optional.
+	UseSelfSignedJWT bool
+	// Client configures the underlying client used to make network requests
+	// when fetching tokens. Optional.
+	Client *http.Client
+	// UniverseDomain is the default service domain for a given Cloud universe.
+	// The default value is "googleapis.com". This option is ignored for
+	// authentication flows that do not support universe domain. Optional.
+	UniverseDomain string
+}
+
+func (o *DetectOptions) validate() error {
+	if o == nil {
+		return errors.New("credentials: options must be provided")
+	}
+	if len(o.Scopes) > 0 && o.Audience != "" {
+		return errors.New("credentials: both scopes and audience were provided")
+	}
+	if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
+		return errors.New("credentials: both credentials file and JSON were provided")
+	}
+	return nil
+}
+
+func (o *DetectOptions) tokenURL() string {
+	if o.TokenURL != "" {
+		return o.TokenURL
+	}
+	return googleTokenURL
+}
+
+func (o *DetectOptions) scopes() []string {
+	scopes := make([]string, len(o.Scopes))
+	copy(scopes, o.Scopes)
+	return scopes
+}
+
+func (o *DetectOptions) client() *http.Client {
+	if o.Client != nil {
+		return o.Client
+	}
+	return internal.DefaultClient()
+}
+
+func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
+	b, err := os.ReadFile(filename)
+	if err != nil {
+		return nil, err
+	}
+	return readCredentialsFileJSON(b, opts)
+}
+
+func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
+	// attempt to parse jsonData as a Google Developers Console client_credentials.json.
+	config := clientCredConfigFromJSON(b, opts)
+	if config != nil {
+		if config.AuthHandlerOpts == nil {
+			return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
+		}
+		tp, err := auth.New3LOTokenProvider(config)
+		if err != nil {
+			return nil, err
+		}
+		return auth.NewCredentials(&auth.CredentialsOptions{
+			TokenProvider: tp,
+			JSON:          b,
+		}), nil
+	}
+	return fileCredentials(b, opts)
+}
+
+func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
+	var creds credsfile.ClientCredentialsFile
+	var c *credsfile.Config3LO
+	if err := json.Unmarshal(b, &creds); err != nil {
+		return nil
+	}
+	switch {
+	case creds.Web != nil:
+		c = creds.Web
+	case creds.Installed != nil:
+		c = creds.Installed
+	default:
+		return nil
+	}
+	if len(c.RedirectURIs) < 1 {
+		return nil
+	}
+	var handleOpts *auth.AuthorizationHandlerOptions
+	if opts.AuthHandlerOptions != nil {
+		handleOpts = &auth.AuthorizationHandlerOptions{
+			Handler:  opts.AuthHandlerOptions.Handler,
+			State:    opts.AuthHandlerOptions.State,
+			PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
+		}
+	}
+	return &auth.Options3LO{
+		ClientID:         c.ClientID,
+		ClientSecret:     c.ClientSecret,
+		RedirectURL:      c.RedirectURIs[0],
+		Scopes:           opts.scopes(),
+		AuthURL:          c.AuthURI,
+		TokenURL:         c.TokenURI,
+		Client:           opts.client(),
+		EarlyTokenExpiry: opts.EarlyTokenRefresh,
+		AuthHandlerOpts:  handleOpts,
+		// TODO(codyoss): refactor this out. We need to add in auto-detection
+		// for this use case.
+		AuthStyle: auth.StyleInParams,
+	}
+}

+ 45 - 0
backend/vendor/cloud.google.com/go/auth/credentials/doc.go

@@ -0,0 +1,45 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package credentials provides support for making OAuth2 authorized and
+// authenticated HTTP requests to Google APIs. It supports the Web server flow,
+// client-side credentials, service accounts, Google Compute Engine service
+// accounts, Google App Engine service accounts and workload identity federation
+// from non-Google cloud platforms.
+//
+// A brief overview of the package follows. For more information, please read
+// https://developers.google.com/accounts/docs/OAuth2
+// and
+// https://developers.google.com/accounts/docs/application-default-credentials.
+// For more information on using workload identity federation, refer to
+// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
+//
+// # Credentials
+//
+// The [cloud.google.com/go/auth.Credentials] type represents Google
+// credentials, including Application Default Credentials.
+//
+// Use [DetectDefault] to obtain Application Default Credentials.
+//
+// Application Default Credentials support workload identity federation to
+// access Google Cloud resources from non-Google Cloud platforms including Amazon
+// Web Services (AWS), Microsoft Azure or any identity provider that supports
+// OpenID Connect (OIDC). Workload identity federation is recommended for
+// non-Google Cloud environments as it avoids the need to download, manage, and
+// store service account private keys locally.
+//
+// # Workforce Identity Federation
+//
+// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
+package credentials

+ 225 - 0
backend/vendor/cloud.google.com/go/auth/credentials/filetypes.go

@@ -0,0 +1,225 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credentials
+
+import (
+	"errors"
+	"fmt"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/credentials/internal/externalaccount"
+	"cloud.google.com/go/auth/credentials/internal/externalaccountuser"
+	"cloud.google.com/go/auth/credentials/internal/gdch"
+	"cloud.google.com/go/auth/credentials/internal/impersonate"
+	internalauth "cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+)
+
+func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
+	fileType, err := credsfile.ParseFileType(b)
+	if err != nil {
+		return nil, err
+	}
+
+	var projectID, universeDomain string
+	var tp auth.TokenProvider
+	switch fileType {
+	case credsfile.ServiceAccountKey:
+		f, err := credsfile.ParseServiceAccount(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleServiceAccount(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		projectID = f.ProjectID
+		universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
+	case credsfile.UserCredentialsKey:
+		f, err := credsfile.ParseUserCredentials(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleUserCredential(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		universeDomain = f.UniverseDomain
+	case credsfile.ExternalAccountKey:
+		f, err := credsfile.ParseExternalAccount(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleExternalAccount(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
+	case credsfile.ExternalAccountAuthorizedUserKey:
+		f, err := credsfile.ParseExternalAccountAuthorizedUser(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleExternalAccountAuthorizedUser(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		universeDomain = f.UniverseDomain
+	case credsfile.ImpersonatedServiceAccountKey:
+		f, err := credsfile.ParseImpersonatedServiceAccount(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleImpersonatedServiceAccount(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
+	case credsfile.GDCHServiceAccountKey:
+		f, err := credsfile.ParseGDCHServiceAccount(b)
+		if err != nil {
+			return nil, err
+		}
+		tp, err = handleGDCHServiceAccount(f, opts)
+		if err != nil {
+			return nil, err
+		}
+		projectID = f.Project
+		universeDomain = f.UniverseDomain
+	default:
+		return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType)
+	}
+	return auth.NewCredentials(&auth.CredentialsOptions{
+		TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{
+			ExpireEarly: opts.EarlyTokenRefresh,
+		}),
+		JSON:              b,
+		ProjectIDProvider: internalauth.StaticCredentialsProperty(projectID),
+		// TODO(codyoss): only set quota project here if there was a user override
+		UniverseDomainProvider: internalauth.StaticCredentialsProperty(universeDomain),
+	}), nil
+}
+
+// resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to
+// support configuring universe-specific credentials in code. Auth flows
+// unsupported for universe domain should not use this func, but should instead
+// simply set the file universe domain on the credentials.
+func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string {
+	if optsUniverseDomain != "" {
+		return optsUniverseDomain
+	}
+	return fileUniverseDomain
+}
+
+func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
+	if opts.UseSelfSignedJWT {
+		return configureSelfSignedJWT(f, opts)
+	} else if ud != "" && ud != internalauth.DefaultUniverseDomain {
+		// For non-GDU universe domains, token exchange is impossible and services
+		// must support self-signed JWTs.
+		opts.UseSelfSignedJWT = true
+		return configureSelfSignedJWT(f, opts)
+	}
+	opts2LO := &auth.Options2LO{
+		Email:        f.ClientEmail,
+		PrivateKey:   []byte(f.PrivateKey),
+		PrivateKeyID: f.PrivateKeyID,
+		Scopes:       opts.scopes(),
+		TokenURL:     f.TokenURL,
+		Subject:      opts.Subject,
+		Client:       opts.client(),
+	}
+	if opts2LO.TokenURL == "" {
+		opts2LO.TokenURL = jwtTokenURL
+	}
+	return auth.New2LOTokenProvider(opts2LO)
+}
+
+func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	opts3LO := &auth.Options3LO{
+		ClientID:         f.ClientID,
+		ClientSecret:     f.ClientSecret,
+		Scopes:           opts.scopes(),
+		AuthURL:          googleAuthURL,
+		TokenURL:         opts.tokenURL(),
+		AuthStyle:        auth.StyleInParams,
+		EarlyTokenExpiry: opts.EarlyTokenRefresh,
+		RefreshToken:     f.RefreshToken,
+		Client:           opts.client(),
+	}
+	return auth.New3LOTokenProvider(opts3LO)
+}
+
+func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	externalOpts := &externalaccount.Options{
+		Audience:                       f.Audience,
+		SubjectTokenType:               f.SubjectTokenType,
+		TokenURL:                       f.TokenURL,
+		TokenInfoURL:                   f.TokenInfoURL,
+		ServiceAccountImpersonationURL: f.ServiceAccountImpersonationURL,
+		ClientSecret:                   f.ClientSecret,
+		ClientID:                       f.ClientID,
+		CredentialSource:               f.CredentialSource,
+		QuotaProjectID:                 f.QuotaProjectID,
+		Scopes:                         opts.scopes(),
+		WorkforcePoolUserProject:       f.WorkforcePoolUserProject,
+		Client:                         opts.client(),
+		IsDefaultClient:                opts.Client == nil,
+	}
+	if f.ServiceAccountImpersonation != nil {
+		externalOpts.ServiceAccountImpersonationLifetimeSeconds = f.ServiceAccountImpersonation.TokenLifetimeSeconds
+	}
+	return externalaccount.NewTokenProvider(externalOpts)
+}
+
+func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	externalOpts := &externalaccountuser.Options{
+		Audience:     f.Audience,
+		RefreshToken: f.RefreshToken,
+		TokenURL:     f.TokenURL,
+		TokenInfoURL: f.TokenInfoURL,
+		ClientID:     f.ClientID,
+		ClientSecret: f.ClientSecret,
+		Scopes:       opts.scopes(),
+		Client:       opts.client(),
+	}
+	return externalaccountuser.NewTokenProvider(externalOpts)
+}
+
+func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	if f.ServiceAccountImpersonationURL == "" || f.CredSource == nil {
+		return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials")
+	}
+
+	tp, err := fileCredentials(f.CredSource, opts)
+	if err != nil {
+		return nil, err
+	}
+	return impersonate.NewTokenProvider(&impersonate.Options{
+		URL:       f.ServiceAccountImpersonationURL,
+		Scopes:    opts.scopes(),
+		Tp:        tp,
+		Delegates: f.Delegates,
+		Client:    opts.client(),
+	})
+}
+
+func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	return gdch.NewTokenProvider(f, &gdch.Options{
+		STSAudience: opts.STSAudience,
+		Client:      opts.client(),
+	})
+}

+ 520 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go

@@ -0,0 +1,520 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"bytes"
+	"context"
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"os"
+	"path"
+	"sort"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth/internal"
+)
+
+var (
+	// getenv aliases os.Getenv for testing
+	getenv = os.Getenv
+)
+
+const (
+	// AWS Signature Version 4 signing algorithm identifier.
+	awsAlgorithm = "AWS4-HMAC-SHA256"
+
+	// The termination string for the AWS credential scope value as defined in
+	// https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
+	awsRequestType = "aws4_request"
+
+	// The AWS authorization header name for the security session token if available.
+	awsSecurityTokenHeader = "x-amz-security-token"
+
+	// The name of the header containing the session token for metadata endpoint calls
+	awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
+
+	awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
+
+	awsIMDSv2SessionTTL = "300"
+
+	// The AWS authorization header name for the auto-generated date.
+	awsDateHeader = "x-amz-date"
+
+	defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
+
+	// Supported AWS configuration environment variables.
+	awsAccessKeyIDEnvVar     = "AWS_ACCESS_KEY_ID"
+	awsDefaultRegionEnvVar   = "AWS_DEFAULT_REGION"
+	awsRegionEnvVar          = "AWS_REGION"
+	awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
+	awsSessionTokenEnvVar    = "AWS_SESSION_TOKEN"
+
+	awsTimeFormatLong  = "20060102T150405Z"
+	awsTimeFormatShort = "20060102"
+	awsProviderType    = "aws"
+)
+
+type awsSubjectProvider struct {
+	EnvironmentID               string
+	RegionURL                   string
+	RegionalCredVerificationURL string
+	CredVerificationURL         string
+	IMDSv2SessionTokenURL       string
+	TargetResource              string
+	requestSigner               *awsRequestSigner
+	region                      string
+	securityCredentialsProvider AwsSecurityCredentialsProvider
+	reqOpts                     *RequestOptions
+
+	Client *http.Client
+}
+
+func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+	// Set Defaults
+	if sp.RegionalCredVerificationURL == "" {
+		sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
+	}
+	headers := make(map[string]string)
+	if sp.shouldUseMetadataServer() {
+		awsSessionToken, err := sp.getAWSSessionToken(ctx)
+		if err != nil {
+			return "", err
+		}
+
+		if awsSessionToken != "" {
+			headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
+		}
+	}
+
+	awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
+	if err != nil {
+		return "", err
+	}
+	if sp.region, err = sp.getRegion(ctx, headers); err != nil {
+		return "", err
+	}
+	sp.requestSigner = &awsRequestSigner{
+		RegionName:             sp.region,
+		AwsSecurityCredentials: awsSecurityCredentials,
+	}
+
+	// Generate the signed request to AWS STS GetCallerIdentity API.
+	// Use the required regional endpoint. Otherwise, the request will fail.
+	req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
+	if err != nil {
+		return "", err
+	}
+	// The full, canonical resource name of the workload identity pool
+	// provider, with or without the HTTPS prefix.
+	// Including this header as part of the signature is recommended to
+	// ensure data integrity.
+	if sp.TargetResource != "" {
+		req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
+	}
+	sp.requestSigner.signRequest(req)
+
+	/*
+	   The GCP STS endpoint expects the headers to be formatted as:
+	   # [
+	   #   {key: 'x-amz-date', value: '...'},
+	   #   {key: 'Authorization', value: '...'},
+	   #   ...
+	   # ]
+	   # And then serialized as:
+	   # quote(json.dumps({
+	   #   url: '...',
+	   #   method: 'POST',
+	   #   headers: [{key: 'x-amz-date', value: '...'}, ...]
+	   # }))
+	*/
+
+	awsSignedReq := awsRequest{
+		URL:    req.URL.String(),
+		Method: "POST",
+	}
+	for headerKey, headerList := range req.Header {
+		for _, headerValue := range headerList {
+			awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{
+				Key:   headerKey,
+				Value: headerValue,
+			})
+		}
+	}
+	sort.Slice(awsSignedReq.Headers, func(i, j int) bool {
+		headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key)
+		if headerCompare == 0 {
+			return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0
+		}
+		return headerCompare < 0
+	})
+
+	result, err := json.Marshal(awsSignedReq)
+	if err != nil {
+		return "", err
+	}
+	return url.QueryEscape(string(result)), nil
+}
+
+func (sp *awsSubjectProvider) providerType() string {
+	if sp.securityCredentialsProvider != nil {
+		return programmaticProviderType
+	}
+	return awsProviderType
+}
+
+func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
+	if sp.IMDSv2SessionTokenURL == "" {
+		return "", nil
+	}
+	req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
+	if err != nil {
+		return "", err
+	}
+	req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
+
+	resp, body, err := internal.DoRequest(sp.Client, req)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
+	}
+	return string(body), nil
+}
+
+func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
+	if sp.securityCredentialsProvider != nil {
+		return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
+	}
+	if canRetrieveRegionFromEnvironment() {
+		if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
+			return envAwsRegion, nil
+		}
+		return getenv(awsDefaultRegionEnvVar), nil
+	}
+
+	if sp.RegionURL == "" {
+		return "", errors.New("credentials: unable to determine AWS region")
+	}
+
+	req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
+	if err != nil {
+		return "", err
+	}
+
+	for name, value := range headers {
+		req.Header.Add(name, value)
+	}
+	resp, body, err := internal.DoRequest(sp.Client, req)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
+	}
+
+	// This endpoint will return the region in format: us-east-2b.
+	// Only the us-east-2 part should be used.
+	bodyLen := len(body)
+	if bodyLen == 0 {
+		return "", nil
+	}
+	return string(body[:bodyLen-1]), nil
+}
+
+func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
+	if sp.securityCredentialsProvider != nil {
+		return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
+	}
+	if canRetrieveSecurityCredentialFromEnvironment() {
+		return &AwsSecurityCredentials{
+			AccessKeyID:     getenv(awsAccessKeyIDEnvVar),
+			SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
+			SessionToken:    getenv(awsSessionTokenEnvVar),
+		}, nil
+	}
+
+	roleName, err := sp.getMetadataRoleName(ctx, headers)
+	if err != nil {
+		return
+	}
+	credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
+	if err != nil {
+		return
+	}
+
+	if credentials.AccessKeyID == "" {
+		return result, errors.New("credentials: missing AccessKeyId credential")
+	}
+	if credentials.SecretAccessKey == "" {
+		return result, errors.New("credentials: missing SecretAccessKey credential")
+	}
+
+	return credentials, nil
+}
+
+func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
+	var result *AwsSecurityCredentials
+
+	req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
+	if err != nil {
+		return result, err
+	}
+	for name, value := range headers {
+		req.Header.Add(name, value)
+	}
+	resp, body, err := internal.DoRequest(sp.Client, req)
+	if err != nil {
+		return result, err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
+	}
+	if err := json.Unmarshal(body, &result); err != nil {
+		return nil, err
+	}
+	return result, nil
+}
+
+func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
+	if sp.CredVerificationURL == "" {
+		return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
+	}
+	req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
+	if err != nil {
+		return "", err
+	}
+	for name, value := range headers {
+		req.Header.Add(name, value)
+	}
+
+	resp, body, err := internal.DoRequest(sp.Client, req)
+	if err != nil {
+		return "", err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
+	}
+	return string(body), nil
+}
+
+// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
+type awsRequestSigner struct {
+	RegionName             string
+	AwsSecurityCredentials *AwsSecurityCredentials
+}
+
+// signRequest adds the appropriate headers to an http.Request
+// or returns an error if something prevented this.
+func (rs *awsRequestSigner) signRequest(req *http.Request) error {
+	// req is assumed non-nil
+	signedRequest := cloneRequest(req)
+	timestamp := Now()
+	signedRequest.Header.Set("host", requestHost(req))
+	if rs.AwsSecurityCredentials.SessionToken != "" {
+		signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
+	}
+	if signedRequest.Header.Get("date") == "" {
+		signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
+	}
+	authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
+	if err != nil {
+		return err
+	}
+	signedRequest.Header.Set("Authorization", authorizationCode)
+	req.Header = signedRequest.Header
+	return nil
+}
+
+func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
+	canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
+	dateStamp := timestamp.Format(awsTimeFormatShort)
+	serviceName := ""
+
+	if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
+		serviceName = splitHost[0]
+	}
+	credentialScope := strings.Join([]string{dateStamp, rs.RegionName, serviceName, awsRequestType}, "/")
+	requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData)
+	if err != nil {
+		return "", err
+	}
+	requestHash, err := getSha256([]byte(requestString))
+	if err != nil {
+		return "", err
+	}
+
+	stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
+	signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey)
+	for _, signingInput := range []string{
+		dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign,
+	} {
+		signingKey, err = getHmacSha256(signingKey, []byte(signingInput))
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
+}
+
+func getSha256(input []byte) (string, error) {
+	hash := sha256.New()
+	if _, err := hash.Write(input); err != nil {
+		return "", err
+	}
+	return hex.EncodeToString(hash.Sum(nil)), nil
+}
+
+func getHmacSha256(key, input []byte) ([]byte, error) {
+	hash := hmac.New(sha256.New, key)
+	if _, err := hash.Write(input); err != nil {
+		return nil, err
+	}
+	return hash.Sum(nil), nil
+}
+
+func cloneRequest(r *http.Request) *http.Request {
+	r2 := new(http.Request)
+	*r2 = *r
+	if r.Header != nil {
+		r2.Header = make(http.Header, len(r.Header))
+
+		// Find total number of values.
+		headerCount := 0
+		for _, headerValues := range r.Header {
+			headerCount += len(headerValues)
+		}
+		copiedHeaders := make([]string, headerCount) // shared backing array for headers' values
+
+		for headerKey, headerValues := range r.Header {
+			headerCount = copy(copiedHeaders, headerValues)
+			r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount]
+			copiedHeaders = copiedHeaders[headerCount:]
+		}
+	}
+	return r2
+}
+
+func canonicalPath(req *http.Request) string {
+	result := req.URL.EscapedPath()
+	if result == "" {
+		return "/"
+	}
+	return path.Clean(result)
+}
+
+func canonicalQuery(req *http.Request) string {
+	queryValues := req.URL.Query()
+	for queryKey := range queryValues {
+		sort.Strings(queryValues[queryKey])
+	}
+	return queryValues.Encode()
+}
+
+func canonicalHeaders(req *http.Request) (string, string) {
+	// Header keys need to be sorted alphabetically.
+	var headers []string
+	lowerCaseHeaders := make(http.Header)
+	for k, v := range req.Header {
+		k := strings.ToLower(k)
+		if _, ok := lowerCaseHeaders[k]; ok {
+			// include additional values
+			lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...)
+		} else {
+			headers = append(headers, k)
+			lowerCaseHeaders[k] = v
+		}
+	}
+	sort.Strings(headers)
+
+	var fullHeaders bytes.Buffer
+	for _, header := range headers {
+		headerValue := strings.Join(lowerCaseHeaders[header], ",")
+		fullHeaders.WriteString(header)
+		fullHeaders.WriteRune(':')
+		fullHeaders.WriteString(headerValue)
+		fullHeaders.WriteRune('\n')
+	}
+
+	return strings.Join(headers, ";"), fullHeaders.String()
+}
+
+func requestDataHash(req *http.Request) (string, error) {
+	var requestData []byte
+	if req.Body != nil {
+		requestBody, err := req.GetBody()
+		if err != nil {
+			return "", err
+		}
+		defer requestBody.Close()
+
+		requestData, err = internal.ReadAll(requestBody)
+		if err != nil {
+			return "", err
+		}
+	}
+
+	return getSha256(requestData)
+}
+
+func requestHost(req *http.Request) string {
+	if req.Host != "" {
+		return req.Host
+	}
+	return req.URL.Host
+}
+
+func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) {
+	dataHash, err := requestDataHash(req)
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
+}
+
+type awsRequestHeader struct {
+	Key   string `json:"key"`
+	Value string `json:"value"`
+}
+
+type awsRequest struct {
+	URL     string             `json:"url"`
+	Method  string             `json:"method"`
+	Headers []awsRequestHeader `json:"headers"`
+}
+
+// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
+// required.
+func canRetrieveRegionFromEnvironment() bool {
+	return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
+}
+
+// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
+func canRetrieveSecurityCredentialFromEnvironment() bool {
+	return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
+}
+
+func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
+	return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
+}

+ 284 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go

@@ -0,0 +1,284 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"os"
+	"os/exec"
+	"regexp"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth/internal"
+)
+
+const (
+	executableSupportedMaxVersion = 1
+	executableDefaultTimeout      = 30 * time.Second
+	executableSource              = "response"
+	executableProviderType        = "executable"
+	outputFileSource              = "output file"
+
+	allowExecutablesEnvVar = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
+
+	jwtTokenType   = "urn:ietf:params:oauth:token-type:jwt"
+	idTokenType    = "urn:ietf:params:oauth:token-type:id_token"
+	saml2TokenType = "urn:ietf:params:oauth:token-type:saml2"
+)
+
+var (
+	serviceAccountImpersonationRE = regexp.MustCompile(`https://iamcredentials..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken`)
+)
+
+type nonCacheableError struct {
+	message string
+}
+
+func (nce nonCacheableError) Error() string {
+	return nce.message
+}
+
+// environment is a contract for testing
+type environment interface {
+	existingEnv() []string
+	getenv(string) string
+	run(ctx context.Context, command string, env []string) ([]byte, error)
+	now() time.Time
+}
+
+type runtimeEnvironment struct{}
+
+func (r runtimeEnvironment) existingEnv() []string {
+	return os.Environ()
+}
+func (r runtimeEnvironment) getenv(key string) string {
+	return os.Getenv(key)
+}
+func (r runtimeEnvironment) now() time.Time {
+	return time.Now().UTC()
+}
+
+func (r runtimeEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
+	splitCommand := strings.Fields(command)
+	cmd := exec.CommandContext(ctx, splitCommand[0], splitCommand[1:]...)
+	cmd.Env = env
+
+	var stdout, stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+
+	if err := cmd.Run(); err != nil {
+		if ctx.Err() == context.DeadlineExceeded {
+			return nil, context.DeadlineExceeded
+		}
+		if exitError, ok := err.(*exec.ExitError); ok {
+			return nil, exitCodeError(exitError)
+		}
+		return nil, executableError(err)
+	}
+
+	bytesStdout := bytes.TrimSpace(stdout.Bytes())
+	if len(bytesStdout) > 0 {
+		return bytesStdout, nil
+	}
+	return bytes.TrimSpace(stderr.Bytes()), nil
+}
+
+type executableSubjectProvider struct {
+	Command    string
+	Timeout    time.Duration
+	OutputFile string
+	client     *http.Client
+	opts       *Options
+	env        environment
+}
+
+type executableResponse struct {
+	Version        int    `json:"version,omitempty"`
+	Success        *bool  `json:"success,omitempty"`
+	TokenType      string `json:"token_type,omitempty"`
+	ExpirationTime int64  `json:"expiration_time,omitempty"`
+	IDToken        string `json:"id_token,omitempty"`
+	SamlResponse   string `json:"saml_response,omitempty"`
+	Code           string `json:"code,omitempty"`
+	Message        string `json:"message,omitempty"`
+}
+
+func (sp *executableSubjectProvider) parseSubjectTokenFromSource(response []byte, source string, now int64) (string, error) {
+	var result executableResponse
+	if err := json.Unmarshal(response, &result); err != nil {
+		return "", jsonParsingError(source, string(response))
+	}
+	// Validate
+	if result.Version == 0 {
+		return "", missingFieldError(source, "version")
+	}
+	if result.Success == nil {
+		return "", missingFieldError(source, "success")
+	}
+	if !*result.Success {
+		if result.Code == "" || result.Message == "" {
+			return "", malformedFailureError()
+		}
+		return "", userDefinedError(result.Code, result.Message)
+	}
+	if result.Version > executableSupportedMaxVersion || result.Version < 0 {
+		return "", unsupportedVersionError(source, result.Version)
+	}
+	if result.ExpirationTime == 0 && sp.OutputFile != "" {
+		return "", missingFieldError(source, "expiration_time")
+	}
+	if result.TokenType == "" {
+		return "", missingFieldError(source, "token_type")
+	}
+	if result.ExpirationTime != 0 && result.ExpirationTime < now {
+		return "", tokenExpiredError()
+	}
+
+	switch result.TokenType {
+	case jwtTokenType, idTokenType:
+		if result.IDToken == "" {
+			return "", missingFieldError(source, "id_token")
+		}
+		return result.IDToken, nil
+	case saml2TokenType:
+		if result.SamlResponse == "" {
+			return "", missingFieldError(source, "saml_response")
+		}
+		return result.SamlResponse, nil
+	default:
+		return "", tokenTypeError(source)
+	}
+}
+
+func (sp *executableSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+	if token, err := sp.getTokenFromOutputFile(); token != "" || err != nil {
+		return token, err
+	}
+	return sp.getTokenFromExecutableCommand(ctx)
+}
+
+func (sp *executableSubjectProvider) providerType() string {
+	return executableProviderType
+}
+
+func (sp *executableSubjectProvider) getTokenFromOutputFile() (token string, err error) {
+	if sp.OutputFile == "" {
+		// This ExecutableCredentialSource doesn't use an OutputFile.
+		return "", nil
+	}
+
+	file, err := os.Open(sp.OutputFile)
+	if err != nil {
+		// No OutputFile found. Hasn't been created yet, so skip it.
+		return "", nil
+	}
+	defer file.Close()
+
+	data, err := internal.ReadAll(file)
+	if err != nil || len(data) == 0 {
+		// Cachefile exists, but no data found. Get new credential.
+		return "", nil
+	}
+
+	token, err = sp.parseSubjectTokenFromSource(data, outputFileSource, sp.env.now().Unix())
+	if err != nil {
+		if _, ok := err.(nonCacheableError); ok {
+			// If the cached token is expired we need a new token,
+			// and if the cache contains a failure, we need to try again.
+			return "", nil
+		}
+
+		// There was an error in the cached token, and the developer should be aware of it.
+		return "", err
+	}
+	// Token parsing succeeded.  Use found token.
+	return token, nil
+}
+
+func (sp *executableSubjectProvider) executableEnvironment() []string {
+	result := sp.env.existingEnv()
+	result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=%v", sp.opts.Audience))
+	result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=%v", sp.opts.SubjectTokenType))
+	result = append(result, "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0")
+	if sp.opts.ServiceAccountImpersonationURL != "" {
+		matches := serviceAccountImpersonationRE.FindStringSubmatch(sp.opts.ServiceAccountImpersonationURL)
+		if matches != nil {
+			result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=%v", matches[1]))
+		}
+	}
+	if sp.OutputFile != "" {
+		result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=%v", sp.OutputFile))
+	}
+	return result
+}
+
+func (sp *executableSubjectProvider) getTokenFromExecutableCommand(ctx context.Context) (string, error) {
+	// For security reasons, we need our consumers to set this environment variable to allow executables to be run.
+	if sp.env.getenv(allowExecutablesEnvVar) != "1" {
+		return "", errors.New("credentials: executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run")
+	}
+
+	ctx, cancel := context.WithDeadline(ctx, sp.env.now().Add(sp.Timeout))
+	defer cancel()
+
+	output, err := sp.env.run(ctx, sp.Command, sp.executableEnvironment())
+	if err != nil {
+		return "", err
+	}
+	return sp.parseSubjectTokenFromSource(output, executableSource, sp.env.now().Unix())
+}
+
+func missingFieldError(source, field string) error {
+	return fmt.Errorf("credentials: %q missing %q field", source, field)
+}
+
+func jsonParsingError(source, data string) error {
+	return fmt.Errorf("credentials: unable to parse %q: %v", source, data)
+}
+
+func malformedFailureError() error {
+	return nonCacheableError{"credentials: response must include `error` and `message` fields when unsuccessful"}
+}
+
+func userDefinedError(code, message string) error {
+	return nonCacheableError{fmt.Sprintf("credentials: response contains unsuccessful response: (%v) %v", code, message)}
+}
+
+func unsupportedVersionError(source string, version int) error {
+	return fmt.Errorf("credentials: %v contains unsupported version: %v", source, version)
+}
+
+func tokenExpiredError() error {
+	return nonCacheableError{"credentials: the token returned by the executable is expired"}
+}
+
+func tokenTypeError(source string) error {
+	return fmt.Errorf("credentials: %v contains unsupported token type", source)
+}
+
+func exitCodeError(err *exec.ExitError) error {
+	return fmt.Errorf("credentials: executable command failed with exit code %v: %w", err.ExitCode(), err)
+}
+
+func executableError(err error) error {
+	return fmt.Errorf("credentials: executable command failed: %w", err)
+}

+ 407 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go

@@ -0,0 +1,407 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strconv"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/credentials/internal/impersonate"
+	"cloud.google.com/go/auth/credentials/internal/stsexchange"
+	"cloud.google.com/go/auth/internal/credsfile"
+)
+
+const (
+	timeoutMinimum = 5 * time.Second
+	timeoutMaximum = 120 * time.Second
+
+	universeDomainPlaceholder = "UNIVERSE_DOMAIN"
+	defaultTokenURL           = "https://sts.UNIVERSE_DOMAIN/v1/token"
+	defaultUniverseDomain     = "googleapis.com"
+)
+
+var (
+	// Now aliases time.Now for testing
+	Now = func() time.Time {
+		return time.Now().UTC()
+	}
+	validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
+)
+
+// Options stores the configuration for fetching tokens with external credentials.
+type Options struct {
+	// Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
+	// identity pool or the workforce pool and the provider identifier in that pool.
+	Audience string
+	// SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
+	// e.g. `urn:ietf:params:oauth:token-type:jwt`.
+	SubjectTokenType string
+	// TokenURL is the STS token exchange endpoint.
+	TokenURL string
+	// TokenInfoURL is the token_info endpoint used to retrieve the account related information (
+	// user attributes like account identifier, eg. email, username, uid, etc). This is
+	// needed for gCloud session account identification.
+	TokenInfoURL string
+	// ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
+	// required for workload identity pools when APIs to be accessed have not integrated with UberMint.
+	ServiceAccountImpersonationURL string
+	// ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation
+	// token will be valid for.
+	ServiceAccountImpersonationLifetimeSeconds int
+	// ClientSecret is currently only required if token_info endpoint also
+	// needs to be called with the generated GCP access token. When provided, STS will be
+	// called with additional basic authentication using client_id as username and client_secret as password.
+	ClientSecret string
+	// ClientID is only required in conjunction with ClientSecret, as described above.
+	ClientID string
+	// CredentialSource contains the necessary information to retrieve the token itself, as well
+	// as some environmental information.
+	CredentialSource *credsfile.CredentialSource
+	// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
+	// will set the x-goog-user-project which overrides the project associated with the credentials.
+	QuotaProjectID string
+	// Scopes contains the desired scopes for the returned access token.
+	Scopes []string
+	// WorkforcePoolUserProject should be set when it is a workforce pool and
+	// not a workload identity pool. The underlying principal must still have
+	// serviceusage.services.use IAM permission to use the project for
+	// billing/quota. Optional.
+	WorkforcePoolUserProject string
+	// UniverseDomain is the default service domain for a given Cloud universe.
+	// This value will be used in the default STS token URL. The default value
+	// is "googleapis.com". It will not be used if TokenURL is set. Optional.
+	UniverseDomain string
+	// SubjectTokenProvider is an optional token provider for OIDC/SAML
+	// credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
+	// or CredentialSource must be provided. Optional.
+	SubjectTokenProvider SubjectTokenProvider
+	// AwsSecurityCredentialsProvider is an AWS Security Credential provider
+	// for AWS credentials. One of SubjectTokenProvider,
+	// AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
+	AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
+	// Client for token request.
+	Client *http.Client
+	// IsDefaultClient marks whether the client passed in is a default client that can be overriden.
+	// This is important for X509 credentials which should create a new client if the default was used
+	// but should respect a client explicitly passed in by the user.
+	IsDefaultClient bool
+}
+
+// SubjectTokenProvider can be used to supply a subject token to exchange for a
+// GCP access token.
+type SubjectTokenProvider interface {
+	// SubjectToken should return a valid subject token or an error.
+	// The external account token provider does not cache the returned subject
+	// token, so caching logic should be implemented in the provider to prevent
+	// multiple requests for the same subject token.
+	SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
+}
+
+// RequestOptions contains information about the requested subject token or AWS
+// security credentials from the Google external account credential.
+type RequestOptions struct {
+	// Audience is the requested audience for the external account credential.
+	Audience string
+	// Subject token type is the requested subject token type for the external
+	// account credential. Expected values include:
+	// “urn:ietf:params:oauth:token-type:jwt”
+	// “urn:ietf:params:oauth:token-type:id-token”
+	// “urn:ietf:params:oauth:token-type:saml2”
+	// “urn:ietf:params:aws:token-type:aws4_request”
+	SubjectTokenType string
+}
+
+// AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
+// and an AWS Region to exchange for a GCP access token.
+type AwsSecurityCredentialsProvider interface {
+	// AwsRegion should return the AWS region or an error.
+	AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
+	// GetAwsSecurityCredentials should return a valid set of
+	// AwsSecurityCredentials or an error. The external account token provider
+	// does not cache the returned security credentials, so caching logic should
+	// be implemented in the provider to prevent multiple requests for the
+	// same security credentials.
+	AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
+}
+
+// AwsSecurityCredentials models AWS security credentials.
+type AwsSecurityCredentials struct {
+	// AccessKeyId is the AWS Access Key ID - Required.
+	AccessKeyID string `json:"AccessKeyID"`
+	// SecretAccessKey is the AWS Secret Access Key - Required.
+	SecretAccessKey string `json:"SecretAccessKey"`
+	// SessionToken is the AWS Session token. This should be provided for
+	// temporary AWS security credentials - Optional.
+	SessionToken string `json:"Token"`
+}
+
+func (o *Options) validate() error {
+	if o.Audience == "" {
+		return fmt.Errorf("externalaccount: Audience must be set")
+	}
+	if o.SubjectTokenType == "" {
+		return fmt.Errorf("externalaccount: Subject token type must be set")
+	}
+	if o.WorkforcePoolUserProject != "" {
+		if valid := validWorkforceAudiencePattern.MatchString(o.Audience); !valid {
+			return fmt.Errorf("externalaccount: workforce_pool_user_project should not be set for non-workforce pool credentials")
+		}
+	}
+	count := 0
+	if o.CredentialSource != nil {
+		count++
+	}
+	if o.SubjectTokenProvider != nil {
+		count++
+	}
+	if o.AwsSecurityCredentialsProvider != nil {
+		count++
+	}
+	if count == 0 {
+		return fmt.Errorf("externalaccount: one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
+	}
+	if count > 1 {
+		return fmt.Errorf("externalaccount: only one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
+	}
+	return nil
+}
+
+// client returns the http client that should be used for the token exchange. If a non-default client
+// is provided, then the client configured in the options will always be returned. If a default client
+// is provided and the options are configured for X509 credentials, a new client will be created.
+func (o *Options) client() (*http.Client, error) {
+	// If a client was provided and no override certificate config location was provided, use the provided client.
+	if o.CredentialSource == nil || o.CredentialSource.Certificate == nil || (!o.IsDefaultClient && o.CredentialSource.Certificate.CertificateConfigLocation == "") {
+		return o.Client, nil
+	}
+
+	// If a new client should be created, validate and use the certificate source to create a new mTLS client.
+	cert := o.CredentialSource.Certificate
+	if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
+		return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
+	}
+	if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
+		return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
+	}
+	return createX509Client(cert.CertificateConfigLocation)
+}
+
+// resolveTokenURL sets the default STS token endpoint with the configured
+// universe domain.
+func (o *Options) resolveTokenURL() {
+	if o.TokenURL != "" {
+		return
+	} else if o.UniverseDomain != "" {
+		o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, o.UniverseDomain, 1)
+	} else {
+		o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, defaultUniverseDomain, 1)
+	}
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
+// configured with the provided options.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+	opts.resolveTokenURL()
+	stp, err := newSubjectTokenProvider(opts)
+	if err != nil {
+		return nil, err
+	}
+
+	client, err := opts.client()
+	if err != nil {
+		return nil, err
+	}
+
+	tp := &tokenProvider{
+		client: client,
+		opts:   opts,
+		stp:    stp,
+	}
+
+	if opts.ServiceAccountImpersonationURL == "" {
+		return auth.NewCachedTokenProvider(tp, nil), nil
+	}
+
+	scopes := make([]string, len(opts.Scopes))
+	copy(scopes, opts.Scopes)
+	// needed for impersonation
+	tp.opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
+	imp, err := impersonate.NewTokenProvider(&impersonate.Options{
+		Client:               client,
+		URL:                  opts.ServiceAccountImpersonationURL,
+		Scopes:               scopes,
+		Tp:                   auth.NewCachedTokenProvider(tp, nil),
+		TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
+	})
+	if err != nil {
+		return nil, err
+	}
+	return auth.NewCachedTokenProvider(imp, nil), nil
+}
+
+type subjectTokenProvider interface {
+	subjectToken(ctx context.Context) (string, error)
+	providerType() string
+}
+
+// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
+type tokenProvider struct {
+	client *http.Client
+	opts   *Options
+	stp    subjectTokenProvider
+}
+
+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
+	subjectToken, err := tp.stp.subjectToken(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	stsRequest := &stsexchange.TokenRequest{
+		GrantType:          stsexchange.GrantType,
+		Audience:           tp.opts.Audience,
+		Scope:              tp.opts.Scopes,
+		RequestedTokenType: stsexchange.TokenType,
+		SubjectToken:       subjectToken,
+		SubjectTokenType:   tp.opts.SubjectTokenType,
+	}
+	header := make(http.Header)
+	header.Set("Content-Type", "application/x-www-form-urlencoded")
+	header.Add("x-goog-api-client", getGoogHeaderValue(tp.opts, tp.stp))
+	clientAuth := stsexchange.ClientAuthentication{
+		AuthStyle:    auth.StyleInHeader,
+		ClientID:     tp.opts.ClientID,
+		ClientSecret: tp.opts.ClientSecret,
+	}
+	var options map[string]interface{}
+	// Do not pass workforce_pool_user_project when client authentication is used.
+	// The client ID is sufficient for determining the user project.
+	if tp.opts.WorkforcePoolUserProject != "" && tp.opts.ClientID == "" {
+		options = map[string]interface{}{
+			"userProject": tp.opts.WorkforcePoolUserProject,
+		}
+	}
+	stsResp, err := stsexchange.ExchangeToken(ctx, &stsexchange.Options{
+		Client:         tp.client,
+		Endpoint:       tp.opts.TokenURL,
+		Request:        stsRequest,
+		Authentication: clientAuth,
+		Headers:        header,
+		ExtraOpts:      options,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	tok := &auth.Token{
+		Value: stsResp.AccessToken,
+		Type:  stsResp.TokenType,
+	}
+	// The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
+	if stsResp.ExpiresIn <= 0 {
+		return nil, fmt.Errorf("credentials: got invalid expiry from security token service")
+	}
+	tok.Expiry = Now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
+	return tok, nil
+}
+
+// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
+// subjectTokenProvider
+func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
+	reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
+	if o.AwsSecurityCredentialsProvider != nil {
+		return &awsSubjectProvider{
+			securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
+			TargetResource:              o.Audience,
+			reqOpts:                     reqOpts,
+		}, nil
+	} else if o.SubjectTokenProvider != nil {
+		return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
+	} else if len(o.CredentialSource.EnvironmentID) > 3 && o.CredentialSource.EnvironmentID[:3] == "aws" {
+		if awsVersion, err := strconv.Atoi(o.CredentialSource.EnvironmentID[3:]); err == nil {
+			if awsVersion != 1 {
+				return nil, fmt.Errorf("credentials: aws version '%d' is not supported in the current build", awsVersion)
+			}
+
+			awsProvider := &awsSubjectProvider{
+				EnvironmentID:               o.CredentialSource.EnvironmentID,
+				RegionURL:                   o.CredentialSource.RegionURL,
+				RegionalCredVerificationURL: o.CredentialSource.RegionalCredVerificationURL,
+				CredVerificationURL:         o.CredentialSource.URL,
+				TargetResource:              o.Audience,
+				Client:                      o.Client,
+			}
+			if o.CredentialSource.IMDSv2SessionTokenURL != "" {
+				awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
+			}
+
+			return awsProvider, nil
+		}
+	} else if o.CredentialSource.File != "" {
+		return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
+	} else if o.CredentialSource.URL != "" {
+		return &urlSubjectProvider{URL: o.CredentialSource.URL, Headers: o.CredentialSource.Headers, Format: o.CredentialSource.Format, Client: o.Client}, nil
+	} else if o.CredentialSource.Executable != nil {
+		ec := o.CredentialSource.Executable
+		if ec.Command == "" {
+			return nil, errors.New("credentials: missing `command` field — executable command must be provided")
+		}
+
+		execProvider := &executableSubjectProvider{}
+		execProvider.Command = ec.Command
+		if ec.TimeoutMillis == 0 {
+			execProvider.Timeout = executableDefaultTimeout
+		} else {
+			execProvider.Timeout = time.Duration(ec.TimeoutMillis) * time.Millisecond
+			if execProvider.Timeout < timeoutMinimum || execProvider.Timeout > timeoutMaximum {
+				return nil, fmt.Errorf("credentials: invalid `timeout_millis` field — executable timeout must be between %v and %v seconds", timeoutMinimum.Seconds(), timeoutMaximum.Seconds())
+			}
+		}
+		execProvider.OutputFile = ec.OutputFile
+		execProvider.client = o.Client
+		execProvider.opts = o
+		execProvider.env = runtimeEnvironment{}
+		return execProvider, nil
+	} else if o.CredentialSource.Certificate != nil {
+		cert := o.CredentialSource.Certificate
+		if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
+			return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
+		}
+		if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
+			return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
+		}
+		return &x509Provider{}, nil
+	}
+	return nil, errors.New("credentials: unable to parse credential source")
+}
+
+func getGoogHeaderValue(conf *Options, p subjectTokenProvider) string {
+	return fmt.Sprintf("gl-go/%s auth/%s google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t",
+		goVersion(),
+		"unknown",
+		p.providerType(),
+		conf.ServiceAccountImpersonationURL != "",
+		conf.ServiceAccountImpersonationLifetimeSeconds != 0)
+}

+ 78 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go

@@ -0,0 +1,78 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"os"
+
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+)
+
+const (
+	fileProviderType = "file"
+)
+
+type fileSubjectProvider struct {
+	File   string
+	Format *credsfile.Format
+}
+
+func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
+	tokenFile, err := os.Open(sp.File)
+	if err != nil {
+		return "", fmt.Errorf("credentials: failed to open credential file %q: %w", sp.File, err)
+	}
+	defer tokenFile.Close()
+	tokenBytes, err := internal.ReadAll(tokenFile)
+	if err != nil {
+		return "", fmt.Errorf("credentials: failed to read credential file: %w", err)
+	}
+	tokenBytes = bytes.TrimSpace(tokenBytes)
+
+	if sp.Format == nil {
+		return string(tokenBytes), nil
+	}
+	switch sp.Format.Type {
+	case fileTypeJSON:
+		jsonData := make(map[string]interface{})
+		err = json.Unmarshal(tokenBytes, &jsonData)
+		if err != nil {
+			return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
+		}
+		val, ok := jsonData[sp.Format.SubjectTokenFieldName]
+		if !ok {
+			return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
+		}
+		token, ok := val.(string)
+		if !ok {
+			return "", errors.New("credentials: improperly formatted subject token")
+		}
+		return token, nil
+	case fileTypeText:
+		return string(tokenBytes), nil
+	default:
+		return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
+	}
+}
+
+func (sp *fileSubjectProvider) providerType() string {
+	return fileProviderType
+}

+ 74 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go

@@ -0,0 +1,74 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"runtime"
+	"strings"
+	"unicode"
+)
+
+var (
+	// version is a package internal global variable for testing purposes.
+	version = runtime.Version
+)
+
+// versionUnknown is only used when the runtime version cannot be determined.
+const versionUnknown = "UNKNOWN"
+
+// goVersion returns a Go runtime version derived from the runtime environment
+// that is modified to be suitable for reporting in a header, meaning it has no
+// whitespace. If it is unable to determine the Go runtime version, it returns
+// versionUnknown.
+func goVersion() string {
+	const develPrefix = "devel +"
+
+	s := version()
+	if strings.HasPrefix(s, develPrefix) {
+		s = s[len(develPrefix):]
+		if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
+			s = s[:p]
+		}
+		return s
+	} else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
+		s = s[:p]
+	}
+
+	notSemverRune := func(r rune) bool {
+		return !strings.ContainsRune("0123456789.", r)
+	}
+
+	if strings.HasPrefix(s, "go1") {
+		s = s[2:]
+		var prerelease string
+		if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
+			s, prerelease = s[:p], s[p:]
+		}
+		if strings.HasSuffix(s, ".") {
+			s += "0"
+		} else if strings.Count(s, ".") < 2 {
+			s += ".0"
+		}
+		if prerelease != "" {
+			// Some release candidates already have a dash in them.
+			if !strings.HasPrefix(prerelease, "-") {
+				prerelease = "-" + prerelease
+			}
+			s += prerelease
+		}
+		return s
+	}
+	return versionUnknown
+}

+ 30 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go

@@ -0,0 +1,30 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import "context"
+
+type programmaticProvider struct {
+	opts *RequestOptions
+	stp  SubjectTokenProvider
+}
+
+func (pp *programmaticProvider) providerType() string {
+	return programmaticProviderType
+}
+
+func (pp *programmaticProvider) subjectToken(ctx context.Context) (string, error) {
+	return pp.stp.SubjectToken(ctx, pp.opts)
+}

+ 88 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go

@@ -0,0 +1,88 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+)
+
+const (
+	fileTypeText             = "text"
+	fileTypeJSON             = "json"
+	urlProviderType          = "url"
+	programmaticProviderType = "programmatic"
+	x509ProviderType         = "x509"
+)
+
+type urlSubjectProvider struct {
+	URL     string
+	Headers map[string]string
+	Format  *credsfile.Format
+	Client  *http.Client
+}
+
+func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+	req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
+	if err != nil {
+		return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
+	}
+
+	for key, val := range sp.Headers {
+		req.Header.Add(key, val)
+	}
+	resp, body, err := internal.DoRequest(sp.Client, req)
+	if err != nil {
+		return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
+	}
+	if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
+		return "", fmt.Errorf("credentials: status code %d: %s", c, body)
+	}
+
+	if sp.Format == nil {
+		return string(body), nil
+	}
+	switch sp.Format.Type {
+	case "json":
+		jsonData := make(map[string]interface{})
+		err = json.Unmarshal(body, &jsonData)
+		if err != nil {
+			return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
+		}
+		val, ok := jsonData[sp.Format.SubjectTokenFieldName]
+		if !ok {
+			return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
+		}
+		token, ok := val.(string)
+		if !ok {
+			return "", errors.New("credentials: improperly formatted subject token")
+		}
+		return token, nil
+	case fileTypeText:
+		return string(body), nil
+	default:
+		return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
+	}
+}
+
+func (sp *urlSubjectProvider) providerType() string {
+	return urlProviderType
+}

+ 63 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go

@@ -0,0 +1,63 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccount
+
+import (
+	"context"
+	"crypto/tls"
+	"net/http"
+	"time"
+
+	"cloud.google.com/go/auth/internal/transport/cert"
+)
+
+// x509Provider implements the subjectTokenProvider type for
+// x509 workload identity credentials. Because x509 credentials
+// rely on an mTLS connection to represent the 3rd party identity
+// rather than a subject token, this provider will always return
+// an empty string when a subject token is requested by the external account
+// token provider.
+type x509Provider struct {
+}
+
+func (xp *x509Provider) providerType() string {
+	return x509ProviderType
+}
+
+func (xp *x509Provider) subjectToken(ctx context.Context) (string, error) {
+	return "", nil
+}
+
+// createX509Client creates a new client that is configured with mTLS, using the
+// certificate configuration specified in the credential source.
+func createX509Client(certificateConfigLocation string) (*http.Client, error) {
+	certProvider, err := cert.NewWorkloadX509CertProvider(certificateConfigLocation)
+	if err != nil {
+		return nil, err
+	}
+	trans := http.DefaultTransport.(*http.Transport).Clone()
+
+	trans.TLSClientConfig = &tls.Config{
+		GetClientCertificate: certProvider,
+	}
+
+	// Create a client with default settings plus the X509 workload cert and key.
+	client := &http.Client{
+		Transport: trans,
+		Timeout:   30 * time.Second,
+	}
+
+	return client, nil
+}

+ 110 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go

@@ -0,0 +1,110 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package externalaccountuser
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/credentials/internal/stsexchange"
+	"cloud.google.com/go/auth/internal"
+)
+
+// Options stores the configuration for fetching tokens with external authorized
+// user credentials.
+type Options struct {
+	// Audience is the Secure Token Service (STS) audience which contains the
+	// resource name for the workforce pool and the provider identifier in that
+	// pool.
+	Audience string
+	// RefreshToken is the OAuth 2.0 refresh token.
+	RefreshToken string
+	// TokenURL is the STS token exchange endpoint for refresh.
+	TokenURL string
+	// TokenInfoURL is the STS endpoint URL for token introspection. Optional.
+	TokenInfoURL string
+	// ClientID is only required in conjunction with ClientSecret, as described
+	// below.
+	ClientID string
+	// ClientSecret is currently only required if token_info endpoint also needs
+	// to be called with the generated a cloud access token. When provided, STS
+	// will be called with additional basic authentication using client_id as
+	// username and client_secret as password.
+	ClientSecret string
+	// Scopes contains the desired scopes for the returned access token.
+	Scopes []string
+
+	// Client for token request.
+	Client *http.Client
+}
+
+func (c *Options) validate() bool {
+	return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != ""
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
+// configured with the provided options.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+	if !opts.validate() {
+		return nil, errors.New("credentials: invalid external_account_authorized_user configuration")
+	}
+
+	tp := &tokenProvider{
+		o: opts,
+	}
+	return auth.NewCachedTokenProvider(tp, nil), nil
+}
+
+type tokenProvider struct {
+	o *Options
+}
+
+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
+	opts := tp.o
+
+	clientAuth := stsexchange.ClientAuthentication{
+		AuthStyle:    auth.StyleInHeader,
+		ClientID:     opts.ClientID,
+		ClientSecret: opts.ClientSecret,
+	}
+	headers := make(http.Header)
+	headers.Set("Content-Type", "application/x-www-form-urlencoded")
+	stsResponse, err := stsexchange.RefreshAccessToken(ctx, &stsexchange.Options{
+		Client:         opts.Client,
+		Endpoint:       opts.TokenURL,
+		RefreshToken:   opts.RefreshToken,
+		Authentication: clientAuth,
+		Headers:        headers,
+	})
+	if err != nil {
+		return nil, err
+	}
+	if stsResponse.ExpiresIn < 0 {
+		return nil, errors.New("credentials: invalid expiry from security token service")
+	}
+
+	// guarded by the wrapping with CachedTokenProvider
+	if stsResponse.RefreshToken != "" {
+		opts.RefreshToken = stsResponse.RefreshToken
+	}
+	return &auth.Token{
+		Value:  stsResponse.AccessToken,
+		Expiry: time.Now().UTC().Add(time.Duration(stsResponse.ExpiresIn) * time.Second),
+		Type:   internal.TokenTypeBearer,
+	}, nil
+}

+ 184 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go

@@ -0,0 +1,184 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gdch
+
+import (
+	"context"
+	"crypto/rsa"
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/url"
+	"os"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+	"cloud.google.com/go/auth/internal/jwt"
+)
+
+const (
+	// GrantType is the grant type for the token request.
+	GrantType        = "urn:ietf:params:oauth:token-type:token-exchange"
+	requestTokenType = "urn:ietf:params:oauth:token-type:access_token"
+	subjectTokenType = "urn:k8s:params:oauth:token-type:serviceaccount"
+)
+
+var (
+	gdchSupportFormatVersions map[string]bool = map[string]bool{
+		"1": true,
+	}
+)
+
+// Options for [NewTokenProvider].
+type Options struct {
+	STSAudience string
+	Client      *http.Client
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
+// GDCH cred file.
+func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.TokenProvider, error) {
+	if !gdchSupportFormatVersions[f.FormatVersion] {
+		return nil, fmt.Errorf("credentials: unsupported gdch_service_account format %q", f.FormatVersion)
+	}
+	if o.STSAudience == "" {
+		return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
+	}
+	pk, err := internal.ParseKey([]byte(f.PrivateKey))
+	if err != nil {
+		return nil, err
+	}
+	certPool, err := loadCertPool(f.CertPath)
+	if err != nil {
+		return nil, err
+	}
+
+	tp := gdchProvider{
+		serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
+		tokenURL:        f.TokenURL,
+		aud:             o.STSAudience,
+		pk:              pk,
+		pkID:            f.PrivateKeyID,
+		certPool:        certPool,
+		client:          o.Client,
+	}
+	return tp, nil
+}
+
+func loadCertPool(path string) (*x509.CertPool, error) {
+	pool := x509.NewCertPool()
+	pem, err := os.ReadFile(path)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: failed to read certificate: %w", err)
+	}
+	pool.AppendCertsFromPEM(pem)
+	return pool, nil
+}
+
+type gdchProvider struct {
+	serviceIdentity string
+	tokenURL        string
+	aud             string
+	pk              *rsa.PrivateKey
+	pkID            string
+	certPool        *x509.CertPool
+
+	client *http.Client
+}
+
+func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
+	addCertToTransport(g.client, g.certPool)
+	iat := time.Now()
+	exp := iat.Add(time.Hour)
+	claims := jwt.Claims{
+		Iss: g.serviceIdentity,
+		Sub: g.serviceIdentity,
+		Aud: g.tokenURL,
+		Iat: iat.Unix(),
+		Exp: exp.Unix(),
+	}
+	h := jwt.Header{
+		Algorithm: jwt.HeaderAlgRSA256,
+		Type:      jwt.HeaderType,
+		KeyID:     string(g.pkID),
+	}
+	payload, err := jwt.EncodeJWS(&h, &claims, g.pk)
+	if err != nil {
+		return nil, err
+	}
+	v := url.Values{}
+	v.Set("grant_type", GrantType)
+	v.Set("audience", g.aud)
+	v.Set("requested_token_type", requestTokenType)
+	v.Set("subject_token", payload)
+	v.Set("subject_token_type", subjectTokenType)
+
+	req, err := http.NewRequestWithContext(ctx, "POST", g.tokenURL, strings.NewReader(v.Encode()))
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	resp, body, err := internal.DoRequest(g.client, req)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+	}
+	if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
+		return nil, &auth.Error{
+			Response: resp,
+			Body:     body,
+		}
+	}
+
+	var tokenRes struct {
+		AccessToken string `json:"access_token"`
+		TokenType   string `json:"token_type"`
+		ExpiresIn   int64  `json:"expires_in"` // relative seconds from now
+	}
+	if err := json.Unmarshal(body, &tokenRes); err != nil {
+		return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+	}
+	token := &auth.Token{
+		Value: tokenRes.AccessToken,
+		Type:  tokenRes.TokenType,
+	}
+	raw := make(map[string]interface{})
+	json.Unmarshal(body, &raw) // no error checks for optional fields
+	token.Metadata = raw
+
+	if secs := tokenRes.ExpiresIn; secs > 0 {
+		token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
+	}
+	return token, nil
+}
+
+// addCertToTransport makes a best effort attempt at adding in the cert info to
+// the client. It tries to keep all configured transport settings if the
+// underlying transport is an http.Transport. Or else it overwrites the
+// transport with defaults adding in the certs.
+func addCertToTransport(hc *http.Client, certPool *x509.CertPool) {
+	trans, ok := hc.Transport.(*http.Transport)
+	if !ok {
+		trans = http.DefaultTransport.(*http.Transport).Clone()
+	}
+	trans.TLSClientConfig = &tls.Config{
+		RootCAs: certPool,
+	}
+}

+ 146 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go

@@ -0,0 +1,146 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package impersonate
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/internal"
+)
+
+const (
+	defaultTokenLifetime = "3600s"
+	authHeaderKey        = "Authorization"
+)
+
+// generateAccesstokenReq is used for service account impersonation
+type generateAccessTokenReq struct {
+	Delegates []string `json:"delegates,omitempty"`
+	Lifetime  string   `json:"lifetime,omitempty"`
+	Scope     []string `json:"scope,omitempty"`
+}
+
+type impersonateTokenResponse struct {
+	AccessToken string `json:"accessToken"`
+	ExpireTime  string `json:"expireTime"`
+}
+
+// NewTokenProvider uses a source credential, stored in Ts, to request an access token to the provided URL.
+// Scopes can be defined when the access token is requested.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+	return opts, nil
+}
+
+// Options for [NewTokenProvider].
+type Options struct {
+	// Tp is the source credential used to generate a token on the
+	// impersonated service account. Required.
+	Tp auth.TokenProvider
+
+	// URL is the endpoint to call to generate a token
+	// on behalf of the service account. Required.
+	URL string
+	// Scopes that the impersonated credential should have. Required.
+	Scopes []string
+	// Delegates are the service account email addresses in a delegation chain.
+	// Each service account must be granted roles/iam.serviceAccountTokenCreator
+	// on the next service account in the chain. Optional.
+	Delegates []string
+	// TokenLifetimeSeconds is the number of seconds the impersonation token will
+	// be valid for. Defaults to 1 hour if unset. Optional.
+	TokenLifetimeSeconds int
+	// Client configures the underlying client used to make network requests
+	// when fetching tokens. Required.
+	Client *http.Client
+}
+
+func (o *Options) validate() error {
+	if o.Tp == nil {
+		return errors.New("credentials: missing required 'source_credentials' field in impersonated credentials")
+	}
+	if o.URL == "" {
+		return errors.New("credentials: missing required 'service_account_impersonation_url' field in impersonated credentials")
+	}
+	return nil
+}
+
+// Token performs the exchange to get a temporary service account token to allow access to GCP.
+func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
+	lifetime := defaultTokenLifetime
+	if o.TokenLifetimeSeconds != 0 {
+		lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
+	}
+	reqBody := generateAccessTokenReq{
+		Lifetime:  lifetime,
+		Scope:     o.Scopes,
+		Delegates: o.Delegates,
+	}
+	b, err := json.Marshal(reqBody)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: unable to marshal request: %w", err)
+	}
+	req, err := http.NewRequestWithContext(ctx, "POST", o.URL, bytes.NewReader(b))
+	if err != nil {
+		return nil, fmt.Errorf("credentials: unable to create impersonation request: %w", err)
+	}
+	req.Header.Set("Content-Type", "application/json")
+	if err := setAuthHeader(ctx, o.Tp, req); err != nil {
+		return nil, err
+	}
+	resp, body, err := internal.DoRequest(o.Client, req)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
+	}
+	if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
+		return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
+	}
+
+	var accessTokenResp impersonateTokenResponse
+	if err := json.Unmarshal(body, &accessTokenResp); err != nil {
+		return nil, fmt.Errorf("credentials: unable to parse response: %w", err)
+	}
+	expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: unable to parse expiry: %w", err)
+	}
+	return &auth.Token{
+		Value:  accessTokenResp.AccessToken,
+		Expiry: expiry,
+		Type:   internal.TokenTypeBearer,
+	}, nil
+}
+
+func setAuthHeader(ctx context.Context, tp auth.TokenProvider, r *http.Request) error {
+	t, err := tp.Token(ctx)
+	if err != nil {
+		return err
+	}
+	typ := t.Type
+	if typ == "" {
+		typ = internal.TokenTypeBearer
+	}
+	r.Header.Set(authHeaderKey, typ+" "+t.Value)
+	return nil
+}

+ 161 - 0
backend/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go

@@ -0,0 +1,161 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package stsexchange
+
+import (
+	"context"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/internal"
+)
+
+const (
+	// GrantType for a sts exchange.
+	GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
+	// TokenType for a sts exchange.
+	TokenType = "urn:ietf:params:oauth:token-type:access_token"
+
+	jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
+)
+
+// Options stores the configuration for making an sts exchange request.
+type Options struct {
+	Client         *http.Client
+	Endpoint       string
+	Request        *TokenRequest
+	Authentication ClientAuthentication
+	Headers        http.Header
+	// ExtraOpts are optional fields marshalled into the `options` field of the
+	// request body.
+	ExtraOpts    map[string]interface{}
+	RefreshToken string
+}
+
+// RefreshAccessToken performs the token exchange using a refresh token flow.
+func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
+	data := url.Values{}
+	data.Set("grant_type", "refresh_token")
+	data.Set("refresh_token", opts.RefreshToken)
+	return doRequest(ctx, opts, data)
+}
+
+// ExchangeToken performs an oauth2 token exchange with the provided endpoint.
+func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
+	data := url.Values{}
+	data.Set("audience", opts.Request.Audience)
+	data.Set("grant_type", GrantType)
+	data.Set("requested_token_type", TokenType)
+	data.Set("subject_token_type", opts.Request.SubjectTokenType)
+	data.Set("subject_token", opts.Request.SubjectToken)
+	data.Set("scope", strings.Join(opts.Request.Scope, " "))
+	if opts.ExtraOpts != nil {
+		opts, err := json.Marshal(opts.ExtraOpts)
+		if err != nil {
+			return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
+		}
+		data.Set("options", string(opts))
+	}
+	return doRequest(ctx, opts, data)
+}
+
+func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
+	opts.Authentication.InjectAuthentication(data, opts.Headers)
+	encodedData := data.Encode()
+
+	req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
+	if err != nil {
+		return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
+
+	}
+	for key, list := range opts.Headers {
+		for _, val := range list {
+			req.Header.Add(key, val)
+		}
+	}
+	req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
+
+	resp, body, err := internal.DoRequest(opts.Client, req)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
+	}
+	if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
+		return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
+	}
+	var stsResp TokenResponse
+	if err := json.Unmarshal(body, &stsResp); err != nil {
+		return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
+	}
+
+	return &stsResp, nil
+}
+
+// TokenRequest contains fields necessary to make an oauth2 token
+// exchange.
+type TokenRequest struct {
+	ActingParty struct {
+		ActorToken     string
+		ActorTokenType string
+	}
+	GrantType          string
+	Resource           string
+	Audience           string
+	Scope              []string
+	RequestedTokenType string
+	SubjectToken       string
+	SubjectTokenType   string
+}
+
+// TokenResponse is used to decode the remote server response during
+// an oauth2 token exchange.
+type TokenResponse struct {
+	AccessToken     string `json:"access_token"`
+	IssuedTokenType string `json:"issued_token_type"`
+	TokenType       string `json:"token_type"`
+	ExpiresIn       int    `json:"expires_in"`
+	Scope           string `json:"scope"`
+	RefreshToken    string `json:"refresh_token"`
+}
+
+// ClientAuthentication represents an OAuth client ID and secret and the
+// mechanism for passing these credentials as stated in rfc6749#2.3.1.
+type ClientAuthentication struct {
+	AuthStyle    auth.Style
+	ClientID     string
+	ClientSecret string
+}
+
+// InjectAuthentication is used to add authentication to a Secure Token Service
+// exchange request.  It modifies either the passed url.Values or http.Header
+// depending on the desired authentication format.
+func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
+	if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
+		return
+	}
+	switch c.AuthStyle {
+	case auth.StyleInHeader:
+		plainHeader := c.ClientID + ":" + c.ClientSecret
+		headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
+	default:
+		values.Set("client_id", c.ClientID)
+		values.Set("client_secret", c.ClientSecret)
+	}
+}

+ 85 - 0
backend/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go

@@ -0,0 +1,85 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credentials
+
+import (
+	"context"
+	"crypto/rsa"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/credsfile"
+	"cloud.google.com/go/auth/internal/jwt"
+)
+
+var (
+	// for testing
+	now func() time.Time = time.Now
+)
+
+// configureSelfSignedJWT uses the private key in the service account to create
+// a JWT without making a network call.
+func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+	if len(opts.scopes()) == 0 && opts.Audience == "" {
+		return nil, errors.New("credentials: both scopes and audience are empty")
+	}
+	pk, err := internal.ParseKey([]byte(f.PrivateKey))
+	if err != nil {
+		return nil, fmt.Errorf("credentials: could not parse key: %w", err)
+	}
+	return &selfSignedTokenProvider{
+		email:    f.ClientEmail,
+		audience: opts.Audience,
+		scopes:   opts.scopes(),
+		pk:       pk,
+		pkID:     f.PrivateKeyID,
+	}, nil
+}
+
+type selfSignedTokenProvider struct {
+	email    string
+	audience string
+	scopes   []string
+	pk       *rsa.PrivateKey
+	pkID     string
+}
+
+func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
+	iat := now()
+	exp := iat.Add(time.Hour)
+	scope := strings.Join(tp.scopes, " ")
+	c := &jwt.Claims{
+		Iss:   tp.email,
+		Sub:   tp.email,
+		Aud:   tp.audience,
+		Scope: scope,
+		Iat:   iat.Unix(),
+		Exp:   exp.Unix(),
+	}
+	h := &jwt.Header{
+		Algorithm: jwt.HeaderAlgRSA256,
+		Type:      jwt.HeaderType,
+		KeyID:     string(tp.pkID),
+	}
+	msg, err := jwt.EncodeJWS(h, c, tp.pk)
+	if err != nil {
+		return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
+	}
+	return &auth.Token{Value: msg, Type: internal.TokenTypeBearer, Expiry: exp}, nil
+}

+ 232 - 0
backend/vendor/cloud.google.com/go/auth/httptransport/httptransport.go

@@ -0,0 +1,232 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package httptransport provides functionality for managing HTTP client
+// connections to Google Cloud services.
+package httptransport
+
+import (
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net/http"
+
+	"cloud.google.com/go/auth"
+	detect "cloud.google.com/go/auth/credentials"
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/transport"
+)
+
+// ClientCertProvider is a function that returns a TLS client certificate to be
+// used when opening TLS connections. It follows the same semantics as
+// [crypto/tls.Config.GetClientCertificate].
+type ClientCertProvider = func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
+
+// Options used to configure a [net/http.Client] from [NewClient].
+type Options struct {
+	// DisableTelemetry disables default telemetry (OpenTelemetry). An example
+	// reason to do so would be to bind custom telemetry that overrides the
+	// defaults.
+	DisableTelemetry bool
+	// DisableAuthentication specifies that no authentication should be used. It
+	// is suitable only for testing and for accessing public resources, like
+	// public Google Cloud Storage buckets.
+	DisableAuthentication bool
+	// Headers are extra HTTP headers that will be appended to every outgoing
+	// request.
+	Headers http.Header
+	// BaseRoundTripper overrides the base transport used for serving requests.
+	// If specified ClientCertProvider is ignored.
+	BaseRoundTripper http.RoundTripper
+	// Endpoint overrides the default endpoint to be used for a service.
+	Endpoint string
+	// APIKey specifies an API key to be used as the basis for authentication.
+	// If set DetectOpts are ignored.
+	APIKey string
+	// Credentials used to add Authorization header to all requests. If set
+	// DetectOpts are ignored.
+	Credentials *auth.Credentials
+	// ClientCertProvider is a function that returns a TLS client certificate to
+	// be used when opening TLS connections. It follows the same semantics as
+	// crypto/tls.Config.GetClientCertificate.
+	ClientCertProvider ClientCertProvider
+	// DetectOpts configures settings for detect Application Default
+	// Credentials.
+	DetectOpts *detect.DetectOptions
+	// UniverseDomain is the default service domain for a given Cloud universe.
+	// The default value is "googleapis.com". This is the universe domain
+	// configured for the client, which will be compared to the universe domain
+	// that is separately configured for the credentials.
+	UniverseDomain string
+
+	// InternalOptions are NOT meant to be set directly by consumers of this
+	// package, they should only be set by generated client code.
+	InternalOptions *InternalOptions
+}
+
+func (o *Options) validate() error {
+	if o == nil {
+		return errors.New("httptransport: opts required to be non-nil")
+	}
+	if o.InternalOptions != nil && o.InternalOptions.SkipValidation {
+		return nil
+	}
+	hasCreds := o.APIKey != "" ||
+		o.Credentials != nil ||
+		(o.DetectOpts != nil && len(o.DetectOpts.CredentialsJSON) > 0) ||
+		(o.DetectOpts != nil && o.DetectOpts.CredentialsFile != "")
+	if o.DisableAuthentication && hasCreds {
+		return errors.New("httptransport: DisableAuthentication is incompatible with options that set or detect credentials")
+	}
+	return nil
+}
+
+// client returns the client a user set for the detect options or nil if one was
+// not set.
+func (o *Options) client() *http.Client {
+	if o.DetectOpts != nil && o.DetectOpts.Client != nil {
+		return o.DetectOpts.Client
+	}
+	return nil
+}
+
+func (o *Options) resolveDetectOptions() *detect.DetectOptions {
+	io := o.InternalOptions
+	// soft-clone these so we are not updating a ref the user holds and may reuse
+	do := transport.CloneDetectOptions(o.DetectOpts)
+
+	// If scoped JWTs are enabled user provided an aud, allow self-signed JWT.
+	if (io != nil && io.EnableJWTWithScope) || do.Audience != "" {
+		do.UseSelfSignedJWT = true
+	}
+	// Only default scopes if user did not also set an audience.
+	if len(do.Scopes) == 0 && do.Audience == "" && io != nil && len(io.DefaultScopes) > 0 {
+		do.Scopes = make([]string, len(io.DefaultScopes))
+		copy(do.Scopes, io.DefaultScopes)
+	}
+	if len(do.Scopes) == 0 && do.Audience == "" && io != nil {
+		do.Audience = o.InternalOptions.DefaultAudience
+	}
+	if o.ClientCertProvider != nil {
+		tlsConfig := &tls.Config{
+			GetClientCertificate: o.ClientCertProvider,
+		}
+		do.Client = transport.DefaultHTTPClientWithTLS(tlsConfig)
+		do.TokenURL = detect.GoogleMTLSTokenURL
+	}
+	return do
+}
+
+// InternalOptions are only meant to be set by generated client code. These are
+// not meant to be set directly by consumers of this package. Configuration in
+// this type is considered EXPERIMENTAL and may be removed at any time in the
+// future without warning.
+type InternalOptions struct {
+	// EnableJWTWithScope specifies if scope can be used with self-signed JWT.
+	EnableJWTWithScope bool
+	// DefaultAudience specifies a default audience to be used as the audience
+	// field ("aud") for the JWT token authentication.
+	DefaultAudience string
+	// DefaultEndpointTemplate combined with UniverseDomain specifies the
+	// default endpoint.
+	DefaultEndpointTemplate string
+	// DefaultMTLSEndpoint specifies the default mTLS endpoint.
+	DefaultMTLSEndpoint string
+	// DefaultScopes specifies the default OAuth2 scopes to be used for a
+	// service.
+	DefaultScopes []string
+	// SkipValidation bypasses validation on Options. It should only be used
+	// internally for clients that need more control over their transport.
+	SkipValidation bool
+	// SkipUniverseDomainValidation skips the verification that the universe
+	// domain configured for the client matches the universe domain configured
+	// for the credentials. It should only be used internally for clients that
+	// need more control over their transport. The default is false.
+	SkipUniverseDomainValidation bool
+}
+
+// AddAuthorizationMiddleware adds a middleware to the provided client's
+// transport that sets the Authorization header with the value produced by the
+// provided [cloud.google.com/go/auth.Credentials]. An error is returned only
+// if client or creds is nil.
+//
+// This function does not support setting a universe domain value on the client.
+func AddAuthorizationMiddleware(client *http.Client, creds *auth.Credentials) error {
+	if client == nil || creds == nil {
+		return fmt.Errorf("httptransport: client and tp must not be nil")
+	}
+	base := client.Transport
+	if base == nil {
+		if dt, ok := http.DefaultTransport.(*http.Transport); ok {
+			base = dt.Clone()
+		} else {
+			// Directly reuse the DefaultTransport if the application has
+			// replaced it with an implementation of RoundTripper other than
+			// http.Transport.
+			base = http.DefaultTransport
+		}
+	}
+	client.Transport = &authTransport{
+		creds: creds,
+		base:  base,
+	}
+	return nil
+}
+
+// NewClient returns a [net/http.Client] that can be used to communicate with a
+// Google cloud service, configured with the provided [Options]. It
+// automatically appends Authorization headers to all outgoing requests.
+func NewClient(opts *Options) (*http.Client, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+
+	tOpts := &transport.Options{
+		Endpoint:           opts.Endpoint,
+		ClientCertProvider: opts.ClientCertProvider,
+		Client:             opts.client(),
+		UniverseDomain:     opts.UniverseDomain,
+	}
+	if io := opts.InternalOptions; io != nil {
+		tOpts.DefaultEndpointTemplate = io.DefaultEndpointTemplate
+		tOpts.DefaultMTLSEndpoint = io.DefaultMTLSEndpoint
+	}
+	clientCertProvider, dialTLSContext, err := transport.GetHTTPTransportConfig(tOpts)
+	if err != nil {
+		return nil, err
+	}
+	baseRoundTripper := opts.BaseRoundTripper
+	if baseRoundTripper == nil {
+		baseRoundTripper = defaultBaseTransport(clientCertProvider, dialTLSContext)
+	}
+	// Ensure the token exchange transport uses the same ClientCertProvider as the API transport.
+	opts.ClientCertProvider = clientCertProvider
+	trans, err := newTransport(baseRoundTripper, opts)
+	if err != nil {
+		return nil, err
+	}
+	return &http.Client{
+		Transport: trans,
+	}, nil
+}
+
+// SetAuthHeader uses the provided token to set the Authorization header on a
+// request. If the token.Type is empty, the type is assumed to be Bearer.
+func SetAuthHeader(token *auth.Token, req *http.Request) {
+	typ := token.Type
+	if typ == "" {
+		typ = internal.TokenTypeBearer
+	}
+	req.Header.Set("Authorization", typ+" "+token.Value)
+}

+ 93 - 0
backend/vendor/cloud.google.com/go/auth/httptransport/trace.go

@@ -0,0 +1,93 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package httptransport
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"go.opencensus.io/trace"
+	"go.opencensus.io/trace/propagation"
+)
+
+const (
+	httpHeaderMaxSize = 200
+	cloudTraceHeader  = `X-Cloud-Trace-Context`
+)
+
+// asserts the httpFormat fulfills this foreign interface
+var _ propagation.HTTPFormat = (*httpFormat)(nil)
+
+// httpFormat implements propagation.httpFormat to propagate
+// traces in HTTP headers for Google Cloud Platform and Cloud Trace.
+type httpFormat struct{}
+
+// SpanContextFromRequest extracts a Cloud Trace span context from incoming requests.
+func (f *httpFormat) SpanContextFromRequest(req *http.Request) (sc trace.SpanContext, ok bool) {
+	h := req.Header.Get(cloudTraceHeader)
+	// See https://cloud.google.com/trace/docs/faq for the header HTTPFormat.
+	// Return if the header is empty or missing, or if the header is unreasonably
+	// large, to avoid making unnecessary copies of a large string.
+	if h == "" || len(h) > httpHeaderMaxSize {
+		return trace.SpanContext{}, false
+	}
+
+	// Parse the trace id field.
+	slash := strings.Index(h, `/`)
+	if slash == -1 {
+		return trace.SpanContext{}, false
+	}
+	tid, h := h[:slash], h[slash+1:]
+
+	buf, err := hex.DecodeString(tid)
+	if err != nil {
+		return trace.SpanContext{}, false
+	}
+	copy(sc.TraceID[:], buf)
+
+	// Parse the span id field.
+	spanstr := h
+	semicolon := strings.Index(h, `;`)
+	if semicolon != -1 {
+		spanstr, h = h[:semicolon], h[semicolon+1:]
+	}
+	sid, err := strconv.ParseUint(spanstr, 10, 64)
+	if err != nil {
+		return trace.SpanContext{}, false
+	}
+	binary.BigEndian.PutUint64(sc.SpanID[:], sid)
+
+	// Parse the options field, options field is optional.
+	if !strings.HasPrefix(h, "o=") {
+		return sc, true
+	}
+	o, err := strconv.ParseUint(h[2:], 10, 32)
+	if err != nil {
+		return trace.SpanContext{}, false
+	}
+	sc.TraceOptions = trace.TraceOptions(o)
+	return sc, true
+}
+
+// SpanContextToRequest modifies the given request to include a Cloud Trace header.
+func (f *httpFormat) SpanContextToRequest(sc trace.SpanContext, req *http.Request) {
+	sid := binary.BigEndian.Uint64(sc.SpanID[:])
+	header := fmt.Sprintf("%s/%d;o=%d", hex.EncodeToString(sc.TraceID[:]), sid, int64(sc.TraceOptions))
+	req.Header.Set(cloudTraceHeader, header)
+}

+ 248 - 0
backend/vendor/cloud.google.com/go/auth/httptransport/transport.go

@@ -0,0 +1,248 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package httptransport
+
+import (
+	"context"
+	"crypto/tls"
+	"net"
+	"net/http"
+	"os"
+	"time"
+
+	"cloud.google.com/go/auth"
+	"cloud.google.com/go/auth/credentials"
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/transport"
+	"cloud.google.com/go/auth/internal/transport/cert"
+	"go.opencensus.io/plugin/ochttp"
+	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
+	"golang.org/x/net/http2"
+)
+
+const (
+	quotaProjectHeaderKey = "X-goog-user-project"
+)
+
+func newTransport(base http.RoundTripper, opts *Options) (http.RoundTripper, error) {
+	var headers = opts.Headers
+	ht := &headerTransport{
+		base:    base,
+		headers: headers,
+	}
+	var trans http.RoundTripper = ht
+	// Give OpenTelemetry precedence over OpenCensus in case user configuration
+	// causes both to write the same header (`X-Cloud-Trace-Context`).
+	trans = addOpenTelemetryTransport(trans, opts)
+	trans = addOCTransport(trans, opts)
+	switch {
+	case opts.DisableAuthentication:
+		// Do nothing.
+	case opts.APIKey != "":
+		qp := internal.GetQuotaProject(nil, opts.Headers.Get(quotaProjectHeaderKey))
+		if qp != "" {
+			if headers == nil {
+				headers = make(map[string][]string, 1)
+			}
+			headers.Set(quotaProjectHeaderKey, qp)
+		}
+		trans = &apiKeyTransport{
+			Transport: trans,
+			Key:       opts.APIKey,
+		}
+	default:
+		var creds *auth.Credentials
+		if opts.Credentials != nil {
+			creds = opts.Credentials
+		} else {
+			var err error
+			creds, err = credentials.DetectDefault(opts.resolveDetectOptions())
+			if err != nil {
+				return nil, err
+			}
+		}
+		qp, err := creds.QuotaProjectID(context.Background())
+		if err != nil {
+			return nil, err
+		}
+		if qp != "" {
+			if headers == nil {
+				headers = make(map[string][]string, 1)
+			}
+			// Don't overwrite user specified quota
+			if v := headers.Get(quotaProjectHeaderKey); v == "" {
+				headers.Set(quotaProjectHeaderKey, qp)
+			}
+		}
+		var skipUD bool
+		if iOpts := opts.InternalOptions; iOpts != nil {
+			skipUD = iOpts.SkipUniverseDomainValidation
+		}
+		creds.TokenProvider = auth.NewCachedTokenProvider(creds.TokenProvider, nil)
+		trans = &authTransport{
+			base:                         trans,
+			creds:                        creds,
+			clientUniverseDomain:         opts.UniverseDomain,
+			skipUniverseDomainValidation: skipUD,
+		}
+	}
+	return trans, nil
+}
+
+// defaultBaseTransport returns the base HTTP transport.
+// On App Engine, this is urlfetch.Transport.
+// Otherwise, use a default transport, taking most defaults from
+// http.DefaultTransport.
+// If TLSCertificate is available, set TLSClientConfig as well.
+func defaultBaseTransport(clientCertSource cert.Provider, dialTLSContext func(context.Context, string, string) (net.Conn, error)) http.RoundTripper {
+	defaultTransport, ok := http.DefaultTransport.(*http.Transport)
+	if !ok {
+		defaultTransport = transport.BaseTransport()
+	}
+	trans := defaultTransport.Clone()
+	trans.MaxIdleConnsPerHost = 100
+
+	if clientCertSource != nil {
+		trans.TLSClientConfig = &tls.Config{
+			GetClientCertificate: clientCertSource,
+		}
+	}
+	if dialTLSContext != nil {
+		// If DialTLSContext is set, TLSClientConfig wil be ignored
+		trans.DialTLSContext = dialTLSContext
+	}
+
+	// Configures the ReadIdleTimeout HTTP/2 option for the
+	// transport. This allows broken idle connections to be pruned more quickly,
+	// preventing the client from attempting to re-use connections that will no
+	// longer work.
+	http2Trans, err := http2.ConfigureTransports(trans)
+	if err == nil {
+		http2Trans.ReadIdleTimeout = time.Second * 31
+	}
+
+	return trans
+}
+
+type apiKeyTransport struct {
+	// Key is the API Key to set on requests.
+	Key string
+	// Transport is the underlying HTTP transport.
+	// If nil, http.DefaultTransport is used.
+	Transport http.RoundTripper
+}
+
+func (t *apiKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	newReq := *req
+	args := newReq.URL.Query()
+	args.Set("key", t.Key)
+	newReq.URL.RawQuery = args.Encode()
+	return t.Transport.RoundTrip(&newReq)
+}
+
+type headerTransport struct {
+	headers http.Header
+	base    http.RoundTripper
+}
+
+func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	rt := t.base
+	newReq := *req
+	newReq.Header = make(http.Header)
+	for k, vv := range req.Header {
+		newReq.Header[k] = vv
+	}
+
+	for k, v := range t.headers {
+		newReq.Header[k] = v
+	}
+
+	return rt.RoundTrip(&newReq)
+}
+
+func addOpenTelemetryTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
+	if opts.DisableTelemetry {
+		return trans
+	}
+	return otelhttp.NewTransport(trans)
+}
+
+func addOCTransport(trans http.RoundTripper, opts *Options) http.RoundTripper {
+	if opts.DisableTelemetry {
+		return trans
+	}
+	return &ochttp.Transport{
+		Base:        trans,
+		Propagation: &httpFormat{},
+	}
+}
+
+type authTransport struct {
+	creds                        *auth.Credentials
+	base                         http.RoundTripper
+	clientUniverseDomain         string
+	skipUniverseDomainValidation bool
+}
+
+// getClientUniverseDomain returns the default service domain for a given Cloud
+// universe, with the following precedence:
+//
+// 1. A non-empty option.WithUniverseDomain or similar client option.
+// 2. A non-empty environment variable GOOGLE_CLOUD_UNIVERSE_DOMAIN.
+// 3. The default value "googleapis.com".
+//
+// This is the universe domain configured for the client, which will be compared
+// to the universe domain that is separately configured for the credentials.
+func (t *authTransport) getClientUniverseDomain() string {
+	if t.clientUniverseDomain != "" {
+		return t.clientUniverseDomain
+	}
+	if envUD := os.Getenv(internal.UniverseDomainEnvVar); envUD != "" {
+		return envUD
+	}
+	return internal.DefaultUniverseDomain
+}
+
+// RoundTrip authorizes and authenticates the request with an
+// access token from Transport's Source. Per the RoundTripper contract we must
+// not modify the initial request, so we clone it, and we must close the body
+// on any errors that happens during our token logic.
+func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+	reqBodyClosed := false
+	if req.Body != nil {
+		defer func() {
+			if !reqBodyClosed {
+				req.Body.Close()
+			}
+		}()
+	}
+	token, err := t.creds.Token(req.Context())
+	if err != nil {
+		return nil, err
+	}
+	if !t.skipUniverseDomainValidation && token.MetadataString("auth.google.tokenSource") != "compute-metadata" {
+		credentialsUniverseDomain, err := t.creds.UniverseDomain(req.Context())
+		if err != nil {
+			return nil, err
+		}
+		if err := transport.ValidateUniverseDomain(t.getClientUniverseDomain(), credentialsUniverseDomain); err != nil {
+			return nil, err
+		}
+	}
+	req2 := req.Clone(req.Context())
+	SetAuthHeader(token, req2)
+	reqBodyClosed = true
+	return t.base.RoundTrip(req2)
+}

+ 107 - 0
backend/vendor/cloud.google.com/go/auth/internal/credsfile/credsfile.go

@@ -0,0 +1,107 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package credsfile is meant to hide implementation details from the pubic
+// surface of the detect package. It should not import any other packages in
+// this module. It is located under the main internal package so other
+// sub-packages can use these parsed types as well.
+package credsfile
+
+import (
+	"os"
+	"os/user"
+	"path/filepath"
+	"runtime"
+)
+
+const (
+	// GoogleAppCredsEnvVar is the environment variable for setting the
+	// application default credentials.
+	GoogleAppCredsEnvVar = "GOOGLE_APPLICATION_CREDENTIALS"
+	userCredsFilename    = "application_default_credentials.json"
+)
+
+// CredentialType represents different credential filetypes Google credentials
+// can be.
+type CredentialType int
+
+const (
+	// UnknownCredType is an unidentified file type.
+	UnknownCredType CredentialType = iota
+	// UserCredentialsKey represents a user creds file type.
+	UserCredentialsKey
+	// ServiceAccountKey represents a service account file type.
+	ServiceAccountKey
+	// ImpersonatedServiceAccountKey represents a impersonated service account
+	// file type.
+	ImpersonatedServiceAccountKey
+	// ExternalAccountKey represents a external account file type.
+	ExternalAccountKey
+	// GDCHServiceAccountKey represents a GDCH file type.
+	GDCHServiceAccountKey
+	// ExternalAccountAuthorizedUserKey represents a external account authorized
+	// user file type.
+	ExternalAccountAuthorizedUserKey
+)
+
+// parseCredentialType returns the associated filetype based on the parsed
+// typeString provided.
+func parseCredentialType(typeString string) CredentialType {
+	switch typeString {
+	case "service_account":
+		return ServiceAccountKey
+	case "authorized_user":
+		return UserCredentialsKey
+	case "impersonated_service_account":
+		return ImpersonatedServiceAccountKey
+	case "external_account":
+		return ExternalAccountKey
+	case "external_account_authorized_user":
+		return ExternalAccountAuthorizedUserKey
+	case "gdch_service_account":
+		return GDCHServiceAccountKey
+	default:
+		return UnknownCredType
+	}
+}
+
+// GetFileNameFromEnv returns the override if provided or detects a filename
+// from the environment.
+func GetFileNameFromEnv(override string) string {
+	if override != "" {
+		return override
+	}
+	return os.Getenv(GoogleAppCredsEnvVar)
+}
+
+// GetWellKnownFileName tries to locate the filepath for the user credential
+// file based on the environment.
+func GetWellKnownFileName() string {
+	if runtime.GOOS == "windows" {
+		return filepath.Join(os.Getenv("APPDATA"), "gcloud", userCredsFilename)
+	}
+	return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", userCredsFilename)
+}
+
+// guessUnixHomeDir default to checking for HOME, but not all unix systems have
+// this set, do have a fallback.
+func guessUnixHomeDir() string {
+	if v := os.Getenv("HOME"); v != "" {
+		return v
+	}
+	if u, err := user.Current(); err == nil {
+		return u.HomeDir
+	}
+	return ""
+}

+ 157 - 0
backend/vendor/cloud.google.com/go/auth/internal/credsfile/filetype.go

@@ -0,0 +1,157 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credsfile
+
+import (
+	"encoding/json"
+)
+
+// Config3LO is the internals of a client creds file.
+type Config3LO struct {
+	ClientID     string   `json:"client_id"`
+	ClientSecret string   `json:"client_secret"`
+	RedirectURIs []string `json:"redirect_uris"`
+	AuthURI      string   `json:"auth_uri"`
+	TokenURI     string   `json:"token_uri"`
+}
+
+// ClientCredentialsFile representation.
+type ClientCredentialsFile struct {
+	Web            *Config3LO `json:"web"`
+	Installed      *Config3LO `json:"installed"`
+	UniverseDomain string     `json:"universe_domain"`
+}
+
+// ServiceAccountFile representation.
+type ServiceAccountFile struct {
+	Type           string `json:"type"`
+	ProjectID      string `json:"project_id"`
+	PrivateKeyID   string `json:"private_key_id"`
+	PrivateKey     string `json:"private_key"`
+	ClientEmail    string `json:"client_email"`
+	ClientID       string `json:"client_id"`
+	AuthURL        string `json:"auth_uri"`
+	TokenURL       string `json:"token_uri"`
+	UniverseDomain string `json:"universe_domain"`
+}
+
+// UserCredentialsFile representation.
+type UserCredentialsFile struct {
+	Type           string `json:"type"`
+	ClientID       string `json:"client_id"`
+	ClientSecret   string `json:"client_secret"`
+	QuotaProjectID string `json:"quota_project_id"`
+	RefreshToken   string `json:"refresh_token"`
+	UniverseDomain string `json:"universe_domain"`
+}
+
+// ExternalAccountFile representation.
+type ExternalAccountFile struct {
+	Type                           string                           `json:"type"`
+	ClientID                       string                           `json:"client_id"`
+	ClientSecret                   string                           `json:"client_secret"`
+	Audience                       string                           `json:"audience"`
+	SubjectTokenType               string                           `json:"subject_token_type"`
+	ServiceAccountImpersonationURL string                           `json:"service_account_impersonation_url"`
+	TokenURL                       string                           `json:"token_url"`
+	CredentialSource               *CredentialSource                `json:"credential_source,omitempty"`
+	TokenInfoURL                   string                           `json:"token_info_url"`
+	ServiceAccountImpersonation    *ServiceAccountImpersonationInfo `json:"service_account_impersonation,omitempty"`
+	QuotaProjectID                 string                           `json:"quota_project_id"`
+	WorkforcePoolUserProject       string                           `json:"workforce_pool_user_project"`
+	UniverseDomain                 string                           `json:"universe_domain"`
+}
+
+// ExternalAccountAuthorizedUserFile representation.
+type ExternalAccountAuthorizedUserFile struct {
+	Type           string `json:"type"`
+	Audience       string `json:"audience"`
+	ClientID       string `json:"client_id"`
+	ClientSecret   string `json:"client_secret"`
+	RefreshToken   string `json:"refresh_token"`
+	TokenURL       string `json:"token_url"`
+	TokenInfoURL   string `json:"token_info_url"`
+	RevokeURL      string `json:"revoke_url"`
+	QuotaProjectID string `json:"quota_project_id"`
+	UniverseDomain string `json:"universe_domain"`
+}
+
+// CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
+//
+// One field amongst File, URL, Certificate, and Executable should be filled, depending on the kind of credential in question.
+// The EnvironmentID should start with AWS if being used for an AWS credential.
+type CredentialSource struct {
+	File                        string             `json:"file"`
+	URL                         string             `json:"url"`
+	Headers                     map[string]string  `json:"headers"`
+	Executable                  *ExecutableConfig  `json:"executable,omitempty"`
+	Certificate                 *CertificateConfig `json:"certificate"`
+	EnvironmentID               string             `json:"environment_id"` // TODO: Make type for this
+	RegionURL                   string             `json:"region_url"`
+	RegionalCredVerificationURL string             `json:"regional_cred_verification_url"`
+	CredVerificationURL         string             `json:"cred_verification_url"`
+	IMDSv2SessionTokenURL       string             `json:"imdsv2_session_token_url"`
+	Format                      *Format            `json:"format,omitempty"`
+}
+
+// Format describes the format of a [CredentialSource].
+type Format struct {
+	// Type is either "text" or "json". When not provided "text" type is assumed.
+	Type string `json:"type"`
+	// SubjectTokenFieldName is only required for JSON format. This would be "access_token" for azure.
+	SubjectTokenFieldName string `json:"subject_token_field_name"`
+}
+
+// ExecutableConfig represents the command to run for an executable
+// [CredentialSource].
+type ExecutableConfig struct {
+	Command       string `json:"command"`
+	TimeoutMillis int    `json:"timeout_millis"`
+	OutputFile    string `json:"output_file"`
+}
+
+// CertificateConfig represents the options used to set up X509 based workload
+// [CredentialSource]
+type CertificateConfig struct {
+	UseDefaultCertificateConfig bool   `json:"use_default_certificate_config"`
+	CertificateConfigLocation   string `json:"certificate_config_location"`
+}
+
+// ServiceAccountImpersonationInfo has impersonation configuration.
+type ServiceAccountImpersonationInfo struct {
+	TokenLifetimeSeconds int `json:"token_lifetime_seconds"`
+}
+
+// ImpersonatedServiceAccountFile representation.
+type ImpersonatedServiceAccountFile struct {
+	Type                           string          `json:"type"`
+	ServiceAccountImpersonationURL string          `json:"service_account_impersonation_url"`
+	Delegates                      []string        `json:"delegates"`
+	CredSource                     json.RawMessage `json:"source_credentials"`
+	UniverseDomain                 string          `json:"universe_domain"`
+}
+
+// GDCHServiceAccountFile represents the Google Distributed Cloud Hosted (GDCH) service identity file.
+type GDCHServiceAccountFile struct {
+	Type           string `json:"type"`
+	FormatVersion  string `json:"format_version"`
+	Project        string `json:"project"`
+	Name           string `json:"name"`
+	CertPath       string `json:"ca_cert_path"`
+	PrivateKeyID   string `json:"private_key_id"`
+	PrivateKey     string `json:"private_key"`
+	TokenURL       string `json:"token_uri"`
+	UniverseDomain string `json:"universe_domain"`
+}

+ 98 - 0
backend/vendor/cloud.google.com/go/auth/internal/credsfile/parse.go

@@ -0,0 +1,98 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package credsfile
+
+import (
+	"encoding/json"
+)
+
+// ParseServiceAccount parses bytes into a [ServiceAccountFile].
+func ParseServiceAccount(b []byte) (*ServiceAccountFile, error) {
+	var f *ServiceAccountFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseClientCredentials parses bytes into a
+// [credsfile.ClientCredentialsFile].
+func ParseClientCredentials(b []byte) (*ClientCredentialsFile, error) {
+	var f *ClientCredentialsFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseUserCredentials parses bytes into a [UserCredentialsFile].
+func ParseUserCredentials(b []byte) (*UserCredentialsFile, error) {
+	var f *UserCredentialsFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseExternalAccount parses bytes into a [ExternalAccountFile].
+func ParseExternalAccount(b []byte) (*ExternalAccountFile, error) {
+	var f *ExternalAccountFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseExternalAccountAuthorizedUser parses bytes into a
+// [ExternalAccountAuthorizedUserFile].
+func ParseExternalAccountAuthorizedUser(b []byte) (*ExternalAccountAuthorizedUserFile, error) {
+	var f *ExternalAccountAuthorizedUserFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseImpersonatedServiceAccount parses bytes into a
+// [ImpersonatedServiceAccountFile].
+func ParseImpersonatedServiceAccount(b []byte) (*ImpersonatedServiceAccountFile, error) {
+	var f *ImpersonatedServiceAccountFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+// ParseGDCHServiceAccount parses bytes into a [GDCHServiceAccountFile].
+func ParseGDCHServiceAccount(b []byte) (*GDCHServiceAccountFile, error) {
+	var f *GDCHServiceAccountFile
+	if err := json.Unmarshal(b, &f); err != nil {
+		return nil, err
+	}
+	return f, nil
+}
+
+type fileTypeChecker struct {
+	Type string `json:"type"`
+}
+
+// ParseFileType determines the [CredentialType] based on bytes provided.
+func ParseFileType(b []byte) (CredentialType, error) {
+	var f fileTypeChecker
+	if err := json.Unmarshal(b, &f); err != nil {
+		return 0, err
+	}
+	return parseCredentialType(f.Type), nil
+}

+ 216 - 0
backend/vendor/cloud.google.com/go/auth/internal/internal.go

@@ -0,0 +1,216 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package internal
+
+import (
+	"context"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/json"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"sync"
+	"time"
+
+	"cloud.google.com/go/compute/metadata"
+)
+
+const (
+	// TokenTypeBearer is the auth header prefix for bearer tokens.
+	TokenTypeBearer = "Bearer"
+
+	// QuotaProjectEnvVar is the environment variable for setting the quota
+	// project.
+	QuotaProjectEnvVar = "GOOGLE_CLOUD_QUOTA_PROJECT"
+	// UniverseDomainEnvVar is the environment variable for setting the default
+	// service domain for a given Cloud universe.
+	UniverseDomainEnvVar = "GOOGLE_CLOUD_UNIVERSE_DOMAIN"
+	projectEnvVar        = "GOOGLE_CLOUD_PROJECT"
+	maxBodySize          = 1 << 20
+
+	// DefaultUniverseDomain is the default value for universe domain.
+	// Universe domain is the default service domain for a given Cloud universe.
+	DefaultUniverseDomain = "googleapis.com"
+)
+
+type clonableTransport interface {
+	Clone() *http.Transport
+}
+
+// DefaultClient returns an [http.Client] with some defaults set. If
+// the current [http.DefaultTransport] is a [clonableTransport], as
+// is the case for an [*http.Transport], the clone will be used.
+// Otherwise the [http.DefaultTransport] is used directly.
+func DefaultClient() *http.Client {
+	if transport, ok := http.DefaultTransport.(clonableTransport); ok {
+		return &http.Client{
+			Transport: transport.Clone(),
+			Timeout:   30 * time.Second,
+		}
+	}
+
+	return &http.Client{
+		Transport: http.DefaultTransport,
+		Timeout:   30 * time.Second,
+	}
+}
+
+// ParseKey converts the binary contents of a private key file
+// to an *rsa.PrivateKey. It detects whether the private key is in a
+// PEM container or not. If so, it extracts the the private key
+// from PEM container before conversion. It only supports PEM
+// containers with no passphrase.
+func ParseKey(key []byte) (*rsa.PrivateKey, error) {
+	block, _ := pem.Decode(key)
+	if block != nil {
+		key = block.Bytes
+	}
+	parsedKey, err := x509.ParsePKCS8PrivateKey(key)
+	if err != nil {
+		parsedKey, err = x509.ParsePKCS1PrivateKey(key)
+		if err != nil {
+			return nil, fmt.Errorf("private key should be a PEM or plain PKCS1 or PKCS8: %w", err)
+		}
+	}
+	parsed, ok := parsedKey.(*rsa.PrivateKey)
+	if !ok {
+		return nil, errors.New("private key is invalid")
+	}
+	return parsed, nil
+}
+
+// GetQuotaProject retrieves quota project with precedence being: override,
+// environment variable, creds json file.
+func GetQuotaProject(b []byte, override string) string {
+	if override != "" {
+		return override
+	}
+	if env := os.Getenv(QuotaProjectEnvVar); env != "" {
+		return env
+	}
+	if b == nil {
+		return ""
+	}
+	var v struct {
+		QuotaProject string `json:"quota_project_id"`
+	}
+	if err := json.Unmarshal(b, &v); err != nil {
+		return ""
+	}
+	return v.QuotaProject
+}
+
+// GetProjectID retrieves project with precedence being: override,
+// environment variable, creds json file.
+func GetProjectID(b []byte, override string) string {
+	if override != "" {
+		return override
+	}
+	if env := os.Getenv(projectEnvVar); env != "" {
+		return env
+	}
+	if b == nil {
+		return ""
+	}
+	var v struct {
+		ProjectID string `json:"project_id"` // standard service account key
+		Project   string `json:"project"`    // gdch key
+	}
+	if err := json.Unmarshal(b, &v); err != nil {
+		return ""
+	}
+	if v.ProjectID != "" {
+		return v.ProjectID
+	}
+	return v.Project
+}
+
+// DoRequest executes the provided req with the client. It reads the response
+// body, closes it, and returns it.
+func DoRequest(client *http.Client, req *http.Request) (*http.Response, []byte, error) {
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, nil, err
+	}
+	defer resp.Body.Close()
+	body, err := ReadAll(io.LimitReader(resp.Body, maxBodySize))
+	if err != nil {
+		return nil, nil, err
+	}
+	return resp, body, nil
+}
+
+// ReadAll consumes the whole reader and safely reads the content of its body
+// with some overflow protection.
+func ReadAll(r io.Reader) ([]byte, error) {
+	return io.ReadAll(io.LimitReader(r, maxBodySize))
+}
+
+// StaticCredentialsProperty is a helper for creating static credentials
+// properties.
+func StaticCredentialsProperty(s string) StaticProperty {
+	return StaticProperty(s)
+}
+
+// StaticProperty always returns that value of the underlying string.
+type StaticProperty string
+
+// GetProperty loads the properly value provided the given context.
+func (p StaticProperty) GetProperty(context.Context) (string, error) {
+	return string(p), nil
+}
+
+// ComputeUniverseDomainProvider fetches the credentials universe domain from
+// the google cloud metadata service.
+type ComputeUniverseDomainProvider struct {
+	universeDomainOnce sync.Once
+	universeDomain     string
+	universeDomainErr  error
+}
+
+// GetProperty fetches the credentials universe domain from the google cloud
+// metadata service.
+func (c *ComputeUniverseDomainProvider) GetProperty(ctx context.Context) (string, error) {
+	c.universeDomainOnce.Do(func() {
+		c.universeDomain, c.universeDomainErr = getMetadataUniverseDomain(ctx)
+	})
+	if c.universeDomainErr != nil {
+		return "", c.universeDomainErr
+	}
+	return c.universeDomain, nil
+}
+
+// httpGetMetadataUniverseDomain is a package var for unit test substitution.
+var httpGetMetadataUniverseDomain = func(ctx context.Context) (string, error) {
+	ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+	defer cancel()
+	return metadata.GetWithContext(ctx, "universe/universe-domain")
+}
+
+func getMetadataUniverseDomain(ctx context.Context) (string, error) {
+	universeDomain, err := httpGetMetadataUniverseDomain(ctx)
+	if err == nil {
+		return universeDomain, nil
+	}
+	if _, ok := err.(metadata.NotDefinedError); ok {
+		// http.StatusNotFound (404)
+		return DefaultUniverseDomain, nil
+	}
+	return "", err
+}

+ 171 - 0
backend/vendor/cloud.google.com/go/auth/internal/jwt/jwt.go

@@ -0,0 +1,171 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jwt
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/rand"
+	"crypto/rsa"
+	"crypto/sha256"
+	"encoding/base64"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+)
+
+const (
+	// HeaderAlgRSA256 is the RS256 [Header.Algorithm].
+	HeaderAlgRSA256 = "RS256"
+	// HeaderAlgES256 is the ES256 [Header.Algorithm].
+	HeaderAlgES256 = "ES256"
+	// HeaderType is the standard [Header.Type].
+	HeaderType = "JWT"
+)
+
+// Header represents a JWT header.
+type Header struct {
+	Algorithm string `json:"alg"`
+	Type      string `json:"typ"`
+	KeyID     string `json:"kid"`
+}
+
+func (h *Header) encode() (string, error) {
+	b, err := json.Marshal(h)
+	if err != nil {
+		return "", err
+	}
+	return base64.RawURLEncoding.EncodeToString(b), nil
+}
+
+// Claims represents the claims set of a JWT.
+type Claims struct {
+	// Iss is the issuer JWT claim.
+	Iss string `json:"iss"`
+	// Scope is the scope JWT claim.
+	Scope string `json:"scope,omitempty"`
+	// Exp is the expiry JWT claim. If unset, default is in one hour from now.
+	Exp int64 `json:"exp"`
+	// Iat is the subject issued at claim. If unset, default is now.
+	Iat int64 `json:"iat"`
+	// Aud is the audience JWT claim. Optional.
+	Aud string `json:"aud"`
+	// Sub is the subject JWT claim. Optional.
+	Sub string `json:"sub,omitempty"`
+	// AdditionalClaims contains any additional non-standard JWT claims. Optional.
+	AdditionalClaims map[string]interface{} `json:"-"`
+}
+
+func (c *Claims) encode() (string, error) {
+	// Compensate for skew
+	now := time.Now().Add(-10 * time.Second)
+	if c.Iat == 0 {
+		c.Iat = now.Unix()
+	}
+	if c.Exp == 0 {
+		c.Exp = now.Add(time.Hour).Unix()
+	}
+	if c.Exp < c.Iat {
+		return "", fmt.Errorf("jwt: invalid Exp = %d; must be later than Iat = %d", c.Exp, c.Iat)
+	}
+
+	b, err := json.Marshal(c)
+	if err != nil {
+		return "", err
+	}
+
+	if len(c.AdditionalClaims) == 0 {
+		return base64.RawURLEncoding.EncodeToString(b), nil
+	}
+
+	// Marshal private claim set and then append it to b.
+	prv, err := json.Marshal(c.AdditionalClaims)
+	if err != nil {
+		return "", fmt.Errorf("invalid map of additional claims %v: %w", c.AdditionalClaims, err)
+	}
+
+	// Concatenate public and private claim JSON objects.
+	if !bytes.HasSuffix(b, []byte{'}'}) {
+		return "", fmt.Errorf("invalid JSON %s", b)
+	}
+	if !bytes.HasPrefix(prv, []byte{'{'}) {
+		return "", fmt.Errorf("invalid JSON %s", prv)
+	}
+	b[len(b)-1] = ','         // Replace closing curly brace with a comma.
+	b = append(b, prv[1:]...) // Append private claims.
+	return base64.RawURLEncoding.EncodeToString(b), nil
+}
+
+// EncodeJWS encodes the data using the provided key as a JSON web signature.
+func EncodeJWS(header *Header, c *Claims, key *rsa.PrivateKey) (string, error) {
+	head, err := header.encode()
+	if err != nil {
+		return "", err
+	}
+	claims, err := c.encode()
+	if err != nil {
+		return "", err
+	}
+	ss := fmt.Sprintf("%s.%s", head, claims)
+	h := sha256.New()
+	h.Write([]byte(ss))
+	sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, h.Sum(nil))
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s.%s", ss, base64.RawURLEncoding.EncodeToString(sig)), nil
+}
+
+// DecodeJWS decodes a claim set from a JWS payload.
+func DecodeJWS(payload string) (*Claims, error) {
+	// decode returned id token to get expiry
+	s := strings.Split(payload, ".")
+	if len(s) < 2 {
+		return nil, errors.New("invalid token received")
+	}
+	decoded, err := base64.RawURLEncoding.DecodeString(s[1])
+	if err != nil {
+		return nil, err
+	}
+	c := &Claims{}
+	if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c); err != nil {
+		return nil, err
+	}
+	if err := json.NewDecoder(bytes.NewBuffer(decoded)).Decode(&c.AdditionalClaims); err != nil {
+		return nil, err
+	}
+	return c, err
+}
+
+// VerifyJWS tests whether the provided JWT token's signature was produced by
+// the private key associated with the provided public key.
+func VerifyJWS(token string, key *rsa.PublicKey) error {
+	parts := strings.Split(token, ".")
+	if len(parts) != 3 {
+		return errors.New("jwt: invalid token received, token must have 3 parts")
+	}
+
+	signedContent := parts[0] + "." + parts[1]
+	signatureString, err := base64.RawURLEncoding.DecodeString(parts[2])
+	if err != nil {
+		return err
+	}
+
+	h := sha256.New()
+	h.Write([]byte(signedContent))
+	return rsa.VerifyPKCS1v15(key, crypto.SHA256, h.Sum(nil), signatureString)
+}

+ 364 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/cba.go

@@ -0,0 +1,364 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package transport
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"errors"
+	"log"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"strconv"
+	"strings"
+
+	"cloud.google.com/go/auth/internal"
+	"cloud.google.com/go/auth/internal/transport/cert"
+	"github.com/google/s2a-go"
+	"github.com/google/s2a-go/fallback"
+	"google.golang.org/grpc/credentials"
+)
+
+const (
+	mTLSModeAlways = "always"
+	mTLSModeNever  = "never"
+	mTLSModeAuto   = "auto"
+
+	// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
+	googleAPIUseS2AEnv     = "EXPERIMENTAL_GOOGLE_API_USE_S2A"
+	googleAPIUseCertSource = "GOOGLE_API_USE_CLIENT_CERTIFICATE"
+	googleAPIUseMTLS       = "GOOGLE_API_USE_MTLS_ENDPOINT"
+	googleAPIUseMTLSOld    = "GOOGLE_API_USE_MTLS"
+
+	universeDomainPlaceholder = "UNIVERSE_DOMAIN"
+
+	mtlsMDSRoot = "/run/google-mds-mtls/root.crt"
+	mtlsMDSKey  = "/run/google-mds-mtls/client.key"
+)
+
+var (
+	errUniverseNotSupportedMTLS = errors.New("mTLS is not supported in any universe other than googleapis.com")
+)
+
+// Options is a struct that is duplicated information from the individual
+// transport packages in order to avoid cyclic deps. It correlates 1:1 with
+// fields on httptransport.Options and grpctransport.Options.
+type Options struct {
+	Endpoint                string
+	DefaultMTLSEndpoint     string
+	DefaultEndpointTemplate string
+	ClientCertProvider      cert.Provider
+	Client                  *http.Client
+	UniverseDomain          string
+	EnableDirectPath        bool
+	EnableDirectPathXds     bool
+}
+
+// getUniverseDomain returns the default service domain for a given Cloud
+// universe.
+func (o *Options) getUniverseDomain() string {
+	if o.UniverseDomain == "" {
+		return internal.DefaultUniverseDomain
+	}
+	return o.UniverseDomain
+}
+
+// isUniverseDomainGDU returns true if the universe domain is the default Google
+// universe.
+func (o *Options) isUniverseDomainGDU() bool {
+	return o.getUniverseDomain() == internal.DefaultUniverseDomain
+}
+
+// defaultEndpoint returns the DefaultEndpointTemplate merged with the
+// universe domain if the DefaultEndpointTemplate is set, otherwise returns an
+// empty string.
+func (o *Options) defaultEndpoint() string {
+	if o.DefaultEndpointTemplate == "" {
+		return ""
+	}
+	return strings.Replace(o.DefaultEndpointTemplate, universeDomainPlaceholder, o.getUniverseDomain(), 1)
+}
+
+// mergedEndpoint merges a user-provided Endpoint of format host[:port] with the
+// default endpoint.
+func (o *Options) mergedEndpoint() (string, error) {
+	defaultEndpoint := o.defaultEndpoint()
+	u, err := url.Parse(fixScheme(defaultEndpoint))
+	if err != nil {
+		return "", err
+	}
+	return strings.Replace(defaultEndpoint, u.Host, o.Endpoint, 1), nil
+}
+
+func fixScheme(baseURL string) string {
+	if !strings.Contains(baseURL, "://") {
+		baseURL = "https://" + baseURL
+	}
+	return baseURL
+}
+
+// GetGRPCTransportCredsAndEndpoint returns an instance of
+// [google.golang.org/grpc/credentials.TransportCredentials], and the
+// corresponding endpoint to use for GRPC client.
+func GetGRPCTransportCredsAndEndpoint(opts *Options) (credentials.TransportCredentials, string, error) {
+	config, err := getTransportConfig(opts)
+	if err != nil {
+		return nil, "", err
+	}
+
+	defaultTransportCreds := credentials.NewTLS(&tls.Config{
+		GetClientCertificate: config.clientCertSource,
+	})
+
+	var s2aAddr string
+	var transportCredsForS2A credentials.TransportCredentials
+
+	if config.mtlsS2AAddress != "" {
+		s2aAddr = config.mtlsS2AAddress
+		transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
+		if err != nil {
+			log.Printf("Loading MTLS MDS credentials failed: %v", err)
+			if config.s2aAddress != "" {
+				s2aAddr = config.s2aAddress
+			} else {
+				return defaultTransportCreds, config.endpoint, nil
+			}
+		}
+	} else if config.s2aAddress != "" {
+		s2aAddr = config.s2aAddress
+	} else {
+		return defaultTransportCreds, config.endpoint, nil
+	}
+
+	var fallbackOpts *s2a.FallbackOptions
+	// In case of S2A failure, fall back to the endpoint that would've been used without S2A.
+	if fallbackHandshake, err := fallback.DefaultFallbackClientHandshakeFunc(config.endpoint); err == nil {
+		fallbackOpts = &s2a.FallbackOptions{
+			FallbackClientHandshakeFunc: fallbackHandshake,
+		}
+	}
+
+	s2aTransportCreds, err := s2a.NewClientCreds(&s2a.ClientOptions{
+		S2AAddress:     s2aAddr,
+		TransportCreds: transportCredsForS2A,
+		FallbackOpts:   fallbackOpts,
+	})
+	if err != nil {
+		// Use default if we cannot initialize S2A client transport credentials.
+		return defaultTransportCreds, config.endpoint, nil
+	}
+	return s2aTransportCreds, config.s2aMTLSEndpoint, nil
+}
+
+// GetHTTPTransportConfig returns a client certificate source and a function for
+// dialing MTLS with S2A.
+func GetHTTPTransportConfig(opts *Options) (cert.Provider, func(context.Context, string, string) (net.Conn, error), error) {
+	config, err := getTransportConfig(opts)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var s2aAddr string
+	var transportCredsForS2A credentials.TransportCredentials
+
+	if config.mtlsS2AAddress != "" {
+		s2aAddr = config.mtlsS2AAddress
+		transportCredsForS2A, err = loadMTLSMDSTransportCreds(mtlsMDSRoot, mtlsMDSKey)
+		if err != nil {
+			log.Printf("Loading MTLS MDS credentials failed: %v", err)
+			if config.s2aAddress != "" {
+				s2aAddr = config.s2aAddress
+			} else {
+				return config.clientCertSource, nil, nil
+			}
+		}
+	} else if config.s2aAddress != "" {
+		s2aAddr = config.s2aAddress
+	} else {
+		return config.clientCertSource, nil, nil
+	}
+
+	var fallbackOpts *s2a.FallbackOptions
+	// In case of S2A failure, fall back to the endpoint that would've been used without S2A.
+	if fallbackURL, err := url.Parse(config.endpoint); err == nil {
+		if fallbackDialer, fallbackServerAddr, err := fallback.DefaultFallbackDialerAndAddress(fallbackURL.Hostname()); err == nil {
+			fallbackOpts = &s2a.FallbackOptions{
+				FallbackDialer: &s2a.FallbackDialer{
+					Dialer:     fallbackDialer,
+					ServerAddr: fallbackServerAddr,
+				},
+			}
+		}
+	}
+
+	dialTLSContextFunc := s2a.NewS2ADialTLSContextFunc(&s2a.ClientOptions{
+		S2AAddress:     s2aAddr,
+		TransportCreds: transportCredsForS2A,
+		FallbackOpts:   fallbackOpts,
+	})
+	return nil, dialTLSContextFunc, nil
+}
+
+func loadMTLSMDSTransportCreds(mtlsMDSRootFile, mtlsMDSKeyFile string) (credentials.TransportCredentials, error) {
+	rootPEM, err := os.ReadFile(mtlsMDSRootFile)
+	if err != nil {
+		return nil, err
+	}
+	caCertPool := x509.NewCertPool()
+	ok := caCertPool.AppendCertsFromPEM(rootPEM)
+	if !ok {
+		return nil, errors.New("failed to load MTLS MDS root certificate")
+	}
+	// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
+	// followed by a PEM-encoded private key. For this reason, the concatenation is passed in to the
+	// tls.X509KeyPair function as both the certificate chain and private key arguments.
+	cert, err := tls.LoadX509KeyPair(mtlsMDSKeyFile, mtlsMDSKeyFile)
+	if err != nil {
+		return nil, err
+	}
+	tlsConfig := tls.Config{
+		RootCAs:      caCertPool,
+		Certificates: []tls.Certificate{cert},
+		MinVersion:   tls.VersionTLS13,
+	}
+	return credentials.NewTLS(&tlsConfig), nil
+}
+
+func getTransportConfig(opts *Options) (*transportConfig, error) {
+	clientCertSource, err := GetClientCertificateProvider(opts)
+	if err != nil {
+		return nil, err
+	}
+	endpoint, err := getEndpoint(opts, clientCertSource)
+	if err != nil {
+		return nil, err
+	}
+	defaultTransportConfig := transportConfig{
+		clientCertSource: clientCertSource,
+		endpoint:         endpoint,
+	}
+
+	if !shouldUseS2A(clientCertSource, opts) {
+		return &defaultTransportConfig, nil
+	}
+	if !opts.isUniverseDomainGDU() {
+		return nil, errUniverseNotSupportedMTLS
+	}
+
+	s2aAddress := GetS2AAddress()
+	mtlsS2AAddress := GetMTLSS2AAddress()
+	if s2aAddress == "" && mtlsS2AAddress == "" {
+		return &defaultTransportConfig, nil
+	}
+	return &transportConfig{
+		clientCertSource: clientCertSource,
+		endpoint:         endpoint,
+		s2aAddress:       s2aAddress,
+		mtlsS2AAddress:   mtlsS2AAddress,
+		s2aMTLSEndpoint:  opts.DefaultMTLSEndpoint,
+	}, nil
+}
+
+// GetClientCertificateProvider returns a default client certificate source, if
+// not provided by the user.
+//
+// A nil default source can be returned if the source does not exist. Any exceptions
+// encountered while initializing the default source will be reported as client
+// error (ex. corrupt metadata file).
+func GetClientCertificateProvider(opts *Options) (cert.Provider, error) {
+	if !isClientCertificateEnabled(opts) {
+		return nil, nil
+	} else if opts.ClientCertProvider != nil {
+		return opts.ClientCertProvider, nil
+	}
+	return cert.DefaultProvider()
+
+}
+
+// isClientCertificateEnabled returns true by default for all GDU universe domain, unless explicitly overridden by env var
+func isClientCertificateEnabled(opts *Options) bool {
+	if value, ok := os.LookupEnv(googleAPIUseCertSource); ok {
+		// error as false is OK
+		b, _ := strconv.ParseBool(value)
+		return b
+	}
+	return opts.isUniverseDomainGDU()
+}
+
+type transportConfig struct {
+	// The client certificate source.
+	clientCertSource cert.Provider
+	// The corresponding endpoint to use based on client certificate source.
+	endpoint string
+	// The plaintext S2A address if it can be used, otherwise an empty string.
+	s2aAddress string
+	// The MTLS S2A address if it can be used, otherwise an empty string.
+	mtlsS2AAddress string
+	// The MTLS endpoint to use with S2A.
+	s2aMTLSEndpoint string
+}
+
+// getEndpoint returns the endpoint for the service, taking into account the
+// user-provided endpoint override "settings.Endpoint".
+//
+// If no endpoint override is specified, we will either return the default endpoint or
+// the default mTLS endpoint if a client certificate is available.
+//
+// You can override the default endpoint choice (mtls vs. regular) by setting the
+// GOOGLE_API_USE_MTLS_ENDPOINT environment variable.
+//
+// If the endpoint override is an address (host:port) rather than full base
+// URL (ex. https://...), then the user-provided address will be merged into
+// the default endpoint. For example, WithEndpoint("myhost:8000") and
+// DefaultEndpointTemplate("https://UNIVERSE_DOMAIN/bar/baz") will return "https://myhost:8080/bar/baz"
+func getEndpoint(opts *Options, clientCertSource cert.Provider) (string, error) {
+	if opts.Endpoint == "" {
+		mtlsMode := getMTLSMode()
+		if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
+			if !opts.isUniverseDomainGDU() {
+				return "", errUniverseNotSupportedMTLS
+			}
+			return opts.DefaultMTLSEndpoint, nil
+		}
+		return opts.defaultEndpoint(), nil
+	}
+	if strings.Contains(opts.Endpoint, "://") {
+		// User passed in a full URL path, use it verbatim.
+		return opts.Endpoint, nil
+	}
+	if opts.defaultEndpoint() == "" {
+		// If DefaultEndpointTemplate is not configured,
+		// use the user provided endpoint verbatim. This allows a naked
+		// "host[:port]" URL to be used with GRPC Direct Path.
+		return opts.Endpoint, nil
+	}
+
+	// Assume user-provided endpoint is host[:port], merge it with the default endpoint.
+	return opts.mergedEndpoint()
+}
+
+func getMTLSMode() string {
+	mode := os.Getenv(googleAPIUseMTLS)
+	if mode == "" {
+		mode = os.Getenv(googleAPIUseMTLSOld) // Deprecated.
+	}
+	if mode == "" {
+		return mTLSModeAuto
+	}
+	return strings.ToLower(mode)
+}

+ 65 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/cert/default_cert.go

@@ -0,0 +1,65 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import (
+	"crypto/tls"
+	"errors"
+	"sync"
+)
+
+// defaultCertData holds all the variables pertaining to
+// the default certificate provider created by [DefaultProvider].
+//
+// A singleton model is used to allow the provider to be reused
+// by the transport layer. As mentioned in [DefaultProvider] (provider nil, nil)
+// may be returned to indicate a default provider could not be found, which
+// will skip extra tls config in the transport layer .
+type defaultCertData struct {
+	once     sync.Once
+	provider Provider
+	err      error
+}
+
+var (
+	defaultCert defaultCertData
+)
+
+// Provider is a function that can be passed into crypto/tls.Config.GetClientCertificate.
+type Provider func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
+
+// errSourceUnavailable is a sentinel error to indicate certificate source is unavailable.
+var errSourceUnavailable = errors.New("certificate source is unavailable")
+
+// DefaultProvider returns a certificate source using the preferred EnterpriseCertificateProxySource.
+// If EnterpriseCertificateProxySource is not available, fall back to the legacy SecureConnectSource.
+//
+// If neither source is available (due to missing configurations), a nil Source and a nil Error are
+// returned to indicate that a default certificate source is unavailable.
+func DefaultProvider() (Provider, error) {
+	defaultCert.once.Do(func() {
+		defaultCert.provider, defaultCert.err = NewWorkloadX509CertProvider("")
+		if errors.Is(defaultCert.err, errSourceUnavailable) {
+			defaultCert.provider, defaultCert.err = NewEnterpriseCertificateProxyProvider("")
+			if errors.Is(defaultCert.err, errSourceUnavailable) {
+				defaultCert.provider, defaultCert.err = NewSecureConnectProvider("")
+				if errors.Is(defaultCert.err, errSourceUnavailable) {
+					defaultCert.provider, defaultCert.err = nil, nil
+				}
+			}
+		}
+	})
+	return defaultCert.provider, defaultCert.err
+}

+ 54 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/cert/enterprise_cert.go

@@ -0,0 +1,54 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import (
+	"crypto/tls"
+
+	"github.com/googleapis/enterprise-certificate-proxy/client"
+)
+
+type ecpSource struct {
+	key *client.Key
+}
+
+// NewEnterpriseCertificateProxyProvider creates a certificate source
+// using the Enterprise Certificate Proxy client, which delegates
+// certifcate related operations to an OS-specific "signer binary"
+// that communicates with the native keystore (ex. keychain on MacOS).
+//
+// The configFilePath points to a config file containing relevant parameters
+// such as the certificate issuer and the location of the signer binary.
+// If configFilePath is empty, the client will attempt to load the config from
+// a well-known gcloud location.
+func NewEnterpriseCertificateProxyProvider(configFilePath string) (Provider, error) {
+	key, err := client.Cred(configFilePath)
+	if err != nil {
+		// TODO(codyoss): once this is fixed upstream can handle this error a
+		// little better here. But be safe for now and assume unavailable.
+		return nil, errSourceUnavailable
+	}
+
+	return (&ecpSource{
+		key: key,
+	}).getClientCertificate, nil
+}
+
+func (s *ecpSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+	var cert tls.Certificate
+	cert.PrivateKey = s.key
+	cert.Certificate = s.key.CertificateChain()
+	return &cert, nil
+}

+ 124 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/cert/secureconnect_cert.go

@@ -0,0 +1,124 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"os"
+	"os/exec"
+	"os/user"
+	"path/filepath"
+	"sync"
+	"time"
+)
+
+const (
+	metadataPath = ".secureConnect"
+	metadataFile = "context_aware_metadata.json"
+)
+
+type secureConnectSource struct {
+	metadata secureConnectMetadata
+
+	// Cache the cert to avoid executing helper command repeatedly.
+	cachedCertMutex sync.Mutex
+	cachedCert      *tls.Certificate
+}
+
+type secureConnectMetadata struct {
+	Cmd []string `json:"cert_provider_command"`
+}
+
+// NewSecureConnectProvider creates a certificate source using
+// the Secure Connect Helper and its associated metadata file.
+//
+// The configFilePath points to the location of the context aware metadata file.
+// If configFilePath is empty, use the default context aware metadata location.
+func NewSecureConnectProvider(configFilePath string) (Provider, error) {
+	if configFilePath == "" {
+		user, err := user.Current()
+		if err != nil {
+			// Error locating the default config means Secure Connect is not supported.
+			return nil, errSourceUnavailable
+		}
+		configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
+	}
+
+	file, err := os.ReadFile(configFilePath)
+	if err != nil {
+		// Config file missing means Secure Connect is not supported.
+		// There are non-os.ErrNotExist errors that may be returned.
+		// (e.g. if the home directory is /dev/null, *nix systems will
+		// return ENOTDIR instead of ENOENT)
+		return nil, errSourceUnavailable
+	}
+
+	var metadata secureConnectMetadata
+	if err := json.Unmarshal(file, &metadata); err != nil {
+		return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
+	}
+	if err := validateMetadata(metadata); err != nil {
+		return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
+	}
+	return (&secureConnectSource{
+		metadata: metadata,
+	}).getClientCertificate, nil
+}
+
+func validateMetadata(metadata secureConnectMetadata) error {
+	if len(metadata.Cmd) == 0 {
+		return errors.New("empty cert_provider_command")
+	}
+	return nil
+}
+
+func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+	s.cachedCertMutex.Lock()
+	defer s.cachedCertMutex.Unlock()
+	if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
+		return s.cachedCert, nil
+	}
+	// Expand OS environment variables in the cert provider command such as "$HOME".
+	for i := 0; i < len(s.metadata.Cmd); i++ {
+		s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
+	}
+	command := s.metadata.Cmd
+	data, err := exec.Command(command[0], command[1:]...).Output()
+	if err != nil {
+		return nil, err
+	}
+	cert, err := tls.X509KeyPair(data, data)
+	if err != nil {
+		return nil, err
+	}
+	s.cachedCert = &cert
+	return &cert, nil
+}
+
+// isCertificateExpired returns true if the given cert is expired or invalid.
+func isCertificateExpired(cert *tls.Certificate) bool {
+	if len(cert.Certificate) == 0 {
+		return true
+	}
+	parsed, err := x509.ParseCertificate(cert.Certificate[0])
+	if err != nil {
+		return true
+	}
+	return time.Now().After(parsed.NotAfter)
+}

+ 114 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/cert/workload_cert.go

@@ -0,0 +1,114 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cert
+
+import (
+	"crypto/tls"
+	"encoding/json"
+	"errors"
+	"io"
+	"os"
+
+	"github.com/googleapis/enterprise-certificate-proxy/client/util"
+)
+
+type certConfigs struct {
+	Workload *workloadSource `json:"workload"`
+}
+
+type workloadSource struct {
+	CertPath string `json:"cert_path"`
+	KeyPath  string `json:"key_path"`
+}
+
+type certificateConfig struct {
+	CertConfigs certConfigs `json:"cert_configs"`
+}
+
+// NewWorkloadX509CertProvider creates a certificate source
+// that reads a certificate and private key file from the local file system.
+// This is intended to be used for workload identity federation.
+//
+// The configFilePath points to a config file containing relevant parameters
+// such as the certificate and key file paths.
+// If configFilePath is empty, the client will attempt to load the config from
+// a well-known gcloud location.
+func NewWorkloadX509CertProvider(configFilePath string) (Provider, error) {
+	if configFilePath == "" {
+		envFilePath := util.GetConfigFilePathFromEnv()
+		if envFilePath != "" {
+			configFilePath = envFilePath
+		} else {
+			configFilePath = util.GetDefaultConfigFilePath()
+		}
+	}
+
+	certFile, keyFile, err := getCertAndKeyFiles(configFilePath)
+	if err != nil {
+		return nil, err
+	}
+
+	source := &workloadSource{
+		CertPath: certFile,
+		KeyPath:  keyFile,
+	}
+	return source.getClientCertificate, nil
+}
+
+// getClientCertificate attempts to load the certificate and key from the files specified in the
+// certificate config.
+func (s *workloadSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
+	cert, err := tls.LoadX509KeyPair(s.CertPath, s.KeyPath)
+	if err != nil {
+		return nil, err
+	}
+	return &cert, nil
+}
+
+// getCertAndKeyFiles attempts to read the provided config file and return the certificate and private
+// key file paths.
+func getCertAndKeyFiles(configFilePath string) (string, string, error) {
+	jsonFile, err := os.Open(configFilePath)
+	if err != nil {
+		return "", "", errSourceUnavailable
+	}
+
+	byteValue, err := io.ReadAll(jsonFile)
+	if err != nil {
+		return "", "", err
+	}
+
+	var config certificateConfig
+	if err := json.Unmarshal(byteValue, &config); err != nil {
+		return "", "", err
+	}
+
+	if config.CertConfigs.Workload == nil {
+		return "", "", errSourceUnavailable
+	}
+
+	certFile := config.CertConfigs.Workload.CertPath
+	keyFile := config.CertConfigs.Workload.KeyPath
+
+	if certFile == "" {
+		return "", "", errors.New("certificate configuration is missing the certificate file location")
+	}
+
+	if keyFile == "" {
+		return "", "", errors.New("certificate configuration is missing the key file location")
+	}
+
+	return certFile, keyFile, nil
+}

+ 134 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/s2a.go

@@ -0,0 +1,134 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package transport
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log"
+	"os"
+	"strconv"
+	"sync"
+
+	"cloud.google.com/go/auth/internal/transport/cert"
+	"cloud.google.com/go/compute/metadata"
+)
+
+const (
+	configEndpointSuffix = "instance/platform-security/auto-mtls-configuration"
+)
+
+var (
+	mtlsConfiguration *mtlsConfig
+
+	mtlsOnce sync.Once
+)
+
+// GetS2AAddress returns the S2A address to be reached via plaintext connection.
+// Returns empty string if not set or invalid.
+func GetS2AAddress() string {
+	getMetadataMTLSAutoConfig()
+	if !mtlsConfiguration.valid() {
+		return ""
+	}
+	return mtlsConfiguration.S2A.PlaintextAddress
+}
+
+// GetMTLSS2AAddress returns the S2A address to be reached via MTLS connection.
+// Returns empty string if not set or invalid.
+func GetMTLSS2AAddress() string {
+	getMetadataMTLSAutoConfig()
+	if !mtlsConfiguration.valid() {
+		return ""
+	}
+	return mtlsConfiguration.S2A.MTLSAddress
+}
+
+// mtlsConfig contains the configuration for establishing MTLS connections with Google APIs.
+type mtlsConfig struct {
+	S2A *s2aAddresses `json:"s2a"`
+}
+
+func (c *mtlsConfig) valid() bool {
+	return c != nil && c.S2A != nil
+}
+
+// s2aAddresses contains the plaintext and/or MTLS S2A addresses.
+type s2aAddresses struct {
+	// PlaintextAddress is the plaintext address to reach S2A
+	PlaintextAddress string `json:"plaintext_address"`
+	// MTLSAddress is the MTLS address to reach S2A
+	MTLSAddress string `json:"mtls_address"`
+}
+
+func getMetadataMTLSAutoConfig() {
+	var err error
+	mtlsOnce.Do(func() {
+		mtlsConfiguration, err = queryConfig()
+		if err != nil {
+			log.Printf("Getting MTLS config failed: %v", err)
+		}
+	})
+}
+
+var httpGetMetadataMTLSConfig = func() (string, error) {
+	return metadata.GetWithContext(context.Background(), configEndpointSuffix)
+}
+
+func queryConfig() (*mtlsConfig, error) {
+	resp, err := httpGetMetadataMTLSConfig()
+	if err != nil {
+		return nil, fmt.Errorf("querying MTLS config from MDS endpoint failed: %w", err)
+	}
+	var config mtlsConfig
+	err = json.Unmarshal([]byte(resp), &config)
+	if err != nil {
+		return nil, fmt.Errorf("unmarshalling MTLS config from MDS endpoint failed: %w", err)
+	}
+	if config.S2A == nil {
+		return nil, fmt.Errorf("returned MTLS config from MDS endpoint is invalid: %v", config)
+	}
+	return &config, nil
+}
+
+func shouldUseS2A(clientCertSource cert.Provider, opts *Options) bool {
+	// If client cert is found, use that over S2A.
+	if clientCertSource != nil {
+		return false
+	}
+	// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
+	if !isGoogleS2AEnabled() {
+		return false
+	}
+	// If DefaultMTLSEndpoint is not set or has endpoint override, skip S2A.
+	if opts.DefaultMTLSEndpoint == "" || opts.Endpoint != "" {
+		return false
+	}
+	// If custom HTTP client is provided, skip S2A.
+	if opts.Client != nil {
+		return false
+	}
+	// If directPath is enabled, skip S2A.
+	return !opts.EnableDirectPath && !opts.EnableDirectPathXds
+}
+
+func isGoogleS2AEnabled() bool {
+	b, err := strconv.ParseBool(os.Getenv(googleAPIUseS2AEnv))
+	if err != nil {
+		return false
+	}
+	return b
+}

+ 105 - 0
backend/vendor/cloud.google.com/go/auth/internal/transport/transport.go

@@ -0,0 +1,105 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package transport provided internal helpers for the two transport packages
+// (grpctransport and httptransport).
+package transport
+
+import (
+	"crypto/tls"
+	"fmt"
+	"net"
+	"net/http"
+	"time"
+
+	"cloud.google.com/go/auth/credentials"
+)
+
+// CloneDetectOptions clones a user set detect option into some new memory that
+// we can internally manipulate before sending onto the detect package.
+func CloneDetectOptions(oldDo *credentials.DetectOptions) *credentials.DetectOptions {
+	if oldDo == nil {
+		// it is valid for users not to set this, but we will need to to default
+		// some options for them in this case so return some initialized memory
+		// to work with.
+		return &credentials.DetectOptions{}
+	}
+	newDo := &credentials.DetectOptions{
+		// Simple types
+		Audience:          oldDo.Audience,
+		Subject:           oldDo.Subject,
+		EarlyTokenRefresh: oldDo.EarlyTokenRefresh,
+		TokenURL:          oldDo.TokenURL,
+		STSAudience:       oldDo.STSAudience,
+		CredentialsFile:   oldDo.CredentialsFile,
+		UseSelfSignedJWT:  oldDo.UseSelfSignedJWT,
+		UniverseDomain:    oldDo.UniverseDomain,
+
+		// These fields are are pointer types that we just want to use exactly
+		// as the user set, copy the ref
+		Client:             oldDo.Client,
+		AuthHandlerOptions: oldDo.AuthHandlerOptions,
+	}
+
+	// Smartly size this memory and copy below.
+	if len(oldDo.CredentialsJSON) > 0 {
+		newDo.CredentialsJSON = make([]byte, len(oldDo.CredentialsJSON))
+		copy(newDo.CredentialsJSON, oldDo.CredentialsJSON)
+	}
+	if len(oldDo.Scopes) > 0 {
+		newDo.Scopes = make([]string, len(oldDo.Scopes))
+		copy(newDo.Scopes, oldDo.Scopes)
+	}
+
+	return newDo
+}
+
+// ValidateUniverseDomain verifies that the universe domain configured for the
+// client matches the universe domain configured for the credentials.
+func ValidateUniverseDomain(clientUniverseDomain, credentialsUniverseDomain string) error {
+	if clientUniverseDomain != credentialsUniverseDomain {
+		return fmt.Errorf(
+			"the configured universe domain (%q) does not match the universe "+
+				"domain found in the credentials (%q). If you haven't configured "+
+				"the universe domain explicitly, \"googleapis.com\" is the default",
+			clientUniverseDomain,
+			credentialsUniverseDomain)
+	}
+	return nil
+}
+
+// DefaultHTTPClientWithTLS constructs an HTTPClient using the provided tlsConfig, to support mTLS.
+func DefaultHTTPClientWithTLS(tlsConfig *tls.Config) *http.Client {
+	trans := BaseTransport()
+	trans.TLSClientConfig = tlsConfig
+	return &http.Client{Transport: trans}
+}
+
+// BaseTransport returns a default [http.Transport] which can be used if
+// [http.DefaultTransport] has been overwritten.
+func BaseTransport() *http.Transport {
+	return &http.Transport{
+		Proxy: http.ProxyFromEnvironment,
+		DialContext: (&net.Dialer{
+			Timeout:   30 * time.Second,
+			KeepAlive: 30 * time.Second,
+			DualStack: true,
+		}).DialContext,
+		MaxIdleConns:          100,
+		MaxIdleConnsPerHost:   100,
+		IdleConnTimeout:       90 * time.Second,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+	}
+}

+ 61 - 0
backend/vendor/cloud.google.com/go/auth/oauth2adapt/CHANGES.md

@@ -0,0 +1,61 @@
+# Changelog
+
+## [0.2.5](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.4...auth/oauth2adapt/v0.2.5) (2024-10-30)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Convert token metadata where possible ([#11062](https://github.com/googleapis/google-cloud-go/issues/11062)) ([34bf1c1](https://github.com/googleapis/google-cloud-go/commit/34bf1c164465d66745c0cfdf7cd10a8e2da92e52))
+
+## [0.2.4](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.3...auth/oauth2adapt/v0.2.4) (2024-08-08)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update dependencies ([257c40b](https://github.com/googleapis/google-cloud-go/commit/257c40bd6d7e59730017cf32bda8823d7a232758))
+
+## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.2...auth/oauth2adapt/v0.2.3) (2024-07-10)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Bump google.golang.org/[email protected] ([8fa9e39](https://github.com/googleapis/google-cloud-go/commit/8fa9e398e512fd8533fd49060371e61b5725a85b))
+
+## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.1...auth/oauth2adapt/v0.2.2) (2024-04-23)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Bump x/net to v0.24.0 ([ba31ed5](https://github.com/googleapis/google-cloud-go/commit/ba31ed5fda2c9664f2e1cf972469295e63deb5b4))
+
+## [0.2.1](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.2.0...auth/oauth2adapt/v0.2.1) (2024-04-18)
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Adapt Token Types to be translated ([#9801](https://github.com/googleapis/google-cloud-go/issues/9801)) ([70f4115](https://github.com/googleapis/google-cloud-go/commit/70f411555ebbf2b71e6d425cc8d2030644c6b438)), refs [#9800](https://github.com/googleapis/google-cloud-go/issues/9800)
+
+## [0.2.0](https://github.com/googleapis/google-cloud-go/compare/auth/oauth2adapt/v0.1.0...auth/oauth2adapt/v0.2.0) (2024-04-16)
+
+
+### Features
+
+* **auth/oauth2adapt:** Add helpers for working with credentials types ([#9694](https://github.com/googleapis/google-cloud-go/issues/9694)) ([cf33b55](https://github.com/googleapis/google-cloud-go/commit/cf33b5514423a2ac5c2a323a1cd99aac34fd4233))
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update protobuf dep to v1.33.0 ([30b038d](https://github.com/googleapis/google-cloud-go/commit/30b038d8cac0b8cd5dd4761c87f3f298760dd33a))
+
+## 0.1.0 (2023-10-19)
+
+
+### Features
+
+* **auth/oauth2adapt:** Adds a new module to translate types ([#8595](https://github.com/googleapis/google-cloud-go/issues/8595)) ([6933c5a](https://github.com/googleapis/google-cloud-go/commit/6933c5a0c1fc8e58cbfff8bbca439d671b94672f))
+* **auth/oauth2adapt:** Fixup deps for release ([#8747](https://github.com/googleapis/google-cloud-go/issues/8747)) ([749d243](https://github.com/googleapis/google-cloud-go/commit/749d243862b025a6487a4d2d339219889b4cfe70))
+
+
+### Bug Fixes
+
+* **auth/oauth2adapt:** Update golang.org/x/net to v0.17.0 ([174da47](https://github.com/googleapis/google-cloud-go/commit/174da47254fefb12921bbfc65b7829a453af6f5d))

+ 202 - 0
backend/vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 195 - 0
backend/vendor/cloud.google.com/go/auth/oauth2adapt/oauth2adapt.go

@@ -0,0 +1,195 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package oauth2adapt helps converts types used in [cloud.google.com/go/auth]
+// and [golang.org/x/oauth2].
+package oauth2adapt
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+
+	"cloud.google.com/go/auth"
+	"golang.org/x/oauth2"
+	"golang.org/x/oauth2/google"
+)
+
+const (
+	oauth2TokenSourceKey    = "oauth2.google.tokenSource"
+	oauth2ServiceAccountKey = "oauth2.google.serviceAccount"
+	authTokenSourceKey      = "auth.google.tokenSource"
+	authServiceAccountKey   = "auth.google.serviceAccount"
+)
+
+// TokenProviderFromTokenSource converts any [golang.org/x/oauth2.TokenSource]
+// into a [cloud.google.com/go/auth.TokenProvider].
+func TokenProviderFromTokenSource(ts oauth2.TokenSource) auth.TokenProvider {
+	return &tokenProviderAdapter{ts: ts}
+}
+
+type tokenProviderAdapter struct {
+	ts oauth2.TokenSource
+}
+
+// Token fulfills the [cloud.google.com/go/auth.TokenProvider] interface. It
+// is a light wrapper around the underlying TokenSource.
+func (tp *tokenProviderAdapter) Token(context.Context) (*auth.Token, error) {
+	tok, err := tp.ts.Token()
+	if err != nil {
+		var err2 *oauth2.RetrieveError
+		if ok := errors.As(err, &err2); ok {
+			return nil, AuthErrorFromRetrieveError(err2)
+		}
+		return nil, err
+	}
+	// Preserve compute token metadata, for both types of tokens.
+	metadata := map[string]interface{}{}
+	if val, ok := tok.Extra(oauth2TokenSourceKey).(string); ok {
+		metadata[authTokenSourceKey] = val
+		metadata[oauth2TokenSourceKey] = val
+	}
+	if val, ok := tok.Extra(oauth2ServiceAccountKey).(string); ok {
+		metadata[authServiceAccountKey] = val
+		metadata[oauth2ServiceAccountKey] = val
+	}
+	return &auth.Token{
+		Value:    tok.AccessToken,
+		Type:     tok.Type(),
+		Expiry:   tok.Expiry,
+		Metadata: metadata,
+	}, nil
+}
+
+// TokenSourceFromTokenProvider converts any
+// [cloud.google.com/go/auth.TokenProvider] into a
+// [golang.org/x/oauth2.TokenSource].
+func TokenSourceFromTokenProvider(tp auth.TokenProvider) oauth2.TokenSource {
+	return &tokenSourceAdapter{tp: tp}
+}
+
+type tokenSourceAdapter struct {
+	tp auth.TokenProvider
+}
+
+// Token fulfills the [golang.org/x/oauth2.TokenSource] interface. It
+// is a light wrapper around the underlying TokenProvider.
+func (ts *tokenSourceAdapter) Token() (*oauth2.Token, error) {
+	tok, err := ts.tp.Token(context.Background())
+	if err != nil {
+		var err2 *auth.Error
+		if ok := errors.As(err, &err2); ok {
+			return nil, AddRetrieveErrorToAuthError(err2)
+		}
+		return nil, err
+	}
+	tok2 := &oauth2.Token{
+		AccessToken: tok.Value,
+		TokenType:   tok.Type,
+		Expiry:      tok.Expiry,
+	}
+	// Preserve token metadata.
+	metadata := tok.Metadata
+	if metadata != nil {
+		// Append compute token metadata in converted form.
+		if val, ok := metadata[authTokenSourceKey].(string); ok && val != "" {
+			metadata[oauth2TokenSourceKey] = val
+		}
+		if val, ok := metadata[authServiceAccountKey].(string); ok && val != "" {
+			metadata[oauth2ServiceAccountKey] = val
+		}
+		tok2 = tok2.WithExtra(metadata)
+	}
+	return tok2, nil
+}
+
+// AuthCredentialsFromOauth2Credentials converts a [golang.org/x/oauth2/google.Credentials]
+// to a [cloud.google.com/go/auth.Credentials].
+func AuthCredentialsFromOauth2Credentials(creds *google.Credentials) *auth.Credentials {
+	if creds == nil {
+		return nil
+	}
+	return auth.NewCredentials(&auth.CredentialsOptions{
+		TokenProvider: TokenProviderFromTokenSource(creds.TokenSource),
+		JSON:          creds.JSON,
+		ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
+			return creds.ProjectID, nil
+		}),
+		UniverseDomainProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
+			return creds.GetUniverseDomain()
+		}),
+	})
+}
+
+// Oauth2CredentialsFromAuthCredentials converts a [cloud.google.com/go/auth.Credentials]
+// to a [golang.org/x/oauth2/google.Credentials].
+func Oauth2CredentialsFromAuthCredentials(creds *auth.Credentials) *google.Credentials {
+	if creds == nil {
+		return nil
+	}
+	// Throw away errors as old credentials are not request aware. Also, no
+	// network requests are currently happening for this use case.
+	projectID, _ := creds.ProjectID(context.Background())
+
+	return &google.Credentials{
+		TokenSource: TokenSourceFromTokenProvider(creds.TokenProvider),
+		ProjectID:   projectID,
+		JSON:        creds.JSON(),
+		UniverseDomainProvider: func() (string, error) {
+			return creds.UniverseDomain(context.Background())
+		},
+	}
+}
+
+type oauth2Error struct {
+	ErrorCode        string `json:"error"`
+	ErrorDescription string `json:"error_description"`
+	ErrorURI         string `json:"error_uri"`
+}
+
+// AddRetrieveErrorToAuthError returns the same error provided and adds a
+// [golang.org/x/oauth2.RetrieveError] to the error chain by setting the `Err` field on the
+// [cloud.google.com/go/auth.Error].
+func AddRetrieveErrorToAuthError(err *auth.Error) *auth.Error {
+	if err == nil {
+		return nil
+	}
+	e := &oauth2.RetrieveError{
+		Response: err.Response,
+		Body:     err.Body,
+	}
+	err.Err = e
+	if len(err.Body) > 0 {
+		var oErr oauth2Error
+		// ignore the error as it only fills in extra details
+		json.Unmarshal(err.Body, &oErr)
+		e.ErrorCode = oErr.ErrorCode
+		e.ErrorDescription = oErr.ErrorDescription
+		e.ErrorURI = oErr.ErrorURI
+	}
+	return err
+}
+
+// AuthErrorFromRetrieveError returns an [cloud.google.com/go/auth.Error] that
+// wraps the provided [golang.org/x/oauth2.RetrieveError].
+func AuthErrorFromRetrieveError(err *oauth2.RetrieveError) *auth.Error {
+	if err == nil {
+		return nil
+	}
+	return &auth.Error{
+		Response: err.Response,
+		Body:     err.Body,
+		Err:      err,
+	}
+}

+ 368 - 0
backend/vendor/cloud.google.com/go/auth/threelegged.go

@@ -0,0 +1,368 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package auth
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"mime"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
+
+	"cloud.google.com/go/auth/internal"
+)
+
+// AuthorizationHandler is a 3-legged-OAuth helper that prompts the user for
+// OAuth consent at the specified auth code URL and returns an auth code and
+// state upon approval.
+type AuthorizationHandler func(authCodeURL string) (code string, state string, err error)
+
+// Options3LO are the options for doing a 3-legged OAuth2 flow.
+type Options3LO struct {
+	// ClientID is the application's ID.
+	ClientID string
+	// ClientSecret is the application's secret. Not required if AuthHandlerOpts
+	// is set.
+	ClientSecret string
+	// AuthURL is the URL for authenticating.
+	AuthURL string
+	// TokenURL is the URL for retrieving a token.
+	TokenURL string
+	// AuthStyle is used to describe how to client info in the token request.
+	AuthStyle Style
+	// RefreshToken is the token used to refresh the credential. Not required
+	// if AuthHandlerOpts is set.
+	RefreshToken string
+	// RedirectURL is the URL to redirect users to. Optional.
+	RedirectURL string
+	// Scopes specifies requested permissions for the Token. Optional.
+	Scopes []string
+
+	// URLParams are the set of values to apply to the token exchange. Optional.
+	URLParams url.Values
+	// Client is the client to be used to make the underlying token requests.
+	// Optional.
+	Client *http.Client
+	// EarlyTokenExpiry is the time before the token expires that it should be
+	// refreshed. If not set the default value is 3 minutes and 45 seconds.
+	// Optional.
+	EarlyTokenExpiry time.Duration
+
+	// AuthHandlerOpts provides a set of options for doing a
+	// 3-legged OAuth2 flow with a custom [AuthorizationHandler]. Optional.
+	AuthHandlerOpts *AuthorizationHandlerOptions
+}
+
+func (o *Options3LO) validate() error {
+	if o == nil {
+		return errors.New("auth: options must be provided")
+	}
+	if o.ClientID == "" {
+		return errors.New("auth: client ID must be provided")
+	}
+	if o.AuthHandlerOpts == nil && o.ClientSecret == "" {
+		return errors.New("auth: client secret must be provided")
+	}
+	if o.AuthURL == "" {
+		return errors.New("auth: auth URL must be provided")
+	}
+	if o.TokenURL == "" {
+		return errors.New("auth: token URL must be provided")
+	}
+	if o.AuthStyle == StyleUnknown {
+		return errors.New("auth: auth style must be provided")
+	}
+	if o.AuthHandlerOpts == nil && o.RefreshToken == "" {
+		return errors.New("auth: refresh token must be provided")
+	}
+	return nil
+}
+
+// PKCEOptions holds parameters to support PKCE.
+type PKCEOptions struct {
+	// Challenge is the un-padded, base64-url-encoded string of the encrypted code verifier.
+	Challenge string // The un-padded, base64-url-encoded string of the encrypted code verifier.
+	// ChallengeMethod is the encryption method (ex. S256).
+	ChallengeMethod string
+	// Verifier is the original, non-encrypted secret.
+	Verifier string // The original, non-encrypted secret.
+}
+
+type tokenJSON struct {
+	AccessToken  string `json:"access_token"`
+	TokenType    string `json:"token_type"`
+	RefreshToken string `json:"refresh_token"`
+	ExpiresIn    int    `json:"expires_in"`
+	// error fields
+	ErrorCode        string `json:"error"`
+	ErrorDescription string `json:"error_description"`
+	ErrorURI         string `json:"error_uri"`
+}
+
+func (e *tokenJSON) expiry() (t time.Time) {
+	if v := e.ExpiresIn; v != 0 {
+		return time.Now().Add(time.Duration(v) * time.Second)
+	}
+	return
+}
+
+func (o *Options3LO) client() *http.Client {
+	if o.Client != nil {
+		return o.Client
+	}
+	return internal.DefaultClient()
+}
+
+// authCodeURL returns a URL that points to a OAuth2 consent page.
+func (o *Options3LO) authCodeURL(state string, values url.Values) string {
+	var buf bytes.Buffer
+	buf.WriteString(o.AuthURL)
+	v := url.Values{
+		"response_type": {"code"},
+		"client_id":     {o.ClientID},
+	}
+	if o.RedirectURL != "" {
+		v.Set("redirect_uri", o.RedirectURL)
+	}
+	if len(o.Scopes) > 0 {
+		v.Set("scope", strings.Join(o.Scopes, " "))
+	}
+	if state != "" {
+		v.Set("state", state)
+	}
+	if o.AuthHandlerOpts != nil {
+		if o.AuthHandlerOpts.PKCEOpts != nil &&
+			o.AuthHandlerOpts.PKCEOpts.Challenge != "" {
+			v.Set(codeChallengeKey, o.AuthHandlerOpts.PKCEOpts.Challenge)
+		}
+		if o.AuthHandlerOpts.PKCEOpts != nil &&
+			o.AuthHandlerOpts.PKCEOpts.ChallengeMethod != "" {
+			v.Set(codeChallengeMethodKey, o.AuthHandlerOpts.PKCEOpts.ChallengeMethod)
+		}
+	}
+	for k := range values {
+		v.Set(k, v.Get(k))
+	}
+	if strings.Contains(o.AuthURL, "?") {
+		buf.WriteByte('&')
+	} else {
+		buf.WriteByte('?')
+	}
+	buf.WriteString(v.Encode())
+	return buf.String()
+}
+
+// New3LOTokenProvider returns a [TokenProvider] based on the 3-legged OAuth2
+// configuration. The TokenProvider is caches and auto-refreshes tokens by
+// default.
+func New3LOTokenProvider(opts *Options3LO) (TokenProvider, error) {
+	if err := opts.validate(); err != nil {
+		return nil, err
+	}
+	if opts.AuthHandlerOpts != nil {
+		return new3LOTokenProviderWithAuthHandler(opts), nil
+	}
+	return NewCachedTokenProvider(&tokenProvider3LO{opts: opts, refreshToken: opts.RefreshToken, client: opts.client()}, &CachedTokenProviderOptions{
+		ExpireEarly: opts.EarlyTokenExpiry,
+	}), nil
+}
+
+// AuthorizationHandlerOptions provides a set of options to specify for doing a
+// 3-legged OAuth2 flow with a custom [AuthorizationHandler].
+type AuthorizationHandlerOptions struct {
+	// AuthorizationHandler specifies the handler used to for the authorization
+	// part of the flow.
+	Handler AuthorizationHandler
+	// State is used verify that the "state" is identical in the request and
+	// response before exchanging the auth code for OAuth2 token.
+	State string
+	// PKCEOpts allows setting configurations for PKCE. Optional.
+	PKCEOpts *PKCEOptions
+}
+
+func new3LOTokenProviderWithAuthHandler(opts *Options3LO) TokenProvider {
+	return NewCachedTokenProvider(&tokenProviderWithHandler{opts: opts, state: opts.AuthHandlerOpts.State}, &CachedTokenProviderOptions{
+		ExpireEarly: opts.EarlyTokenExpiry,
+	})
+}
+
+// exchange handles the final exchange portion of the 3lo flow. Returns a Token,
+// refreshToken, and error.
+func (o *Options3LO) exchange(ctx context.Context, code string) (*Token, string, error) {
+	// Build request
+	v := url.Values{
+		"grant_type": {"authorization_code"},
+		"code":       {code},
+	}
+	if o.RedirectURL != "" {
+		v.Set("redirect_uri", o.RedirectURL)
+	}
+	if o.AuthHandlerOpts != nil &&
+		o.AuthHandlerOpts.PKCEOpts != nil &&
+		o.AuthHandlerOpts.PKCEOpts.Verifier != "" {
+		v.Set(codeVerifierKey, o.AuthHandlerOpts.PKCEOpts.Verifier)
+	}
+	for k := range o.URLParams {
+		v.Set(k, o.URLParams.Get(k))
+	}
+	return fetchToken(ctx, o, v)
+}
+
+// This struct is not safe for concurrent access alone, but the way it is used
+// in this package by wrapping it with a cachedTokenProvider makes it so.
+type tokenProvider3LO struct {
+	opts         *Options3LO
+	client       *http.Client
+	refreshToken string
+}
+
+func (tp *tokenProvider3LO) Token(ctx context.Context) (*Token, error) {
+	if tp.refreshToken == "" {
+		return nil, errors.New("auth: token expired and refresh token is not set")
+	}
+	v := url.Values{
+		"grant_type":    {"refresh_token"},
+		"refresh_token": {tp.refreshToken},
+	}
+	for k := range tp.opts.URLParams {
+		v.Set(k, tp.opts.URLParams.Get(k))
+	}
+
+	tk, rt, err := fetchToken(ctx, tp.opts, v)
+	if err != nil {
+		return nil, err
+	}
+	if tp.refreshToken != rt && rt != "" {
+		tp.refreshToken = rt
+	}
+	return tk, err
+}
+
+type tokenProviderWithHandler struct {
+	opts  *Options3LO
+	state string
+}
+
+func (tp tokenProviderWithHandler) Token(ctx context.Context) (*Token, error) {
+	url := tp.opts.authCodeURL(tp.state, nil)
+	code, state, err := tp.opts.AuthHandlerOpts.Handler(url)
+	if err != nil {
+		return nil, err
+	}
+	if state != tp.state {
+		return nil, errors.New("auth: state mismatch in 3-legged-OAuth flow")
+	}
+	tok, _, err := tp.opts.exchange(ctx, code)
+	return tok, err
+}
+
+// fetchToken returns a Token, refresh token, and/or an error.
+func fetchToken(ctx context.Context, o *Options3LO, v url.Values) (*Token, string, error) {
+	var refreshToken string
+	if o.AuthStyle == StyleInParams {
+		if o.ClientID != "" {
+			v.Set("client_id", o.ClientID)
+		}
+		if o.ClientSecret != "" {
+			v.Set("client_secret", o.ClientSecret)
+		}
+	}
+	req, err := http.NewRequestWithContext(ctx, "POST", o.TokenURL, strings.NewReader(v.Encode()))
+	if err != nil {
+		return nil, refreshToken, err
+	}
+	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+	if o.AuthStyle == StyleInHeader {
+		req.SetBasicAuth(url.QueryEscape(o.ClientID), url.QueryEscape(o.ClientSecret))
+	}
+
+	// Make request
+	resp, body, err := internal.DoRequest(o.client(), req)
+	if err != nil {
+		return nil, refreshToken, err
+	}
+	failureStatus := resp.StatusCode < 200 || resp.StatusCode > 299
+	tokError := &Error{
+		Response: resp,
+		Body:     body,
+	}
+
+	var token *Token
+	// errors ignored because of default switch on content
+	content, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
+	switch content {
+	case "application/x-www-form-urlencoded", "text/plain":
+		// some endpoints return a query string
+		vals, err := url.ParseQuery(string(body))
+		if err != nil {
+			if failureStatus {
+				return nil, refreshToken, tokError
+			}
+			return nil, refreshToken, fmt.Errorf("auth: cannot parse response: %w", err)
+		}
+		tokError.code = vals.Get("error")
+		tokError.description = vals.Get("error_description")
+		tokError.uri = vals.Get("error_uri")
+		token = &Token{
+			Value:    vals.Get("access_token"),
+			Type:     vals.Get("token_type"),
+			Metadata: make(map[string]interface{}, len(vals)),
+		}
+		for k, v := range vals {
+			token.Metadata[k] = v
+		}
+		refreshToken = vals.Get("refresh_token")
+		e := vals.Get("expires_in")
+		expires, _ := strconv.Atoi(e)
+		if expires != 0 {
+			token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
+		}
+	default:
+		var tj tokenJSON
+		if err = json.Unmarshal(body, &tj); err != nil {
+			if failureStatus {
+				return nil, refreshToken, tokError
+			}
+			return nil, refreshToken, fmt.Errorf("auth: cannot parse json: %w", err)
+		}
+		tokError.code = tj.ErrorCode
+		tokError.description = tj.ErrorDescription
+		tokError.uri = tj.ErrorURI
+		token = &Token{
+			Value:    tj.AccessToken,
+			Type:     tj.TokenType,
+			Expiry:   tj.expiry(),
+			Metadata: make(map[string]interface{}),
+		}
+		json.Unmarshal(body, &token.Metadata) // optional field, skip err check
+		refreshToken = tj.RefreshToken
+	}
+	// according to spec, servers should respond status 400 in error case
+	// https://www.rfc-editor.org/rfc/rfc6749#section-5.2
+	// but some unorthodox servers respond 200 in error case
+	if failureStatus || tokError.code != "" {
+		return nil, refreshToken, tokError
+	}
+	if token.Value == "" {
+		return nil, refreshToken, errors.New("auth: server response missing access_token")
+	}
+	return token, refreshToken, nil
+}

+ 59 - 0
backend/vendor/cloud.google.com/go/compute/metadata/CHANGES.md

@@ -0,0 +1,59 @@
+# Changes
+
+## [0.5.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.1...compute/metadata/v0.5.2) (2024-09-20)
+
+
+### Bug Fixes
+
+* **compute/metadata:** Close Response Body for failed request ([#10891](https://github.com/googleapis/google-cloud-go/issues/10891)) ([e91d45e](https://github.com/googleapis/google-cloud-go/commit/e91d45e4757a9e354114509ba9800085d9e0ff1f))
+
+## [0.5.1](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.5.0...compute/metadata/v0.5.1) (2024-09-12)
+
+
+### Bug Fixes
+
+* **compute/metadata:** Check error chain for retryable error ([#10840](https://github.com/googleapis/google-cloud-go/issues/10840)) ([2bdedef](https://github.com/googleapis/google-cloud-go/commit/2bdedeff621b223d63cebc4355fcf83bc68412cd))
+
+## [0.5.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.4.0...compute/metadata/v0.5.0) (2024-07-10)
+
+
+### Features
+
+* **compute/metadata:** Add sys check for windows OnGCE ([#10521](https://github.com/googleapis/google-cloud-go/issues/10521)) ([3b9a830](https://github.com/googleapis/google-cloud-go/commit/3b9a83063960d2a2ac20beb47cc15818a68bd302))
+
+## [0.4.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.3.0...compute/metadata/v0.4.0) (2024-07-01)
+
+
+### Features
+
+* **compute/metadata:** Add context for all functions/methods ([#10370](https://github.com/googleapis/google-cloud-go/issues/10370)) ([66b8efe](https://github.com/googleapis/google-cloud-go/commit/66b8efe7ad877e052b2987bb4475477e38c67bb3))
+
+
+### Documentation
+
+* **compute/metadata:** Update OnGCE description ([#10408](https://github.com/googleapis/google-cloud-go/issues/10408)) ([6a46dca](https://github.com/googleapis/google-cloud-go/commit/6a46dca4eae4f88ec6f88822e01e5bf8aeca787f))
+
+## [0.3.0](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.3...compute/metadata/v0.3.0) (2024-04-15)
+
+
+### Features
+
+* **compute/metadata:** Add context aware functions  ([#9733](https://github.com/googleapis/google-cloud-go/issues/9733)) ([e4eb5b4](https://github.com/googleapis/google-cloud-go/commit/e4eb5b46ee2aec9d2fc18300bfd66015e25a0510))
+
+## [0.2.3](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.2...compute/metadata/v0.2.3) (2022-12-15)
+
+
+### Bug Fixes
+
+* **compute/metadata:** Switch DNS lookup to an absolute lookup ([119b410](https://github.com/googleapis/google-cloud-go/commit/119b41060c7895e45e48aee5621ad35607c4d021)), refs [#7165](https://github.com/googleapis/google-cloud-go/issues/7165)
+
+## [0.2.2](https://github.com/googleapis/google-cloud-go/compare/compute/metadata/v0.2.1...compute/metadata/v0.2.2) (2022-12-01)
+
+
+### Bug Fixes
+
+* **compute/metadata:** Set IdleConnTimeout for http.Client ([#7084](https://github.com/googleapis/google-cloud-go/issues/7084)) ([766516a](https://github.com/googleapis/google-cloud-go/commit/766516aaf3816bfb3159efeea65aa3d1d205a3e2)), refs [#5430](https://github.com/googleapis/google-cloud-go/issues/5430)
+
+## [0.1.0] (2022-10-26)
+
+Initial release of metadata being it's own module.

+ 202 - 0
backend/vendor/cloud.google.com/go/compute/metadata/LICENSE

@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 27 - 0
backend/vendor/cloud.google.com/go/compute/metadata/README.md

@@ -0,0 +1,27 @@
+# Compute API
+
+[![Go Reference](https://pkg.go.dev/badge/cloud.google.com/go/compute.svg)](https://pkg.go.dev/cloud.google.com/go/compute/metadata)
+
+This is a utility library for communicating with Google Cloud metadata service
+on Google Cloud.
+
+## Install
+
+```bash
+go get cloud.google.com/go/compute/metadata
+```
+
+## Go Version Support
+
+See the [Go Versions Supported](https://github.com/googleapis/google-cloud-go#go-versions-supported)
+section in the root directory's README.
+
+## Contributing
+
+Contributions are welcome. Please, see the [CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md)
+document for details.
+
+Please note that this project is released with a Contributor Code of Conduct.
+By participating in this project you agree to abide by its terms. See
+[Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/main/CONTRIBUTING.md#contributor-code-of-conduct)
+for more information.

+ 839 - 0
backend/vendor/cloud.google.com/go/compute/metadata/metadata.go

@@ -0,0 +1,839 @@
+// Copyright 2014 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package metadata provides access to Google Compute Engine (GCE)
+// metadata and API service accounts.
+//
+// This package is a wrapper around the GCE metadata service,
+// as documented at https://cloud.google.com/compute/docs/metadata/overview.
+package metadata // import "cloud.google.com/go/compute/metadata"
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	// metadataIP is the documented metadata server IP address.
+	metadataIP = "169.254.169.254"
+
+	// metadataHostEnv is the environment variable specifying the
+	// GCE metadata hostname.  If empty, the default value of
+	// metadataIP ("169.254.169.254") is used instead.
+	// This is variable name is not defined by any spec, as far as
+	// I know; it was made up for the Go package.
+	metadataHostEnv = "GCE_METADATA_HOST"
+
+	userAgent = "gcloud-golang/0.1"
+)
+
+type cachedValue struct {
+	k    string
+	trim bool
+	mu   sync.Mutex
+	v    string
+}
+
+var (
+	projID  = &cachedValue{k: "project/project-id", trim: true}
+	projNum = &cachedValue{k: "project/numeric-project-id", trim: true}
+	instID  = &cachedValue{k: "instance/id", trim: true}
+)
+
+var defaultClient = &Client{hc: newDefaultHTTPClient()}
+
+func newDefaultHTTPClient() *http.Client {
+	return &http.Client{
+		Transport: &http.Transport{
+			Dial: (&net.Dialer{
+				Timeout:   2 * time.Second,
+				KeepAlive: 30 * time.Second,
+			}).Dial,
+			IdleConnTimeout: 60 * time.Second,
+		},
+		Timeout: 5 * time.Second,
+	}
+}
+
+// NotDefinedError is returned when requested metadata is not defined.
+//
+// The underlying string is the suffix after "/computeMetadata/v1/".
+//
+// This error is not returned if the value is defined to be the empty
+// string.
+type NotDefinedError string
+
+func (suffix NotDefinedError) Error() string {
+	return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix))
+}
+
+func (c *cachedValue) get(ctx context.Context, cl *Client) (v string, err error) {
+	defer c.mu.Unlock()
+	c.mu.Lock()
+	if c.v != "" {
+		return c.v, nil
+	}
+	if c.trim {
+		v, err = cl.getTrimmed(ctx, c.k)
+	} else {
+		v, err = cl.GetWithContext(ctx, c.k)
+	}
+	if err == nil {
+		c.v = v
+	}
+	return
+}
+
+var (
+	onGCEOnce sync.Once
+	onGCE     bool
+)
+
+// OnGCE reports whether this process is running on Google Compute Platforms.
+// NOTE: True returned from `OnGCE` does not guarantee that the metadata server
+// is accessible from this process and have all the metadata defined.
+func OnGCE() bool {
+	onGCEOnce.Do(initOnGCE)
+	return onGCE
+}
+
+func initOnGCE() {
+	onGCE = testOnGCE()
+}
+
+func testOnGCE() bool {
+	// The user explicitly said they're on GCE, so trust them.
+	if os.Getenv(metadataHostEnv) != "" {
+		return true
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	resc := make(chan bool, 2)
+
+	// Try two strategies in parallel.
+	// See https://github.com/googleapis/google-cloud-go/issues/194
+	go func() {
+		req, _ := http.NewRequest("GET", "http://"+metadataIP, nil)
+		req.Header.Set("User-Agent", userAgent)
+		res, err := newDefaultHTTPClient().Do(req.WithContext(ctx))
+		if err != nil {
+			resc <- false
+			return
+		}
+		defer res.Body.Close()
+		resc <- res.Header.Get("Metadata-Flavor") == "Google"
+	}()
+
+	go func() {
+		resolver := &net.Resolver{}
+		addrs, err := resolver.LookupHost(ctx, "metadata.google.internal.")
+		if err != nil || len(addrs) == 0 {
+			resc <- false
+			return
+		}
+		resc <- strsContains(addrs, metadataIP)
+	}()
+
+	tryHarder := systemInfoSuggestsGCE()
+	if tryHarder {
+		res := <-resc
+		if res {
+			// The first strategy succeeded, so let's use it.
+			return true
+		}
+		// Wait for either the DNS or metadata server probe to
+		// contradict the other one and say we are running on
+		// GCE. Give it a lot of time to do so, since the system
+		// info already suggests we're running on a GCE BIOS.
+		timer := time.NewTimer(5 * time.Second)
+		defer timer.Stop()
+		select {
+		case res = <-resc:
+			return res
+		case <-timer.C:
+			// Too slow. Who knows what this system is.
+			return false
+		}
+	}
+
+	// There's no hint from the system info that we're running on
+	// GCE, so use the first probe's result as truth, whether it's
+	// true or false. The goal here is to optimize for speed for
+	// users who are NOT running on GCE. We can't assume that
+	// either a DNS lookup or an HTTP request to a blackholed IP
+	// address is fast. Worst case this should return when the
+	// metaClient's Transport.ResponseHeaderTimeout or
+	// Transport.Dial.Timeout fires (in two seconds).
+	return <-resc
+}
+
+// Subscribe calls Client.SubscribeWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [SubscribeWithContext].
+func Subscribe(suffix string, fn func(v string, ok bool) error) error {
+	return defaultClient.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
+}
+
+// SubscribeWithContext calls Client.SubscribeWithContext on the default client.
+func SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
+	return defaultClient.SubscribeWithContext(ctx, suffix, fn)
+}
+
+// Get calls Client.GetWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [GetWithContext].
+func Get(suffix string) (string, error) {
+	return defaultClient.GetWithContext(context.Background(), suffix)
+}
+
+// GetWithContext calls Client.GetWithContext on the default client.
+func GetWithContext(ctx context.Context, suffix string) (string, error) {
+	return defaultClient.GetWithContext(ctx, suffix)
+}
+
+// ProjectID returns the current instance's project ID string.
+//
+// Deprecated: Please use the context aware variant [ProjectIDWithContext].
+func ProjectID() (string, error) {
+	return defaultClient.ProjectIDWithContext(context.Background())
+}
+
+// ProjectIDWithContext returns the current instance's project ID string.
+func ProjectIDWithContext(ctx context.Context) (string, error) {
+	return defaultClient.ProjectIDWithContext(ctx)
+}
+
+// NumericProjectID returns the current instance's numeric project ID.
+//
+// Deprecated: Please use the context aware variant [NumericProjectIDWithContext].
+func NumericProjectID() (string, error) {
+	return defaultClient.NumericProjectIDWithContext(context.Background())
+}
+
+// NumericProjectIDWithContext returns the current instance's numeric project ID.
+func NumericProjectIDWithContext(ctx context.Context) (string, error) {
+	return defaultClient.NumericProjectIDWithContext(ctx)
+}
+
+// InternalIP returns the instance's primary internal IP address.
+//
+// Deprecated: Please use the context aware variant [InternalIPWithContext].
+func InternalIP() (string, error) {
+	return defaultClient.InternalIPWithContext(context.Background())
+}
+
+// InternalIPWithContext returns the instance's primary internal IP address.
+func InternalIPWithContext(ctx context.Context) (string, error) {
+	return defaultClient.InternalIPWithContext(ctx)
+}
+
+// ExternalIP returns the instance's primary external (public) IP address.
+//
+// Deprecated: Please use the context aware variant [ExternalIPWithContext].
+func ExternalIP() (string, error) {
+	return defaultClient.ExternalIPWithContext(context.Background())
+}
+
+// ExternalIPWithContext returns the instance's primary external (public) IP address.
+func ExternalIPWithContext(ctx context.Context) (string, error) {
+	return defaultClient.ExternalIPWithContext(ctx)
+}
+
+// Email calls Client.EmailWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [EmailWithContext].
+func Email(serviceAccount string) (string, error) {
+	return defaultClient.EmailWithContext(context.Background(), serviceAccount)
+}
+
+// EmailWithContext calls Client.EmailWithContext on the default client.
+func EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
+	return defaultClient.EmailWithContext(ctx, serviceAccount)
+}
+
+// Hostname returns the instance's hostname. This will be of the form
+// "<instanceID>.c.<projID>.internal".
+//
+// Deprecated: Please use the context aware variant [HostnameWithContext].
+func Hostname() (string, error) {
+	return defaultClient.HostnameWithContext(context.Background())
+}
+
+// HostnameWithContext returns the instance's hostname. This will be of the form
+// "<instanceID>.c.<projID>.internal".
+func HostnameWithContext(ctx context.Context) (string, error) {
+	return defaultClient.HostnameWithContext(ctx)
+}
+
+// InstanceTags returns the list of user-defined instance tags,
+// assigned when initially creating a GCE instance.
+//
+// Deprecated: Please use the context aware variant [InstanceTagsWithContext].
+func InstanceTags() ([]string, error) {
+	return defaultClient.InstanceTagsWithContext(context.Background())
+}
+
+// InstanceTagsWithContext returns the list of user-defined instance tags,
+// assigned when initially creating a GCE instance.
+func InstanceTagsWithContext(ctx context.Context) ([]string, error) {
+	return defaultClient.InstanceTagsWithContext(ctx)
+}
+
+// InstanceID returns the current VM's numeric instance ID.
+//
+// Deprecated: Please use the context aware variant [InstanceIDWithContext].
+func InstanceID() (string, error) {
+	return defaultClient.InstanceIDWithContext(context.Background())
+}
+
+// InstanceIDWithContext returns the current VM's numeric instance ID.
+func InstanceIDWithContext(ctx context.Context) (string, error) {
+	return defaultClient.InstanceIDWithContext(ctx)
+}
+
+// InstanceName returns the current VM's instance ID string.
+//
+// Deprecated: Please use the context aware variant [InstanceNameWithContext].
+func InstanceName() (string, error) {
+	return defaultClient.InstanceNameWithContext(context.Background())
+}
+
+// InstanceNameWithContext returns the current VM's instance ID string.
+func InstanceNameWithContext(ctx context.Context) (string, error) {
+	return defaultClient.InstanceNameWithContext(ctx)
+}
+
+// Zone returns the current VM's zone, such as "us-central1-b".
+//
+// Deprecated: Please use the context aware variant [ZoneWithContext].
+func Zone() (string, error) {
+	return defaultClient.ZoneWithContext(context.Background())
+}
+
+// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
+func ZoneWithContext(ctx context.Context) (string, error) {
+	return defaultClient.ZoneWithContext(ctx)
+}
+
+// InstanceAttributes calls Client.InstanceAttributesWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [InstanceAttributesWithContext.
+func InstanceAttributes() ([]string, error) {
+	return defaultClient.InstanceAttributesWithContext(context.Background())
+}
+
+// InstanceAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
+func InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
+	return defaultClient.InstanceAttributesWithContext(ctx)
+}
+
+// ProjectAttributes calls Client.ProjectAttributesWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [ProjectAttributesWithContext].
+func ProjectAttributes() ([]string, error) {
+	return defaultClient.ProjectAttributesWithContext(context.Background())
+}
+
+// ProjectAttributesWithContext calls Client.ProjectAttributesWithContext on the default client.
+func ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
+	return defaultClient.ProjectAttributesWithContext(ctx)
+}
+
+// InstanceAttributeValue calls Client.InstanceAttributeValueWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [InstanceAttributeValueWithContext].
+func InstanceAttributeValue(attr string) (string, error) {
+	return defaultClient.InstanceAttributeValueWithContext(context.Background(), attr)
+}
+
+// InstanceAttributeValueWithContext calls Client.InstanceAttributeValueWithContext on the default client.
+func InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
+	return defaultClient.InstanceAttributeValueWithContext(ctx, attr)
+}
+
+// ProjectAttributeValue calls Client.ProjectAttributeValueWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [ProjectAttributeValueWithContext].
+func ProjectAttributeValue(attr string) (string, error) {
+	return defaultClient.ProjectAttributeValueWithContext(context.Background(), attr)
+}
+
+// ProjectAttributeValueWithContext calls Client.ProjectAttributeValueWithContext on the default client.
+func ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
+	return defaultClient.ProjectAttributeValueWithContext(ctx, attr)
+}
+
+// Scopes calls Client.ScopesWithContext on the default client.
+//
+// Deprecated: Please use the context aware variant [ScopesWithContext].
+func Scopes(serviceAccount string) ([]string, error) {
+	return defaultClient.ScopesWithContext(context.Background(), serviceAccount)
+}
+
+// ScopesWithContext calls Client.ScopesWithContext on the default client.
+func ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
+	return defaultClient.ScopesWithContext(ctx, serviceAccount)
+}
+
+func strsContains(ss []string, s string) bool {
+	for _, v := range ss {
+		if v == s {
+			return true
+		}
+	}
+	return false
+}
+
+// A Client provides metadata.
+type Client struct {
+	hc *http.Client
+}
+
+// NewClient returns a Client that can be used to fetch metadata.
+// Returns the client that uses the specified http.Client for HTTP requests.
+// If nil is specified, returns the default client.
+func NewClient(c *http.Client) *Client {
+	if c == nil {
+		return defaultClient
+	}
+	return &Client{hc: c}
+}
+
+// getETag returns a value from the metadata service as well as the associated ETag.
+// This func is otherwise equivalent to Get.
+func (c *Client) getETag(ctx context.Context, suffix string) (value, etag string, err error) {
+	// Using a fixed IP makes it very difficult to spoof the metadata service in
+	// a container, which is an important use-case for local testing of cloud
+	// deployments. To enable spoofing of the metadata service, the environment
+	// variable GCE_METADATA_HOST is first inspected to decide where metadata
+	// requests shall go.
+	host := os.Getenv(metadataHostEnv)
+	if host == "" {
+		// Using 169.254.169.254 instead of "metadata" here because Go
+		// binaries built with the "netgo" tag and without cgo won't
+		// know the search suffix for "metadata" is
+		// ".google.internal", and this IP address is documented as
+		// being stable anyway.
+		host = metadataIP
+	}
+	suffix = strings.TrimLeft(suffix, "/")
+	u := "http://" + host + "/computeMetadata/v1/" + suffix
+	req, err := http.NewRequestWithContext(ctx, "GET", u, nil)
+	if err != nil {
+		return "", "", err
+	}
+	req.Header.Set("Metadata-Flavor", "Google")
+	req.Header.Set("User-Agent", userAgent)
+	var res *http.Response
+	var reqErr error
+	retryer := newRetryer()
+	for {
+		res, reqErr = c.hc.Do(req)
+		var code int
+		if res != nil {
+			code = res.StatusCode
+		}
+		if delay, shouldRetry := retryer.Retry(code, reqErr); shouldRetry {
+			if res != nil && res.Body != nil {
+				res.Body.Close()
+			}
+			if err := sleep(ctx, delay); err != nil {
+				return "", "", err
+			}
+			continue
+		}
+		break
+	}
+	if reqErr != nil {
+		return "", "", reqErr
+	}
+	defer res.Body.Close()
+	if res.StatusCode == http.StatusNotFound {
+		return "", "", NotDefinedError(suffix)
+	}
+	all, err := io.ReadAll(res.Body)
+	if err != nil {
+		return "", "", err
+	}
+	if res.StatusCode != 200 {
+		return "", "", &Error{Code: res.StatusCode, Message: string(all)}
+	}
+	return string(all), res.Header.Get("Etag"), nil
+}
+
+// Get returns a value from the metadata service.
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
+//
+// If the GCE_METADATA_HOST environment variable is not defined, a default of
+// 169.254.169.254 will be used instead.
+//
+// If the requested metadata is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// Deprecated: Please use the context aware variant [Client.GetWithContext].
+func (c *Client) Get(suffix string) (string, error) {
+	return c.GetWithContext(context.Background(), suffix)
+}
+
+// GetWithContext returns a value from the metadata service.
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
+//
+// If the GCE_METADATA_HOST environment variable is not defined, a default of
+// 169.254.169.254 will be used instead.
+//
+// If the requested metadata is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// NOTE: Without an extra deadline in the context this call can take in the
+// worst case, with internal backoff retries, up to 15 seconds (e.g. when server
+// is responding slowly). Pass context with additional timeouts when needed.
+func (c *Client) GetWithContext(ctx context.Context, suffix string) (string, error) {
+	val, _, err := c.getETag(ctx, suffix)
+	return val, err
+}
+
+func (c *Client) getTrimmed(ctx context.Context, suffix string) (s string, err error) {
+	s, err = c.GetWithContext(ctx, suffix)
+	s = strings.TrimSpace(s)
+	return
+}
+
+func (c *Client) lines(ctx context.Context, suffix string) ([]string, error) {
+	j, err := c.GetWithContext(ctx, suffix)
+	if err != nil {
+		return nil, err
+	}
+	s := strings.Split(strings.TrimSpace(j), "\n")
+	for i := range s {
+		s[i] = strings.TrimSpace(s[i])
+	}
+	return s, nil
+}
+
+// ProjectID returns the current instance's project ID string.
+//
+// Deprecated: Please use the context aware variant [Client.ProjectIDWithContext].
+func (c *Client) ProjectID() (string, error) { return c.ProjectIDWithContext(context.Background()) }
+
+// ProjectIDWithContext returns the current instance's project ID string.
+func (c *Client) ProjectIDWithContext(ctx context.Context) (string, error) { return projID.get(ctx, c) }
+
+// NumericProjectID returns the current instance's numeric project ID.
+//
+// Deprecated: Please use the context aware variant [Client.NumericProjectIDWithContext].
+func (c *Client) NumericProjectID() (string, error) {
+	return c.NumericProjectIDWithContext(context.Background())
+}
+
+// NumericProjectIDWithContext returns the current instance's numeric project ID.
+func (c *Client) NumericProjectIDWithContext(ctx context.Context) (string, error) {
+	return projNum.get(ctx, c)
+}
+
+// InstanceID returns the current VM's numeric instance ID.
+//
+// Deprecated: Please use the context aware variant [Client.InstanceIDWithContext].
+func (c *Client) InstanceID() (string, error) {
+	return c.InstanceIDWithContext(context.Background())
+}
+
+// InstanceIDWithContext returns the current VM's numeric instance ID.
+func (c *Client) InstanceIDWithContext(ctx context.Context) (string, error) {
+	return instID.get(ctx, c)
+}
+
+// InternalIP returns the instance's primary internal IP address.
+//
+// Deprecated: Please use the context aware variant [Client.InternalIPWithContext].
+func (c *Client) InternalIP() (string, error) {
+	return c.InternalIPWithContext(context.Background())
+}
+
+// InternalIPWithContext returns the instance's primary internal IP address.
+func (c *Client) InternalIPWithContext(ctx context.Context) (string, error) {
+	return c.getTrimmed(ctx, "instance/network-interfaces/0/ip")
+}
+
+// Email returns the email address associated with the service account.
+//
+// Deprecated: Please use the context aware variant [Client.EmailWithContext].
+func (c *Client) Email(serviceAccount string) (string, error) {
+	return c.EmailWithContext(context.Background(), serviceAccount)
+}
+
+// EmailWithContext returns the email address associated with the service account.
+// The serviceAccount parameter default value (empty string or "default" value)
+// will use the instance's main account.
+func (c *Client) EmailWithContext(ctx context.Context, serviceAccount string) (string, error) {
+	if serviceAccount == "" {
+		serviceAccount = "default"
+	}
+	return c.getTrimmed(ctx, "instance/service-accounts/"+serviceAccount+"/email")
+}
+
+// ExternalIP returns the instance's primary external (public) IP address.
+//
+// Deprecated: Please use the context aware variant [Client.ExternalIPWithContext].
+func (c *Client) ExternalIP() (string, error) {
+	return c.ExternalIPWithContext(context.Background())
+}
+
+// ExternalIPWithContext returns the instance's primary external (public) IP address.
+func (c *Client) ExternalIPWithContext(ctx context.Context) (string, error) {
+	return c.getTrimmed(ctx, "instance/network-interfaces/0/access-configs/0/external-ip")
+}
+
+// Hostname returns the instance's hostname. This will be of the form
+// "<instanceID>.c.<projID>.internal".
+//
+// Deprecated: Please use the context aware variant [Client.HostnameWithContext].
+func (c *Client) Hostname() (string, error) {
+	return c.HostnameWithContext(context.Background())
+}
+
+// HostnameWithContext returns the instance's hostname. This will be of the form
+// "<instanceID>.c.<projID>.internal".
+func (c *Client) HostnameWithContext(ctx context.Context) (string, error) {
+	return c.getTrimmed(ctx, "instance/hostname")
+}
+
+// InstanceTags returns the list of user-defined instance tags.
+//
+// Deprecated: Please use the context aware variant [Client.InstanceTagsWithContext].
+func (c *Client) InstanceTags() ([]string, error) {
+	return c.InstanceTagsWithContext(context.Background())
+}
+
+// InstanceTagsWithContext returns the list of user-defined instance tags,
+// assigned when initially creating a GCE instance.
+func (c *Client) InstanceTagsWithContext(ctx context.Context) ([]string, error) {
+	var s []string
+	j, err := c.GetWithContext(ctx, "instance/tags")
+	if err != nil {
+		return nil, err
+	}
+	if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil {
+		return nil, err
+	}
+	return s, nil
+}
+
+// InstanceName returns the current VM's instance ID string.
+//
+// Deprecated: Please use the context aware variant [Client.InstanceNameWithContext].
+func (c *Client) InstanceName() (string, error) {
+	return c.InstanceNameWithContext(context.Background())
+}
+
+// InstanceNameWithContext returns the current VM's instance ID string.
+func (c *Client) InstanceNameWithContext(ctx context.Context) (string, error) {
+	return c.getTrimmed(ctx, "instance/name")
+}
+
+// Zone returns the current VM's zone, such as "us-central1-b".
+//
+// Deprecated: Please use the context aware variant [Client.ZoneWithContext].
+func (c *Client) Zone() (string, error) {
+	return c.ZoneWithContext(context.Background())
+}
+
+// ZoneWithContext returns the current VM's zone, such as "us-central1-b".
+func (c *Client) ZoneWithContext(ctx context.Context) (string, error) {
+	zone, err := c.getTrimmed(ctx, "instance/zone")
+	// zone is of the form "projects/<projNum>/zones/<zoneName>".
+	if err != nil {
+		return "", err
+	}
+	return zone[strings.LastIndex(zone, "/")+1:], nil
+}
+
+// InstanceAttributes returns the list of user-defined attributes,
+// assigned when initially creating a GCE VM instance. The value of an
+// attribute can be obtained with InstanceAttributeValue.
+//
+// Deprecated: Please use the context aware variant [Client.InstanceAttributesWithContext].
+func (c *Client) InstanceAttributes() ([]string, error) {
+	return c.InstanceAttributesWithContext(context.Background())
+}
+
+// InstanceAttributesWithContext returns the list of user-defined attributes,
+// assigned when initially creating a GCE VM instance. The value of an
+// attribute can be obtained with InstanceAttributeValue.
+func (c *Client) InstanceAttributesWithContext(ctx context.Context) ([]string, error) {
+	return c.lines(ctx, "instance/attributes/")
+}
+
+// ProjectAttributes returns the list of user-defined attributes
+// applying to the project as a whole, not just this VM.  The value of
+// an attribute can be obtained with ProjectAttributeValue.
+//
+// Deprecated: Please use the context aware variant [Client.ProjectAttributesWithContext].
+func (c *Client) ProjectAttributes() ([]string, error) {
+	return c.ProjectAttributesWithContext(context.Background())
+}
+
+// ProjectAttributesWithContext returns the list of user-defined attributes
+// applying to the project as a whole, not just this VM.  The value of
+// an attribute can be obtained with ProjectAttributeValue.
+func (c *Client) ProjectAttributesWithContext(ctx context.Context) ([]string, error) {
+	return c.lines(ctx, "project/attributes/")
+}
+
+// InstanceAttributeValue returns the value of the provided VM
+// instance attribute.
+//
+// If the requested attribute is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// InstanceAttributeValue may return ("", nil) if the attribute was
+// defined to be the empty string.
+//
+// Deprecated: Please use the context aware variant [Client.InstanceAttributeValueWithContext].
+func (c *Client) InstanceAttributeValue(attr string) (string, error) {
+	return c.InstanceAttributeValueWithContext(context.Background(), attr)
+}
+
+// InstanceAttributeValueWithContext returns the value of the provided VM
+// instance attribute.
+//
+// If the requested attribute is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// InstanceAttributeValue may return ("", nil) if the attribute was
+// defined to be the empty string.
+func (c *Client) InstanceAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
+	return c.GetWithContext(ctx, "instance/attributes/"+attr)
+}
+
+// ProjectAttributeValue returns the value of the provided
+// project attribute.
+//
+// If the requested attribute is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// ProjectAttributeValue may return ("", nil) if the attribute was
+// defined to be the empty string.
+//
+// Deprecated: Please use the context aware variant [Client.ProjectAttributeValueWithContext].
+func (c *Client) ProjectAttributeValue(attr string) (string, error) {
+	return c.ProjectAttributeValueWithContext(context.Background(), attr)
+}
+
+// ProjectAttributeValueWithContext returns the value of the provided
+// project attribute.
+//
+// If the requested attribute is not defined, the returned error will
+// be of type NotDefinedError.
+//
+// ProjectAttributeValue may return ("", nil) if the attribute was
+// defined to be the empty string.
+func (c *Client) ProjectAttributeValueWithContext(ctx context.Context, attr string) (string, error) {
+	return c.GetWithContext(ctx, "project/attributes/"+attr)
+}
+
+// Scopes returns the service account scopes for the given account.
+// The account may be empty or the string "default" to use the instance's
+// main account.
+//
+// Deprecated: Please use the context aware variant [Client.ScopesWithContext].
+func (c *Client) Scopes(serviceAccount string) ([]string, error) {
+	return c.ScopesWithContext(context.Background(), serviceAccount)
+}
+
+// ScopesWithContext returns the service account scopes for the given account.
+// The account may be empty or the string "default" to use the instance's
+// main account.
+func (c *Client) ScopesWithContext(ctx context.Context, serviceAccount string) ([]string, error) {
+	if serviceAccount == "" {
+		serviceAccount = "default"
+	}
+	return c.lines(ctx, "instance/service-accounts/"+serviceAccount+"/scopes")
+}
+
+// Subscribe subscribes to a value from the metadata service.
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
+// The suffix may contain query parameters.
+//
+// Deprecated: Please use the context aware variant [Client.SubscribeWithContext].
+func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error {
+	return c.SubscribeWithContext(context.Background(), suffix, func(ctx context.Context, v string, ok bool) error { return fn(v, ok) })
+}
+
+// SubscribeWithContext subscribes to a value from the metadata service.
+// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
+// The suffix may contain query parameters.
+//
+// SubscribeWithContext calls fn with the latest metadata value indicated by the
+// provided suffix. If the metadata value is deleted, fn is called with the
+// empty string and ok false. Subscribe blocks until fn returns a non-nil error
+// or the value is deleted. Subscribe returns the error value returned from the
+// last call to fn, which may be nil when ok == false.
+func (c *Client) SubscribeWithContext(ctx context.Context, suffix string, fn func(ctx context.Context, v string, ok bool) error) error {
+	const failedSubscribeSleep = time.Second * 5
+
+	// First check to see if the metadata value exists at all.
+	val, lastETag, err := c.getETag(ctx, suffix)
+	if err != nil {
+		return err
+	}
+
+	if err := fn(ctx, val, true); err != nil {
+		return err
+	}
+
+	ok := true
+	if strings.ContainsRune(suffix, '?') {
+		suffix += "&wait_for_change=true&last_etag="
+	} else {
+		suffix += "?wait_for_change=true&last_etag="
+	}
+	for {
+		val, etag, err := c.getETag(ctx, suffix+url.QueryEscape(lastETag))
+		if err != nil {
+			if _, deleted := err.(NotDefinedError); !deleted {
+				time.Sleep(failedSubscribeSleep)
+				continue // Retry on other errors.
+			}
+			ok = false
+		}
+		lastETag = etag
+
+		if err := fn(ctx, val, ok); err != nil || !ok {
+			return err
+		}
+	}
+}
+
+// Error contains an error response from the server.
+type Error struct {
+	// Code is the HTTP response status code.
+	Code int
+	// Message is the server response message.
+	Message string
+}
+
+func (e *Error) Error() string {
+	return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message)
+}

+ 114 - 0
backend/vendor/cloud.google.com/go/compute/metadata/retry.go

@@ -0,0 +1,114 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package metadata
+
+import (
+	"context"
+	"io"
+	"math/rand"
+	"net/http"
+	"time"
+)
+
+const (
+	maxRetryAttempts = 5
+)
+
+var (
+	syscallRetryable = func(error) bool { return false }
+)
+
+// defaultBackoff is basically equivalent to gax.Backoff without the need for
+// the dependency.
+type defaultBackoff struct {
+	max time.Duration
+	mul float64
+	cur time.Duration
+}
+
+func (b *defaultBackoff) Pause() time.Duration {
+	d := time.Duration(1 + rand.Int63n(int64(b.cur)))
+	b.cur = time.Duration(float64(b.cur) * b.mul)
+	if b.cur > b.max {
+		b.cur = b.max
+	}
+	return d
+}
+
+// sleep is the equivalent of gax.Sleep without the need for the dependency.
+func sleep(ctx context.Context, d time.Duration) error {
+	t := time.NewTimer(d)
+	select {
+	case <-ctx.Done():
+		t.Stop()
+		return ctx.Err()
+	case <-t.C:
+		return nil
+	}
+}
+
+func newRetryer() *metadataRetryer {
+	return &metadataRetryer{bo: &defaultBackoff{
+		cur: 100 * time.Millisecond,
+		max: 30 * time.Second,
+		mul: 2,
+	}}
+}
+
+type backoff interface {
+	Pause() time.Duration
+}
+
+type metadataRetryer struct {
+	bo       backoff
+	attempts int
+}
+
+func (r *metadataRetryer) Retry(status int, err error) (time.Duration, bool) {
+	if status == http.StatusOK {
+		return 0, false
+	}
+	retryOk := shouldRetry(status, err)
+	if !retryOk {
+		return 0, false
+	}
+	if r.attempts == maxRetryAttempts {
+		return 0, false
+	}
+	r.attempts++
+	return r.bo.Pause(), true
+}
+
+func shouldRetry(status int, err error) bool {
+	if 500 <= status && status <= 599 {
+		return true
+	}
+	if err == io.ErrUnexpectedEOF {
+		return true
+	}
+	// Transient network errors should be retried.
+	if syscallRetryable(err) {
+		return true
+	}
+	if err, ok := err.(interface{ Temporary() bool }); ok {
+		if err.Temporary() {
+			return true
+		}
+	}
+	if err, ok := err.(interface{ Unwrap() error }); ok {
+		return shouldRetry(status, err.Unwrap())
+	}
+	return false
+}

+ 31 - 0
backend/vendor/cloud.google.com/go/compute/metadata/retry_linux.go

@@ -0,0 +1,31 @@
+// Copyright 2021 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build linux
+// +build linux
+
+package metadata
+
+import (
+	"errors"
+	"syscall"
+)
+
+func init() {
+	// Initialize syscallRetryable to return true on transient socket-level
+	// errors. These errors are specific to Linux.
+	syscallRetryable = func(err error) bool {
+		return errors.Is(err, syscall.ECONNRESET) || errors.Is(err, syscall.ECONNREFUSED)
+	}
+}

+ 26 - 0
backend/vendor/cloud.google.com/go/compute/metadata/syscheck.go

@@ -0,0 +1,26 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build !windows && !linux
+
+package metadata
+
+// systemInfoSuggestsGCE reports whether the local system (without
+// doing network requests) suggests that we're running on GCE. If this
+// returns true, testOnGCE tries a bit harder to reach its metadata
+// server.
+func systemInfoSuggestsGCE() bool {
+	// We don't currently have checks for other GOOS
+	return false
+}

+ 28 - 0
backend/vendor/cloud.google.com/go/compute/metadata/syscheck_linux.go

@@ -0,0 +1,28 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build linux
+
+package metadata
+
+import (
+	"os"
+	"strings"
+)
+
+func systemInfoSuggestsGCE() bool {
+	b, _ := os.ReadFile("/sys/class/dmi/id/product_name")
+	name := strings.TrimSpace(string(b))
+	return name == "Google" || name == "Google Compute Engine"
+}

+ 38 - 0
backend/vendor/cloud.google.com/go/compute/metadata/syscheck_windows.go

@@ -0,0 +1,38 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//go:build windows
+
+package metadata
+
+import (
+	"strings"
+
+	"golang.org/x/sys/windows/registry"
+)
+
+func systemInfoSuggestsGCE() bool {
+	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\HardwareConfig\Current`, registry.QUERY_VALUE)
+	if err != nil {
+		return false
+	}
+	defer k.Close()
+
+	s, _, err := k.GetStringValue("SystemProductName")
+	if err != nil {
+		return false
+	}
+	s = strings.TrimSpace(s)
+	return strings.HasPrefix(s, "Google")
+}

+ 3 - 0
backend/vendor/github.com/a-h/templ/.dockerignore

@@ -0,0 +1,3 @@
+.git
+Dockerfile
+.dockerignore

+ 1 - 0
backend/vendor/github.com/a-h/templ/.envrc

@@ -0,0 +1 @@
+use flake

+ 31 - 0
backend/vendor/github.com/a-h/templ/.gitignore

@@ -0,0 +1,31 @@
+# Output.
+cmd/templ/templ
+
+# Logs.
+cmd/templ/lspcmd/*log.txt
+
+# Go code coverage.
+coverage.out
+coverage
+
+# Mac filesystem jank.
+.DS_Store
+
+# Docusaurus.
+docs/build/
+docs/resources/_gen/
+node_modules/
+dist/
+
+# Nix artifacts.
+result
+
+# Editors
+## nvim
+.null-ls*
+
+# Go workspace.
+go.work
+
+# direnv
+.direnv

+ 72 - 0
backend/vendor/github.com/a-h/templ/.goreleaser.yaml

@@ -0,0 +1,72 @@
+builds:
+  - env:
+      - CGO_ENABLED=0
+    dir: cmd/templ
+    mod_timestamp: '{{ .CommitTimestamp }}'
+    flags:
+      - -trimpath
+    ldflags:
+      - -s -w
+    goos:
+      - linux
+      - windows
+      - darwin
+
+checksum:
+  name_template: 'checksums.txt'
+
+signs:
+  - id: checksums
+    cmd: cosign
+    stdin: '{{ .Env.COSIGN_PASSWORD }}'
+    output: true
+    artifacts: checksum
+    args:
+      - sign-blob
+      - --yes
+      - --key
+      - env://COSIGN_PRIVATE_KEY
+      - '--output-certificate=${certificate}'
+      - '--output-signature=${signature}'
+      - '${artifact}'
+
+archives:
+  - format: tar.gz
+    name_template: >-
+      {{ .ProjectName }}_
+      {{- title .Os }}_
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+
+kos:
+  - repository: ghcr.io/a-h/templ
+    platforms:
+    - linux/amd64
+    - linux/arm64
+    tags:
+    - latest
+    - '{{.Tag}}'
+    bare: true
+
+docker_signs:
+  - cmd: cosign
+    artifacts: all
+    output: true
+    args:
+      - sign
+      - --yes
+      - --key
+      - env://COSIGN_PRIVATE_KEY
+      - '${artifact}'
+
+snapshot:
+  name_template: "{{ incpatch .Version }}-next"
+
+changelog:
+  sort: asc
+  filters:
+    exclude:
+      - '^docs:'
+      - '^test:'

+ 7 - 0
backend/vendor/github.com/a-h/templ/.ignore

@@ -0,0 +1,7 @@
+*_templ.go
+examples/integration-ct/static/index.js
+examples/counter/assets/css/bulma.*
+examples/counter/assets/js/htmx.min.js
+examples/counter-basic/assets/css/bulma.*
+examples/typescript/assets/index.js
+package-lock.json

+ 1 - 0
backend/vendor/github.com/a-h/templ/.version

@@ -0,0 +1 @@
+0.2.793

+ 128 - 0
backend/vendor/github.com/a-h/templ/CODE_OF_CONDUCT.md

@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+  and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+  overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+  advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+  address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
[email protected].
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior,  harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.

+ 244 - 0
backend/vendor/github.com/a-h/templ/CONTRIBUTING.md

@@ -0,0 +1,244 @@
+# Contributing to templ
+
+## Vision
+
+Enable Go developers to build strongly typed, component-based HTML user interfaces with first-class developer tooling, and a short learning curve.
+
+## Come up with a design and share it
+
+Before starting work on any major pull requests or code changes, start a discussion at https://github.com/a-h/templ/discussions or raise an issue.
+
+We don't want you to spend time on a PR or feature that ultimately doesn't get merged because it doesn't fit with the project goals, or the design doesn't work for some reason.
+
+For issues, it really helps if you provide a reproduction repo, or can create a failing unit test to describe the behaviour.
+
+In designs, we need to consider:
+
+* Backwards compatibility - Not changing the public API between releases, introducing gradual deprecation - don't break people's code.
+* Correctness over time - How can we reduce the risk of defects both now, and in future releases?
+* Threat model - How could each change be used to inject vulnerabilities into web pages?
+* Go version - We target the oldest supported version of Go as per https://go.dev/doc/devel/release
+* Automatic migration - If we need to force through a change.
+* Compile time vs runtime errors - Prefer compile time.
+* Documentation - New features are only useful if people can understand the new feature, what would the documentation look like?
+* Examples - How will we demonstrate the feature?
+
+## Project structure
+
+templ is structured into a few areas:
+
+### Parser `./parser`
+
+The parser directory currently contains both v1 and v2 parsers.
+
+The v1 parser is not maintained, it's only used to migrate v1 code over to the v2 syntax.
+
+The parser is responsible for parsing templ files into an object model. The types that make up the object model are in `types.go`. Automatic formatting of the types is tested in `types_test.go`.
+
+A templ file is parsed into the `TemplateFile` struct object model.
+
+```go
+type TemplateFile struct {
+	// Header contains comments or whitespace at the top of the file.
+	Header []GoExpression
+	// Package expression.
+	Package Package
+	// Nodes in the file.
+	Nodes []TemplateFileNode
+}
+```
+
+Parsers are individually tested using two types of unit test.
+
+One test covers the successful parsing of text into an object. For example, the `HTMLCommentParser` test checks for successful patterns.
+
+```go
+func TestHTMLCommentParser(t *testing.T) {
+	var tests = []struct {
+		name     string
+		input    string
+		expected HTMLComment
+	}{
+		{
+			name:  "comment - single line",
+			input: `<!-- single line comment -->`,
+			expected: HTMLComment{
+				Contents: " single line comment ",
+			},
+		},
+		{
+			name:  "comment - no whitespace",
+			input: `<!--no whitespace between sequence open and close-->`,
+			expected: HTMLComment{
+				Contents: "no whitespace between sequence open and close",
+			},
+		},
+		{
+			name: "comment - multiline",
+			input: `<!-- multiline
+								comment
+					-->`,
+			expected: HTMLComment{
+				Contents: ` multiline
+								comment
+					`,
+			},
+		},
+		{
+			name:  "comment - with tag",
+			input: `<!-- <p class="test">tag</p> -->`,
+			expected: HTMLComment{
+				Contents: ` <p class="test">tag</p> `,
+			},
+		},
+		{
+			name:  "comments can contain tags",
+			input: `<!-- <div> hello world </div> -->`,
+			expected: HTMLComment{
+				Contents: ` <div> hello world </div> `,
+			},
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			input := parse.NewInput(tt.input)
+			result, ok, err := htmlComment.Parse(input)
+			if err != nil {
+				t.Fatalf("parser error: %v", err)
+			}
+			if !ok {
+				t.Fatalf("failed to parse at %d", input.Index())
+			}
+			if diff := cmp.Diff(tt.expected, result); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
+```
+
+Alongside each success test, is a similar test to check that invalid syntax is detected.
+
+```go
+func TestHTMLCommentParserErrors(t *testing.T) {
+	var tests = []struct {
+		name     string
+		input    string
+		expected error
+	}{
+		{
+			name:  "unclosed HTML comment",
+			input: `<!-- unclosed HTML comment`,
+			expected: parse.Error("expected end comment literal '-->' not found",
+				parse.Position{
+					Index: 26,
+					Line:  0,
+					Col:   26,
+				}),
+		},
+		{
+			name:  "comment in comment",
+			input: `<!-- <-- other --> -->`,
+			expected: parse.Error("comment contains invalid sequence '--'", parse.Position{
+				Index: 8,
+				Line:  0,
+				Col:   8,
+			}),
+		},
+	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			input := parse.NewInput(tt.input)
+			_, _, err := htmlComment.Parse(input)
+			if diff := cmp.Diff(tt.expected, err); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+```
+
+### Generator
+
+The generator takes the object model and writes out Go code that produces the expected output. Any changes to Go code output by templ are made in this area.
+
+Testing of the generator is carried out by creating a templ file, and a matching expected output file.
+
+For example, `./generator/test-a-href` contains a templ file of:
+
+```templ
+package testahref
+
+templ render() {
+	<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
+	<a href={ templ.URL("javascript:alert('should be sanitized')") }>Sanitized</a>
+	<a href={ templ.SafeURL("javascript:alert('should not be sanitized')") }>Unsanitized</a>
+}
+```
+
+It also contains an expected output file.
+
+```html
+<a href="javascript:alert(&#39;unaffected&#39;);">Ignored</a>
+<a href="about:invalid#TemplFailedSanitizationURL">Sanitized</a>
+<a href="javascript:alert(&#39;should not be sanitized&#39;)">Unsanitized</a>
+```
+
+These tests contribute towards the code coverage metrics by building an instrumented test CLI program. See the `test-cover` task in the `README.md` file.
+
+### CLI
+
+The command line interface for templ is used to generate Go code from templ files, format templ files, and run the LSP.
+
+The code for this is at `./cmd/templ`.
+
+Testing of the templ command line is done with unit tests to check the argument parsing.
+
+The `templ generate` command is tested by generating templ files in the project, and testing that the expected output HTML is present.
+
+### Runtime
+
+The runtime is used by generated code, and by template authors, to serve template content over HTTP, and to carry out various operations.
+
+It is in the root directory of the project at `./runtime.go`. The runtime is unit tested, as well as being tested as part of the `generate` tests.
+
+### LSP
+
+The LSP is structured within the command line interface, and proxies commands through to the `gopls` LSP.
+
+### Docs
+
+The docs are a Docusaurus project at `./docs`.
+
+## Coding
+
+### Build tasks
+
+templ uses the `xc` task runner - https://github.com/joerdav/xc
+
+If you run `xc` you can get see a list of the development tasks that can be run, or you can read the `README.md` file and see the `Tasks` section.
+
+The most useful tasks for local development are:
+
+* `install-snapshot` - this builds the templ CLI and installs it into `~/bin`. Ensure that this is in your path.
+* `test` - this regenerates all templates, and runs the unit tests.
+* `fmt` - run the `gofmt` tool to format all Go code.
+* `lint` - run the same linting as run in the CI process.
+* `docs-run` - run the Docusaurus documentation site.
+
+### Commit messages
+
+The project using https://www.conventionalcommits.org/en/v1.0.0/
+
+Examples:
+
+* `feat: support Go comments in templates, fixes #234"`
+
+### Coding style
+
+* Reduce nesting - i.e. prefer early returns over an `else` block, as per https://danp.net/posts/reducing-go-nesting/ or https://go.dev/doc/effective_go#if
+* Use line breaks to separate "paragraphs" of code - don't use line breaks in between lines, or at the start/end of functions etc.
+* Use the `fmt` and `lint` build tasks to format and lint your code before submitting a PR.
+

+ 21 - 0
backend/vendor/github.com/a-h/templ/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Adrian Hesketh
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 183 - 0
backend/vendor/github.com/a-h/templ/README.md

@@ -0,0 +1,183 @@
+![templ](https://github.com/a-h/templ/raw/main/templ.png)
+
+## An HTML templating language for Go that has great developer tooling.
+
+![templ](ide-demo.gif)
+
+
+## Documentation
+
+See user documentation at https://templ.guide
+
+<p align="center">
+<a href="https://pkg.go.dev/github.com/a-h/templ"><img src="https://pkg.go.dev/badge/github.com/a-h/templ.svg" alt="Go Reference" /></a>
+<a href="https://xcfile.dev"><img src="https://xcfile.dev/badge.svg" alt="xc compatible" /></a>
+<a href="https://raw.githack.com/wiki/a-h/templ/coverage.html"><img src="https://github.com/a-h/templ/wiki/coverage.svg" alt="Go Coverage" /></a>
+<a href="https://goreportcard.com/report/github.com/a-h/templ"><img src="https://goreportcard.com/badge/github.com/a-h/templ" alt="Go Report Card" /></a>
+</p>
+
+## Tasks
+
+### build
+
+Build a local version.
+
+```sh
+go run ./get-version > .version
+cd cmd/templ
+go build
+```
+
+### nix-update-gomod2nix
+
+```sh
+gomod2nix
+```
+
+### install-snapshot
+
+Build and install current version.
+
+```sh
+# Remove templ from the non-standard ~/bin/templ path
+# that this command previously used.
+rm -f ~/bin/templ
+# Clear LSP logs.
+rm -f cmd/templ/lspcmd/*.txt
+# Update version.
+go run ./get-version > .version
+# Install to $GOPATH/bin or $HOME/go/bin
+cd cmd/templ && go install
+```
+
+### build-snapshot
+
+Use goreleaser to build the command line binary using goreleaser.
+
+```sh
+goreleaser build --snapshot --clean
+```
+
+### generate
+
+Run templ generate using local version.
+
+```sh
+go run ./cmd/templ generate -include-version=false
+```
+
+### test
+
+Run Go tests.
+
+```sh
+go run ./get-version > .version
+go run ./cmd/templ generate -include-version=false
+go test ./...
+```
+
+### test-short
+
+Run Go tests.
+
+```sh
+go run ./get-version > .version
+go run ./cmd/templ generate -include-version=false
+go test ./... -short
+```
+
+### test-cover
+
+Run Go tests.
+
+```sh
+# Create test profile directories.
+mkdir -p coverage/fmt
+mkdir -p coverage/generate
+mkdir -p coverage/version
+mkdir -p coverage/unit
+# Build the test binary.
+go build -cover -o ./coverage/templ-cover ./cmd/templ
+# Run the covered generate command.
+GOCOVERDIR=coverage/fmt ./coverage/templ-cover fmt .
+GOCOVERDIR=coverage/generate ./coverage/templ-cover generate -include-version=false
+GOCOVERDIR=coverage/version ./coverage/templ-cover version
+# Run the unit tests.
+go test -cover ./... -coverpkg ./... -args -test.gocoverdir="$PWD/coverage/unit"
+# Display the combined percentage.
+go tool covdata percent -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit
+# Generate a text coverage profile for tooling to use.
+go tool covdata textfmt -i=./coverage/fmt,./coverage/generate,./coverage/version,./coverage/unit -o coverage.out
+# Print total
+go tool cover -func coverage.out | grep total
+```
+
+### test-cover-watch
+
+```sh
+gotestsum --watch -- -coverprofile=coverage.out
+```
+
+### benchmark
+
+Run benchmarks.
+
+```sh
+go run ./cmd/templ generate -include-version=false && go test ./... -bench=. -benchmem
+```
+
+### fmt
+
+Format all Go and templ code.
+
+```sh
+gofmt -s -w .
+go run ./cmd/templ fmt .
+```
+
+### lint
+
+Run the lint operations that are run as part of the CI.
+
+```sh
+golangci-lint run --verbose
+```
+
+### ensure-generated
+
+Ensure that templ files have been generated with the local version of templ, and that those files have been added to git.
+
+Requires: generate
+
+```sh
+git diff --exit-code
+```
+
+### push-release-tag
+
+Push a semantic version number to Github to trigger the release process.
+
+```sh
+./push-tag.sh
+```
+
+### docs-run
+
+Run the development server.
+
+Directory: docs
+
+```sh
+npm run start
+```
+
+### docs-build
+
+Build production docs site.
+
+Directory: docs
+
+```sh
+npm run build
+```
+

+ 9 - 0
backend/vendor/github.com/a-h/templ/SECURITY.md

@@ -0,0 +1,9 @@
+# Security Policy
+
+## Supported Versions
+
+The latest version of templ is supported.
+
+## Reporting a Vulnerability
+
+Use the "Security" tab in Github and fill out the "Report a vulnerability" form.

+ 4 - 0
backend/vendor/github.com/a-h/templ/cosign.pub

@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqHp75uAj8XqKrLO2YvY0M2EddckH
+evQnNAj+0GmBptqdf3NJcUCjL6w4z2Ikh/Zb8lh6b13akAwO/dJQaMLoMA==
+-----END PUBLIC KEY-----

+ 140 - 0
backend/vendor/github.com/a-h/templ/flake.lock

@@ -0,0 +1,140 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1694529238,
+        "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_2": {
+      "locked": {
+        "lastModified": 1667395993,
+        "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "gitignore": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "gomod2nix": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1722589758,
+        "narHash": "sha256-sbbA8b6Q2vB/t/r1znHawoXLysCyD4L/6n6/RykiSnA=",
+        "owner": "nix-community",
+        "repo": "gomod2nix",
+        "rev": "4e08ca09253ef996bd4c03afa383b23e35fe28a1",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-community",
+        "repo": "gomod2nix",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1724322575,
+        "narHash": "sha256-kRYwAdYsaICNb2WYcWtBFG6caSuT0v/vTAyR8ap0IR0=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "2a02822b466ffb9f1c02d07c5dd6b96d08b56c6b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "release-24.05",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "gitignore": "gitignore",
+        "gomod2nix": "gomod2nix",
+        "nixpkgs": "nixpkgs",
+        "xc": "xc"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    },
+    "xc": {
+      "inputs": {
+        "flake-utils": "flake-utils_2",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1724404748,
+        "narHash": "sha256-p6rXzNiDm2uBvO1MLzC5pJp/0zRNzj/snBzZI0ce62s=",
+        "owner": "joerdav",
+        "repo": "xc",
+        "rev": "960ff9f109d47a19122cfb015721a76e3a0f23a2",
+        "type": "github"
+      },
+      "original": {
+        "owner": "joerdav",
+        "repo": "xc",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}

+ 93 - 0
backend/vendor/github.com/a-h/templ/flake.nix

@@ -0,0 +1,93 @@
+{
+  description = "templ";
+
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/release-24.05";
+    gomod2nix = {
+      url = "github:nix-community/gomod2nix";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    gitignore = {
+      url = "github:hercules-ci/gitignore.nix";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    xc = {
+      url = "github:joerdav/xc";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+  };
+
+  outputs = { self, nixpkgs, gomod2nix, gitignore, xc }:
+    let
+      allSystems = [
+        "x86_64-linux" # 64-bit Intel/AMD Linux
+        "aarch64-linux" # 64-bit ARM Linux
+        "x86_64-darwin" # 64-bit Intel macOS
+        "aarch64-darwin" # 64-bit ARM macOS
+      ];
+      forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
+        inherit system;
+        pkgs = import nixpkgs { inherit system; };
+      });
+    in
+    {
+      packages = forAllSystems ({ system, pkgs, ... }:
+        let
+          buildGoApplication = gomod2nix.legacyPackages.${system}.buildGoApplication;
+        in
+        rec {
+          default = templ;
+
+          templ = buildGoApplication {
+            name = "templ";
+            src = gitignore.lib.gitignoreSource ./.;
+            # Update to latest Go version when https://nixpk.gs/pr-tracker.html?pr=324123 is backported to release-24.05.
+            go = pkgs.go;
+            # Must be added due to bug https://github.com/nix-community/gomod2nix/issues/120
+            pwd = ./.;
+            subPackages = [ "cmd/templ" ];
+            CGO_ENABLED = 0;
+            flags = [
+              "-trimpath"
+            ];
+            ldflags = [
+              "-s"
+              "-w"
+              "-extldflags -static"
+            ];
+          };
+        });
+
+      # `nix develop` provides a shell containing development tools.
+      devShell = forAllSystems ({ system, pkgs }:
+        pkgs.mkShell {
+          buildInputs = with pkgs; [
+            golangci-lint
+            cosign # Used to sign container images.
+            esbuild # Used to package JS examples.
+            go_1_22
+            gomod2nix.legacyPackages.${system}.gomod2nix
+            gopls
+            goreleaser
+            gotestsum
+            ko # Used to build Docker images.
+            nodejs # Used to build templ-docs.
+            xc.packages.${system}.xc
+          ];
+        });
+
+      # This flake outputs an overlay that can be used to add templ and
+      # templ-docs to nixpkgs as per https://templ.guide/quick-start/installation/#nix
+      #
+      # Example usage:
+      #
+      # nixpkgs.overlays = [
+      #   inputs.templ.overlays.default
+      # ];
+      overlays.default = final: prev: {
+        templ = self.packages.${final.stdenv.system}.templ;
+        templ-docs = self.packages.${final.stdenv.system}.templ-docs;
+      };
+    };
+}
+

+ 36 - 0
backend/vendor/github.com/a-h/templ/flush.go

@@ -0,0 +1,36 @@
+package templ
+
+import (
+	"context"
+	"io"
+)
+
+// Flush flushes the output buffer after all its child components have been rendered.
+func Flush() FlushComponent {
+	return FlushComponent{}
+}
+
+type FlushComponent struct {
+}
+
+type flusherError interface {
+	Flush() error
+}
+
+type flusher interface {
+	Flush()
+}
+
+func (f FlushComponent) Render(ctx context.Context, w io.Writer) (err error) {
+	if err = GetChildren(ctx).Render(ctx, w); err != nil {
+		return err
+	}
+	switch w := w.(type) {
+	case flusher:
+		w.Flush()
+		return nil
+	case flusherError:
+		return w.Flush()
+	}
+	return nil
+}

+ 90 - 0
backend/vendor/github.com/a-h/templ/gomod2nix.toml

@@ -0,0 +1,90 @@
+schema = 3
+
+[mod]
+  [mod."github.com/PuerkitoBio/goquery"]
+    version = "v1.8.1"
+    hash = "sha256-z2RaB8PVPEzSJdMUfkfNjT616yXWTjW2gkhNOh989ZU="
+  [mod."github.com/a-h/htmlformat"]
+    version = "v0.0.0-20231108124658-5bd994fe268e"
+    hash = "sha256-YSl9GsXhc0L2oKGZLwwjUtpe5W6ra6kk74zvQdsDCMU="
+  [mod."github.com/a-h/parse"]
+    version = "v0.0.0-20240121214402-3caf7543159a"
+    hash = "sha256-ee/g6xwwhtF7vVt3griUSh96Kz4z0hM5/tpXxHW6PZk="
+  [mod."github.com/a-h/pathvars"]
+    version = "v0.0.14"
+    hash = "sha256-2NytUpcO0zbzE5XunCLcK3jDqxYzmyb3WqtYDEudAYg="
+  [mod."github.com/a-h/protocol"]
+    version = "v0.0.0-20240704131721-1e461c188041"
+    hash = "sha256-KSw8m+kVIubEi+nuS3dMdBw2ZZTlmcKD/hGbVRFaE5Q="
+  [mod."github.com/andybalholm/brotli"]
+    version = "v1.1.0"
+    hash = "sha256-njLViV4v++ZdgOWGWzlvkefuFvA/nkugl3Ta/h1nu/0="
+  [mod."github.com/andybalholm/cascadia"]
+    version = "v1.3.1"
+    hash = "sha256-M0u22DXSeXUaYtl1KoW1qWL46niFpycFkraCEQ/luYA="
+  [mod."github.com/cenkalti/backoff/v4"]
+    version = "v4.3.0"
+    hash = "sha256-wfVjNZsGG1WoNC5aL+kdcy6QXPgZo4THAevZ1787md8="
+  [mod."github.com/cli/browser"]
+    version = "v1.3.0"
+    hash = "sha256-06hcvQeOEm31clxkTuZ8ts8ZtdNKY575EsM1osRVpLg="
+  [mod."github.com/fatih/color"]
+    version = "v1.16.0"
+    hash = "sha256-Aq/SM28aPJVzvapllQ64R/DM4aZ5CHPewcm/AUJPyJQ="
+  [mod."github.com/fsnotify/fsnotify"]
+    version = "v1.7.0"
+    hash = "sha256-MdT2rQyQHspPJcx6n9ozkLbsktIOJutOqDuKpNAtoZY="
+  [mod."github.com/google/go-cmp"]
+    version = "v0.6.0"
+    hash = "sha256-qgra5jze4iPGP0JSTVeY5qV5AvEnEu39LYAuUCIkMtg="
+  [mod."github.com/mattn/go-colorable"]
+    version = "v0.1.13"
+    hash = "sha256-qb3Qbo0CELGRIzvw7NVM1g/aayaz4Tguppk9MD2/OI8="
+  [mod."github.com/mattn/go-isatty"]
+    version = "v0.0.20"
+    hash = "sha256-qhw9hWtU5wnyFyuMbKx+7RB8ckQaFQ8D+8GKPkN3HHQ="
+  [mod."github.com/natefinch/atomic"]
+    version = "v1.0.1"
+    hash = "sha256-fbOVHCwRNI8PFjC4o0YXpKZO0JU2aWTfH5c7WXXKMHg="
+  [mod."github.com/rs/cors"]
+    version = "v1.11.0"
+    hash = "sha256-hF25bVehtWCQsxiOfLuL4Hv8NKVunEqLPk/Vcuheha0="
+  [mod."github.com/segmentio/asm"]
+    version = "v1.2.0"
+    hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs="
+  [mod."github.com/segmentio/encoding"]
+    version = "v0.4.0"
+    hash = "sha256-4pWI9eTZRRDP9kO8rG6vbLCtBVVRLtbCJKd0Z2+8JoU="
+  [mod."github.com/stretchr/testify"]
+    version = "v1.8.4"
+    hash = "sha256-MoOmRzbz9QgiJ+OOBo5h5/LbilhJfRUryvzHJmXAWjo="
+  [mod."go.lsp.dev/jsonrpc2"]
+    version = "v0.10.0"
+    hash = "sha256-RbRsMYVBLR7ZDHHGMooycrkdbIauMXkQjVOGP7ggSgM="
+  [mod."go.lsp.dev/pkg"]
+    version = "v0.0.0-20210717090340-384b27a52fb2"
+    hash = "sha256-TxS0Iqe1wbIaFe7MWZJRQdgqhKE8i8CggaGSV9zU1Vg="
+  [mod."go.lsp.dev/uri"]
+    version = "v0.3.0"
+    hash = "sha256-jGP0N7Gf+bql5oJraUo33sXqWg7AKOTj0D8b4paV4dc="
+  [mod."go.uber.org/multierr"]
+    version = "v1.11.0"
+    hash = "sha256-Lb6rHHfR62Ozg2j2JZy3MKOMKdsfzd1IYTR57r3Mhp0="
+  [mod."go.uber.org/zap"]
+    version = "v1.27.0"
+    hash = "sha256-8655KDrulc4Das3VRduO9MjCn8ZYD5WkULjCvruaYsU="
+  [mod."golang.org/x/mod"]
+    version = "v0.20.0"
+    hash = "sha256-nXYnY2kpbVkaZ/7Mf7FmxwGDX7N4cID3gKjGghmVRp4="
+  [mod."golang.org/x/net"]
+    version = "v0.28.0"
+    hash = "sha256-WdH/mgsX/CB+CiYtXEwJAXHN8FgtW2YhFcWwrrHNBLo="
+  [mod."golang.org/x/sync"]
+    version = "v0.8.0"
+    hash = "sha256-usvF0z7gq1vsX58p4orX+8WHlv52pdXgaueXlwj2Wss="
+  [mod."golang.org/x/sys"]
+    version = "v0.23.0"
+    hash = "sha256-tC6QVLu72bADgINz26FUGdmYqKgsU45bHPg7sa0ZV7w="
+  [mod."golang.org/x/tools"]
+    version = "v0.24.0"
+    hash = "sha256-2LBEW//aW8qrHc26F6Ma7CsYJRaCALfi0xQl2KgWems="

+ 102 - 0
backend/vendor/github.com/a-h/templ/handler.go

@@ -0,0 +1,102 @@
+package templ
+
+import "net/http"
+
+// ComponentHandler is a http.Handler that renders components.
+type ComponentHandler struct {
+	Component      Component
+	Status         int
+	ContentType    string
+	ErrorHandler   func(r *http.Request, err error) http.Handler
+	StreamResponse bool
+}
+
+const componentHandlerErrorMessage = "templ: failed to render template"
+
+func (ch *ComponentHandler) ServeHTTPBuffered(w http.ResponseWriter, r *http.Request) {
+	// Since the component may error, write to a buffer first.
+	// This prevents partial responses from being written to the client.
+	buf := GetBuffer()
+	defer ReleaseBuffer(buf)
+	err := ch.Component.Render(r.Context(), buf)
+	if err != nil {
+		if ch.ErrorHandler != nil {
+			w.Header().Set("Content-Type", ch.ContentType)
+			ch.ErrorHandler(r, err).ServeHTTP(w, r)
+			return
+		}
+		http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
+		return
+	}
+	w.Header().Set("Content-Type", ch.ContentType)
+	if ch.Status != 0 {
+		w.WriteHeader(ch.Status)
+	}
+	// Ignore write error like http.Error() does, because there is
+	// no way to recover at this point.
+	_, _ = w.Write(buf.Bytes())
+}
+
+func (ch *ComponentHandler) ServeHTTPStreamed(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", ch.ContentType)
+	if ch.Status != 0 {
+		w.WriteHeader(ch.Status)
+	}
+	if err := ch.Component.Render(r.Context(), w); err != nil {
+		if ch.ErrorHandler != nil {
+			w.Header().Set("Content-Type", ch.ContentType)
+			ch.ErrorHandler(r, err).ServeHTTP(w, r)
+			return
+		}
+		http.Error(w, componentHandlerErrorMessage, http.StatusInternalServerError)
+	}
+}
+
+// ServeHTTP implements the http.Handler interface.
+func (ch ComponentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if ch.StreamResponse {
+		ch.ServeHTTPStreamed(w, r)
+		return
+	}
+	ch.ServeHTTPBuffered(w, r)
+}
+
+// Handler creates a http.Handler that renders the template.
+func Handler(c Component, options ...func(*ComponentHandler)) *ComponentHandler {
+	ch := &ComponentHandler{
+		Component:   c,
+		ContentType: "text/html; charset=utf-8",
+	}
+	for _, o := range options {
+		o(ch)
+	}
+	return ch
+}
+
+// WithStatus sets the HTTP status code returned by the ComponentHandler.
+func WithStatus(status int) func(*ComponentHandler) {
+	return func(ch *ComponentHandler) {
+		ch.Status = status
+	}
+}
+
+// WithContentType sets the Content-Type header returned by the ComponentHandler.
+func WithContentType(contentType string) func(*ComponentHandler) {
+	return func(ch *ComponentHandler) {
+		ch.ContentType = contentType
+	}
+}
+
+// WithErrorHandler sets the error handler used if rendering fails.
+func WithErrorHandler(eh func(r *http.Request, err error) http.Handler) func(*ComponentHandler) {
+	return func(ch *ComponentHandler) {
+		ch.ErrorHandler = eh
+	}
+}
+
+// WithStreaming sets the ComponentHandler to stream the response instead of buffering it.
+func WithStreaming() func(*ComponentHandler) {
+	return func(ch *ComponentHandler) {
+		ch.StreamResponse = true
+	}
+}

BIN=BIN
backend/vendor/github.com/a-h/templ/ide-demo.gif


+ 19 - 0
backend/vendor/github.com/a-h/templ/join.go

@@ -0,0 +1,19 @@
+package templ
+
+import (
+	"context"
+	"io"
+)
+
+// Join returns a single `templ.Component` that will render provided components in order.
+// If any of the components return an error the Join component will immediately return with the error.
+func Join(components ...Component) Component {
+	return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+		for _, c := range components {
+			if err = c.Render(ctx, w); err != nil {
+				return err
+			}
+		}
+		return nil
+	})
+}

+ 85 - 0
backend/vendor/github.com/a-h/templ/jsonscript.go

@@ -0,0 +1,85 @@
+package templ
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+)
+
+var _ Component = JSONScriptElement{}
+
+// JSONScript renders a JSON object inside a script element.
+// e.g. <script type="application/json">{"foo":"bar"}</script>
+func JSONScript(id string, data any) JSONScriptElement {
+	return JSONScriptElement{
+		ID:    id,
+		Type:  "application/json",
+		Data:  data,
+		Nonce: GetNonce,
+	}
+}
+
+// WithType sets the value of the type attribute of the script element.
+func (j JSONScriptElement) WithType(t string) JSONScriptElement {
+	j.Type = t
+	return j
+}
+
+// WithNonceFromString sets the value of the nonce attribute of the script element to the given string.
+func (j JSONScriptElement) WithNonceFromString(nonce string) JSONScriptElement {
+	j.Nonce = func(context.Context) string {
+		return nonce
+	}
+	return j
+}
+
+// WithNonceFrom sets the value of the nonce attribute of the script element to the value returned by the given function.
+func (j JSONScriptElement) WithNonceFrom(f func(context.Context) string) JSONScriptElement {
+	j.Nonce = f
+	return j
+}
+
+type JSONScriptElement struct {
+	// ID of the element in the DOM.
+	ID string
+	// Type of the script element, defaults to "application/json".
+	Type string
+	// Data that will be encoded as JSON.
+	Data any
+	// Nonce is a function that returns a CSP nonce.
+	// Defaults to CSPNonceFromContext.
+	// See https://content-security-policy.com/nonce for more information.
+	Nonce func(ctx context.Context) string
+}
+
+func (j JSONScriptElement) Render(ctx context.Context, w io.Writer) (err error) {
+	if _, err = io.WriteString(w, "<script"); err != nil {
+		return err
+	}
+	if j.ID != "" {
+		if _, err = fmt.Fprintf(w, " id=\"%s\"", EscapeString(j.ID)); err != nil {
+			return err
+		}
+	}
+	if j.Type != "" {
+		if _, err = fmt.Fprintf(w, " type=\"%s\"", EscapeString(j.Type)); err != nil {
+			return err
+		}
+	}
+	if nonce := j.Nonce(ctx); nonce != "" {
+		if _, err = fmt.Fprintf(w, " nonce=\"%s\"", EscapeString(nonce)); err != nil {
+			return err
+		}
+	}
+	if _, err = io.WriteString(w, ">"); err != nil {
+		return err
+	}
+	if err = json.NewEncoder(w).Encode(j.Data); err != nil {
+		return err
+	}
+	if _, err = io.WriteString(w, "</script>"); err != nil {
+		return err
+	}
+	return nil
+}

+ 14 - 0
backend/vendor/github.com/a-h/templ/jsonstring.go

@@ -0,0 +1,14 @@
+package templ
+
+import (
+	"encoding/json"
+)
+
+// JSONString returns a JSON encoded string of v.
+func JSONString(v any) (string, error) {
+	b, err := json.Marshal(v)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}

+ 64 - 0
backend/vendor/github.com/a-h/templ/once.go

@@ -0,0 +1,64 @@
+package templ
+
+import (
+	"context"
+	"io"
+	"sync/atomic"
+)
+
+// onceHandleIndex is used to identify unique once handles in a program run.
+var onceHandleIndex int64
+
+type OnceOpt func(*OnceHandle)
+
+// WithOnceComponent sets the component to be rendered once per context.
+// This can be used instead of setting the children of the `Once` method,
+// for example, if creating a code component outside of a templ HTML template.
+func WithComponent(c Component) OnceOpt {
+	return func(o *OnceHandle) {
+		o.c = c
+	}
+}
+
+// NewOnceHandle creates a OnceHandle used to ensure that the children of its
+// `Once` method are only rendered once per context.
+func NewOnceHandle(opts ...OnceOpt) *OnceHandle {
+	oh := &OnceHandle{
+		id: atomic.AddInt64(&onceHandleIndex, 1),
+	}
+	for _, opt := range opts {
+		opt(oh)
+	}
+	return oh
+}
+
+// OnceHandle is used to ensure that the children of its `Once` method are are only
+// rendered once per context.
+type OnceHandle struct {
+	// id is used to identify which instance of the OnceHandle is being used.
+	// The OnceHandle can't be an empty struct, because:
+	//
+	//  | Two distinct zero-size variables may
+	//  | have the same address in memory
+	//
+	// https://go.dev/ref/spec#Size_and_alignment_guarantees
+	id int64
+	// c is the component to be rendered once per context.
+	// if c is nil, the children of the `Once` method are rendered.
+	c Component
+}
+
+// Once returns a component that renders its children once per context.
+func (o *OnceHandle) Once() Component {
+	return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+		_, v := getContext(ctx)
+		if v.getHasBeenRendered(o) {
+			return nil
+		}
+		v.setHasBeenRendered(o)
+		if o.c != nil {
+			return o.c.Render(ctx, w)
+		}
+		return GetChildren(ctx).Render(ctx, w)
+	})
+}

+ 14 - 0
backend/vendor/github.com/a-h/templ/push-tag.sh

@@ -0,0 +1,14 @@
+#!/bin/sh
+if [ `git rev-parse --abbrev-ref HEAD` != "main" ]; then
+  echo "Error: Not on main branch. Please switch to main branch.";
+  exit 1;
+fi
+git pull
+if ! git diff --quiet; then
+  echo "Error: Working directory is not clean. Please commit the changes first.";
+  exit 1;
+fi
+export VERSION=`cat .version`
+echo Adding git tag with version v${VERSION};
+git tag v${VERSION};
+git push origin v${VERSION};

+ 730 - 0
backend/vendor/github.com/a-h/templ/runtime.go

@@ -0,0 +1,730 @@
+package templ
+
+import (
+	"bytes"
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"html"
+	"html/template"
+	"io"
+	"net/http"
+	"os"
+	"reflect"
+	"runtime"
+	"sort"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/a-h/templ/safehtml"
+)
+
+// Types exposed by all components.
+
+// Component is the interface that all templates implement.
+type Component interface {
+	// Render the template.
+	Render(ctx context.Context, w io.Writer) error
+}
+
+// ComponentFunc converts a function that matches the Component interface's
+// Render method into a Component.
+type ComponentFunc func(ctx context.Context, w io.Writer) error
+
+// Render the template.
+func (cf ComponentFunc) Render(ctx context.Context, w io.Writer) error {
+	return cf(ctx, w)
+}
+
+// WithNonce sets a CSP nonce on the context and returns it.
+func WithNonce(ctx context.Context, nonce string) context.Context {
+	ctx, v := getContext(ctx)
+	v.nonce = nonce
+	return ctx
+}
+
+// GetNonce returns the CSP nonce value set with WithNonce, or an
+// empty string if none has been set.
+func GetNonce(ctx context.Context) (nonce string) {
+	if ctx == nil {
+		return ""
+	}
+	_, v := getContext(ctx)
+	return v.nonce
+}
+
+func WithChildren(ctx context.Context, children Component) context.Context {
+	ctx, v := getContext(ctx)
+	v.children = &children
+	return ctx
+}
+
+func ClearChildren(ctx context.Context) context.Context {
+	_, v := getContext(ctx)
+	v.children = nil
+	return ctx
+}
+
+// NopComponent is a component that doesn't render anything.
+var NopComponent = ComponentFunc(func(ctx context.Context, w io.Writer) error { return nil })
+
+// GetChildren from the context.
+func GetChildren(ctx context.Context) Component {
+	_, v := getContext(ctx)
+	if v.children == nil {
+		return NopComponent
+	}
+	return *v.children
+}
+
+// EscapeString escapes HTML text within templates.
+func EscapeString(s string) string {
+	return html.EscapeString(s)
+}
+
+// Bool attribute value.
+func Bool(value bool) bool {
+	return value
+}
+
+// Classes for CSS.
+// Supported types are string, ConstantCSSClass, ComponentCSSClass, map[string]bool.
+func Classes(classes ...any) CSSClasses {
+	return CSSClasses(classes)
+}
+
+// CSSClasses is a slice of CSS classes.
+type CSSClasses []any
+
+// String returns the names of all CSS classes.
+func (classes CSSClasses) String() string {
+	if len(classes) == 0 {
+		return ""
+	}
+	cp := newCSSProcessor()
+	for _, v := range classes {
+		cp.Add(v)
+	}
+	return cp.String()
+}
+
+func newCSSProcessor() *cssProcessor {
+	return &cssProcessor{
+		classNameToEnabled: make(map[string]bool),
+	}
+}
+
+type cssProcessor struct {
+	classNameToEnabled map[string]bool
+	orderedNames       []string
+}
+
+func (cp *cssProcessor) Add(item any) {
+	switch c := item.(type) {
+	case []string:
+		for _, className := range c {
+			cp.AddClassName(className, true)
+		}
+	case string:
+		cp.AddClassName(c, true)
+	case ConstantCSSClass:
+		cp.AddClassName(c.ClassName(), true)
+	case ComponentCSSClass:
+		cp.AddClassName(c.ClassName(), true)
+	case map[string]bool:
+		// In Go, map keys are iterated in a randomized order.
+		// So the keys in the map must be sorted to produce consistent output.
+		keys := make([]string, len(c))
+		var i int
+		for key := range c {
+			keys[i] = key
+			i++
+		}
+		sort.Strings(keys)
+		for _, className := range keys {
+			cp.AddClassName(className, c[className])
+		}
+	case []KeyValue[string, bool]:
+		for _, kv := range c {
+			cp.AddClassName(kv.Key, kv.Value)
+		}
+	case KeyValue[string, bool]:
+		cp.AddClassName(c.Key, c.Value)
+	case []KeyValue[CSSClass, bool]:
+		for _, kv := range c {
+			cp.AddClassName(kv.Key.ClassName(), kv.Value)
+		}
+	case KeyValue[CSSClass, bool]:
+		cp.AddClassName(c.Key.ClassName(), c.Value)
+	case CSSClasses:
+		for _, item := range c {
+			cp.Add(item)
+		}
+	case []CSSClass:
+		for _, item := range c {
+			cp.Add(item)
+		}
+	case func() CSSClass:
+		cp.AddClassName(c().ClassName(), true)
+	default:
+		cp.AddClassName(unknownTypeClassName, true)
+	}
+}
+
+func (cp *cssProcessor) AddClassName(className string, enabled bool) {
+	cp.classNameToEnabled[className] = enabled
+	cp.orderedNames = append(cp.orderedNames, className)
+}
+
+func (cp *cssProcessor) String() string {
+	// Order the outputs according to how they were input, and remove disabled names.
+	rendered := make(map[string]any, len(cp.classNameToEnabled))
+	var names []string
+	for _, name := range cp.orderedNames {
+		if enabled := cp.classNameToEnabled[name]; !enabled {
+			continue
+		}
+		if _, hasBeenRendered := rendered[name]; hasBeenRendered {
+			continue
+		}
+		names = append(names, name)
+		rendered[name] = struct{}{}
+	}
+
+	return strings.Join(names, " ")
+}
+
+// KeyValue is a key and value pair.
+type KeyValue[TKey comparable, TValue any] struct {
+	Key   TKey   `json:"name"`
+	Value TValue `json:"value"`
+}
+
+// KV creates a new key/value pair from the input key and value.
+func KV[TKey comparable, TValue any](key TKey, value TValue) KeyValue[TKey, TValue] {
+	return KeyValue[TKey, TValue]{
+		Key:   key,
+		Value: value,
+	}
+}
+
+const unknownTypeClassName = "--templ-css-class-unknown-type"
+
+// Class returns a CSS class name.
+// Deprecated: use a string instead.
+func Class(name string) CSSClass {
+	return SafeClass(name)
+}
+
+// SafeClass bypasses CSS class name validation.
+// Deprecated: use a string instead.
+func SafeClass(name string) CSSClass {
+	return ConstantCSSClass(name)
+}
+
+// CSSClass provides a class name.
+type CSSClass interface {
+	ClassName() string
+}
+
+// ConstantCSSClass is a string constant of a CSS class name.
+// Deprecated: use a string instead.
+type ConstantCSSClass string
+
+// ClassName of the CSS class.
+func (css ConstantCSSClass) ClassName() string {
+	return string(css)
+}
+
+// ComponentCSSClass is a templ.CSS
+type ComponentCSSClass struct {
+	// ID of the class, will be autogenerated.
+	ID string
+	// Definition of the CSS.
+	Class SafeCSS
+}
+
+// ClassName of the CSS class.
+func (css ComponentCSSClass) ClassName() string {
+	return css.ID
+}
+
+// CSSID calculates an ID.
+func CSSID(name string, css string) string {
+	sum := sha256.Sum256([]byte(css))
+	hp := hex.EncodeToString(sum[:])[0:4]
+	// Benchmarking showed this was fastest, and with fewest allocations (1).
+	// Using strings.Builder (2 allocs).
+	// Using fmt.Sprintf (3 allocs).
+	return name + "_" + hp
+}
+
+// NewCSSMiddleware creates HTTP middleware that renders a global stylesheet of ComponentCSSClass
+// CSS if the request path matches, or updates the HTTP context to ensure that any handlers that
+// use templ.Components skip rendering <style> elements for classes that are included in the global
+// stylesheet. By default, the stylesheet path is /styles/templ.css
+func NewCSSMiddleware(next http.Handler, classes ...CSSClass) CSSMiddleware {
+	return CSSMiddleware{
+		Path:       "/styles/templ.css",
+		CSSHandler: NewCSSHandler(classes...),
+		Next:       next,
+	}
+}
+
+// CSSMiddleware renders a global stylesheet.
+type CSSMiddleware struct {
+	Path       string
+	CSSHandler CSSHandler
+	Next       http.Handler
+}
+
+func (cssm CSSMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if r.URL.Path == cssm.Path {
+		cssm.CSSHandler.ServeHTTP(w, r)
+		return
+	}
+	// Add registered classes to the context.
+	ctx, v := getContext(r.Context())
+	for _, c := range cssm.CSSHandler.Classes {
+		v.addClass(c.ID)
+	}
+	// Serve the request. Templ components will use the updated context
+	// to know to skip rendering <style> elements for any component CSS
+	// classes that have been included in the global stylesheet.
+	cssm.Next.ServeHTTP(w, r.WithContext(ctx))
+}
+
+// NewCSSHandler creates a handler that serves a stylesheet containing the CSS of the
+// classes passed in. This is used by the CSSMiddleware to provide global stylesheets
+// for templ components.
+func NewCSSHandler(classes ...CSSClass) CSSHandler {
+	ccssc := make([]ComponentCSSClass, 0, len(classes))
+	for _, c := range classes {
+		ccss, ok := c.(ComponentCSSClass)
+		if !ok {
+			continue
+		}
+		ccssc = append(ccssc, ccss)
+	}
+	return CSSHandler{
+		Classes: ccssc,
+	}
+}
+
+// CSSHandler is a HTTP handler that serves CSS.
+type CSSHandler struct {
+	Logger  func(err error)
+	Classes []ComponentCSSClass
+}
+
+func (cssh CSSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/css")
+	for _, c := range cssh.Classes {
+		_, err := w.Write([]byte(c.Class))
+		if err != nil && cssh.Logger != nil {
+			cssh.Logger(err)
+		}
+	}
+}
+
+// RenderCSSItems renders the CSS to the writer, if the items haven't already been rendered.
+func RenderCSSItems(ctx context.Context, w io.Writer, classes ...any) (err error) {
+	if len(classes) == 0 {
+		return nil
+	}
+	_, v := getContext(ctx)
+	sb := new(strings.Builder)
+	renderCSSItemsToBuilder(sb, v, classes...)
+	if sb.Len() > 0 {
+		if _, err = io.WriteString(w, `<style type="text/css">`); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, sb.String()); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, `</style>`); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func renderCSSItemsToBuilder(sb *strings.Builder, v *contextValue, classes ...any) {
+	for _, c := range classes {
+		switch ccc := c.(type) {
+		case ComponentCSSClass:
+			if !v.hasClassBeenRendered(ccc.ID) {
+				sb.WriteString(string(ccc.Class))
+				v.addClass(ccc.ID)
+			}
+		case KeyValue[ComponentCSSClass, bool]:
+			if !ccc.Value {
+				continue
+			}
+			renderCSSItemsToBuilder(sb, v, ccc.Key)
+		case KeyValue[CSSClass, bool]:
+			if !ccc.Value {
+				continue
+			}
+			renderCSSItemsToBuilder(sb, v, ccc.Key)
+		case CSSClasses:
+			renderCSSItemsToBuilder(sb, v, ccc...)
+		case []CSSClass:
+			for _, item := range ccc {
+				renderCSSItemsToBuilder(sb, v, item)
+			}
+		case func() CSSClass:
+			renderCSSItemsToBuilder(sb, v, ccc())
+		case []string:
+			// Skip. These are class names, not CSS classes.
+		case string:
+			// Skip. This is a class name, not a CSS class.
+		case ConstantCSSClass:
+			// Skip. This is a class name, not a CSS class.
+		case CSSClass:
+			// Skip. This is a class name, not a CSS class.
+		case map[string]bool:
+			// Skip. These are class names, not CSS classes.
+		case KeyValue[string, bool]:
+			// Skip. These are class names, not CSS classes.
+		case []KeyValue[string, bool]:
+			// Skip. These are class names, not CSS classes.
+		case KeyValue[ConstantCSSClass, bool]:
+			// Skip. These are class names, not CSS classes.
+		case []KeyValue[ConstantCSSClass, bool]:
+			// Skip. These are class names, not CSS classes.
+		}
+	}
+}
+
+// SafeCSS is CSS that has been sanitized.
+type SafeCSS string
+
+type SafeCSSProperty string
+
+var safeCSSPropertyType = reflect.TypeOf(SafeCSSProperty(""))
+
+// SanitizeCSS sanitizes CSS properties to ensure that they are safe.
+func SanitizeCSS[T ~string](property string, value T) SafeCSS {
+	if reflect.TypeOf(value) == safeCSSPropertyType {
+		return SafeCSS(safehtml.SanitizeCSSProperty(property) + ":" + string(value) + ";")
+	}
+	p, v := safehtml.SanitizeCSS(property, string(value))
+	return SafeCSS(p + ":" + v + ";")
+}
+
+// Attributes is an alias to map[string]any made for spread attributes.
+type Attributes map[string]any
+
+// sortedKeys returns the keys of a map in sorted order.
+func sortedKeys(m map[string]any) (keys []string) {
+	keys = make([]string, len(m))
+	var i int
+	for k := range m {
+		keys[i] = k
+		i++
+	}
+	sort.Strings(keys)
+	return keys
+}
+
+func writeStrings(w io.Writer, ss ...string) (err error) {
+	for _, s := range ss {
+		if _, err = io.WriteString(w, s); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func RenderAttributes(ctx context.Context, w io.Writer, attributes Attributes) (err error) {
+	for _, key := range sortedKeys(attributes) {
+		value := attributes[key]
+		switch value := value.(type) {
+		case string:
+			if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value), `"`); err != nil {
+				return err
+			}
+		case *string:
+			if value != nil {
+				if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(*value), `"`); err != nil {
+					return err
+				}
+			}
+		case bool:
+			if value {
+				if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
+					return err
+				}
+			}
+		case *bool:
+			if value != nil && *value {
+				if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
+					return err
+				}
+			}
+		case KeyValue[string, bool]:
+			if value.Value {
+				if err = writeStrings(w, ` `, EscapeString(key), `="`, EscapeString(value.Key), `"`); err != nil {
+					return err
+				}
+			}
+		case KeyValue[bool, bool]:
+			if value.Value && value.Key {
+				if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
+					return err
+				}
+			}
+		case func() bool:
+			if value() {
+				if err = writeStrings(w, ` `, EscapeString(key)); err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// Context.
+
+type contextKeyType int
+
+const contextKey = contextKeyType(0)
+
+type contextValue struct {
+	ss          map[string]struct{}
+	onceHandles map[*OnceHandle]struct{}
+	children    *Component
+	nonce       string
+}
+
+func (v *contextValue) setHasBeenRendered(h *OnceHandle) {
+	if v.onceHandles == nil {
+		v.onceHandles = map[*OnceHandle]struct{}{}
+	}
+	v.onceHandles[h] = struct{}{}
+}
+
+func (v *contextValue) getHasBeenRendered(h *OnceHandle) (ok bool) {
+	if v.onceHandles == nil {
+		v.onceHandles = map[*OnceHandle]struct{}{}
+	}
+	_, ok = v.onceHandles[h]
+	return
+}
+
+func (v *contextValue) addScript(s string) {
+	if v.ss == nil {
+		v.ss = map[string]struct{}{}
+	}
+	v.ss["script_"+s] = struct{}{}
+}
+
+func (v *contextValue) hasScriptBeenRendered(s string) (ok bool) {
+	if v.ss == nil {
+		v.ss = map[string]struct{}{}
+	}
+	_, ok = v.ss["script_"+s]
+	return
+}
+
+func (v *contextValue) addClass(s string) {
+	if v.ss == nil {
+		v.ss = map[string]struct{}{}
+	}
+	v.ss["class_"+s] = struct{}{}
+}
+
+func (v *contextValue) hasClassBeenRendered(s string) (ok bool) {
+	if v.ss == nil {
+		v.ss = map[string]struct{}{}
+	}
+	_, ok = v.ss["class_"+s]
+	return
+}
+
+// InitializeContext initializes context used to store internal state used during rendering.
+func InitializeContext(ctx context.Context) context.Context {
+	if _, ok := ctx.Value(contextKey).(*contextValue); ok {
+		return ctx
+	}
+	v := &contextValue{}
+	ctx = context.WithValue(ctx, contextKey, v)
+	return ctx
+}
+
+func getContext(ctx context.Context) (context.Context, *contextValue) {
+	v, ok := ctx.Value(contextKey).(*contextValue)
+	if !ok {
+		ctx = InitializeContext(ctx)
+		v = ctx.Value(contextKey).(*contextValue)
+	}
+	return ctx, v
+}
+
+var bufferPool = sync.Pool{
+	New: func() any {
+		return new(bytes.Buffer)
+	},
+}
+
+func GetBuffer() *bytes.Buffer {
+	return bufferPool.Get().(*bytes.Buffer)
+}
+
+func ReleaseBuffer(b *bytes.Buffer) {
+	b.Reset()
+	bufferPool.Put(b)
+}
+
+// JoinStringErrs joins an optional list of errors.
+func JoinStringErrs(s string, errs ...error) (string, error) {
+	return s, errors.Join(errs...)
+}
+
+// Error returned during template rendering.
+type Error struct {
+	Err error
+	// FileName of the template file.
+	FileName string
+	// Line index of the error.
+	Line int
+	// Col index of the error.
+	Col int
+}
+
+func (e Error) Error() string {
+	if e.FileName == "" {
+		e.FileName = "templ"
+	}
+	return fmt.Sprintf("%s: error at line %d, col %d: %v", e.FileName, e.Line, e.Col, e.Err)
+}
+
+func (e Error) Unwrap() error {
+	return e.Err
+}
+
+// Raw renders the input HTML to the output without applying HTML escaping.
+//
+// Use of this component presents a security risk - the HTML should come from
+// a trusted source, because it will be included as-is in the output.
+func Raw[T ~string](html T, errs ...error) Component {
+	return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+		if err = errors.Join(errs...); err != nil {
+			return err
+		}
+		_, err = io.WriteString(w, string(html))
+		return err
+	})
+}
+
+// FromGoHTML creates a templ Component from a Go html/template template.
+func FromGoHTML(t *template.Template, data any) Component {
+	return ComponentFunc(func(ctx context.Context, w io.Writer) (err error) {
+		return t.Execute(w, data)
+	})
+}
+
+// ToGoHTML renders the component to a Go html/template template.HTML string.
+func ToGoHTML(ctx context.Context, c Component) (s template.HTML, err error) {
+	b := GetBuffer()
+	defer ReleaseBuffer(b)
+	if err = c.Render(ctx, b); err != nil {
+		return
+	}
+	s = template.HTML(b.String())
+	return
+}
+
+// WriteWatchModeString is used when rendering templates in development mode.
+// the generator would have written non-go code to the _templ.txt file, which
+// is then read by this function and written to the output.
+func WriteWatchModeString(w io.Writer, lineNum int) error {
+	_, path, _, _ := runtime.Caller(1)
+	if !strings.HasSuffix(path, "_templ.go") {
+		return errors.New("templ: WriteWatchModeString can only be called from _templ.go")
+	}
+	txtFilePath := strings.Replace(path, "_templ.go", "_templ.txt", 1)
+
+	literals, err := getWatchedStrings(txtFilePath)
+	if err != nil {
+		return fmt.Errorf("templ: failed to cache strings: %w", err)
+	}
+
+	if lineNum > len(literals) {
+		return errors.New("templ: failed to find line " + strconv.Itoa(lineNum) + " in " + txtFilePath)
+	}
+
+	unquoted, err := strconv.Unquote(`"` + literals[lineNum-1] + `"`)
+	if err != nil {
+		return err
+	}
+	_, err = io.WriteString(w, unquoted)
+	return err
+}
+
+var (
+	watchModeCache  = map[string]watchState{}
+	watchStateMutex sync.Mutex
+)
+
+type watchState struct {
+	modTime time.Time
+	strings []string
+}
+
+func getWatchedStrings(txtFilePath string) ([]string, error) {
+	watchStateMutex.Lock()
+	defer watchStateMutex.Unlock()
+
+	state, cached := watchModeCache[txtFilePath]
+	if !cached {
+		return cacheStrings(txtFilePath)
+	}
+
+	if time.Since(state.modTime) < time.Millisecond*100 {
+		return state.strings, nil
+	}
+
+	info, err := os.Stat(txtFilePath)
+	if err != nil {
+		return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
+	}
+
+	if !info.ModTime().After(state.modTime) {
+		return state.strings, nil
+	}
+
+	return cacheStrings(txtFilePath)
+}
+
+func cacheStrings(txtFilePath string) ([]string, error) {
+	txtFile, err := os.Open(txtFilePath)
+	if err != nil {
+		return nil, fmt.Errorf("templ: failed to open %s: %w", txtFilePath, err)
+	}
+	defer txtFile.Close()
+
+	info, err := txtFile.Stat()
+	if err != nil {
+		return nil, fmt.Errorf("templ: failed to stat %s: %w", txtFilePath, err)
+	}
+
+	all, err := io.ReadAll(txtFile)
+	if err != nil {
+		return nil, fmt.Errorf("templ: failed to read %s: %w", txtFilePath, err)
+	}
+
+	literals := strings.Split(string(all), "\n")
+	watchModeCache[txtFilePath] = watchState{
+		modTime: info.ModTime(),
+		strings: literals,
+	}
+
+	return literals, nil
+}

+ 62 - 0
backend/vendor/github.com/a-h/templ/runtime/buffer.go

@@ -0,0 +1,62 @@
+package runtime
+
+import (
+	"bufio"
+	"io"
+	"net/http"
+)
+
+// DefaultBufferSize is the default size of buffers. It is set to 4KB by default, which is the
+// same as the default buffer size of bufio.Writer.
+var DefaultBufferSize = 4 * 1024 // 4KB
+
+// Buffer is a wrapper around bufio.Writer that enables flushing and closing of
+// the underlying writer.
+type Buffer struct {
+	Underlying io.Writer
+	b          *bufio.Writer
+}
+
+// Write the contents of p into the buffer.
+func (b *Buffer) Write(p []byte) (n int, err error) {
+	return b.b.Write(p)
+}
+
+// Flush writes any buffered data to the underlying io.Writer and
+// calls the Flush method of the underlying http.Flusher if it implements it.
+func (b *Buffer) Flush() error {
+	if err := b.b.Flush(); err != nil {
+		return err
+	}
+	if f, ok := b.Underlying.(http.Flusher); ok {
+		f.Flush()
+	}
+	return nil
+}
+
+// Close closes the buffer and the underlying io.Writer if it implements io.Closer.
+func (b *Buffer) Close() error {
+	if c, ok := b.Underlying.(io.Closer); ok {
+		return c.Close()
+	}
+	return nil
+}
+
+// Reset sets the underlying io.Writer to w and resets the buffer.
+func (b *Buffer) Reset(w io.Writer) {
+	if b.b == nil {
+		b.b = bufio.NewWriterSize(b, DefaultBufferSize)
+	}
+	b.Underlying = w
+	b.b.Reset(w)
+}
+
+// Size returns the size of the underlying buffer in bytes.
+func (b *Buffer) Size() int {
+	return b.b.Size()
+}
+
+// WriteString writes the contents of s into the buffer.
+func (b *Buffer) WriteString(s string) (n int, err error) {
+	return b.b.WriteString(s)
+}

+ 38 - 0
backend/vendor/github.com/a-h/templ/runtime/bufferpool.go

@@ -0,0 +1,38 @@
+package runtime
+
+import (
+	"io"
+	"sync"
+)
+
+var bufferPool = sync.Pool{
+	New: func() any {
+		return new(Buffer)
+	},
+}
+
+// GetBuffer creates and returns a new buffer if the writer is not already a buffer,
+// or returns the existing buffer if it is.
+func GetBuffer(w io.Writer) (b *Buffer, existing bool) {
+	if w == nil {
+		return nil, false
+	}
+	b, ok := w.(*Buffer)
+	if ok {
+		return b, true
+	}
+	b = bufferPool.Get().(*Buffer)
+	b.Reset(w)
+	return b, false
+}
+
+// ReleaseBuffer flushes the buffer and returns it to the pool.
+func ReleaseBuffer(w io.Writer) (err error) {
+	b, ok := w.(*Buffer)
+	if !ok {
+		return nil
+	}
+	err = b.Flush()
+	bufferPool.Put(b)
+	return err
+}

+ 8 - 0
backend/vendor/github.com/a-h/templ/runtime/builder.go

@@ -0,0 +1,8 @@
+package runtime
+
+import "strings"
+
+// GetBuilder returns a strings.Builder.
+func GetBuilder() (sb strings.Builder) {
+	return sb
+}

+ 21 - 0
backend/vendor/github.com/a-h/templ/runtime/runtime.go

@@ -0,0 +1,21 @@
+package runtime
+
+import (
+	"context"
+	"io"
+
+	"github.com/a-h/templ"
+)
+
+// GeneratedComponentInput is used to avoid generated code needing to import the `context` and `io` packages.
+type GeneratedComponentInput struct {
+	Context context.Context
+	Writer  io.Writer
+}
+
+// GeneratedTemplate is used to avoid generated code needing to import the `context` and `io` packages.
+func GeneratedTemplate(f func(GeneratedComponentInput) error) templ.Component {
+	return templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+		return f(GeneratedComponentInput{ctx, w})
+	})
+}

+ 168 - 0
backend/vendor/github.com/a-h/templ/safehtml/style.go

@@ -0,0 +1,168 @@
+// Adapted from https://raw.githubusercontent.com/google/safehtml/3c4cd5b5d8c9a6c5882fba099979e9f50b65c876/style.go
+
+// Copyright (c) 2017 The Go Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+package safehtml
+
+import (
+	"net/url"
+	"regexp"
+	"strings"
+)
+
+// SanitizeCSS attempts to sanitize CSS properties.
+func SanitizeCSS(property, value string) (string, string) {
+	property = SanitizeCSSProperty(property)
+	if property == InnocuousPropertyName {
+		return InnocuousPropertyName, InnocuousPropertyValue
+	}
+	return property, SanitizeCSSValue(property, value)
+}
+
+func SanitizeCSSValue(property, value string) string {
+	if sanitizer, ok := cssPropertyNameToValueSanitizer[property]; ok {
+		return sanitizer(value)
+	}
+	return sanitizeRegular(value)
+}
+
+func SanitizeCSSProperty(property string) string {
+	if !identifierPattern.MatchString(property) {
+		return InnocuousPropertyName
+	}
+	return strings.ToLower(property)
+}
+
+// identifierPattern matches a subset of valid <ident-token> values defined in
+// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name
+// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value.
+var identifierPattern = regexp.MustCompile(`^[-a-zA-Z]+$`)
+
+var cssPropertyNameToValueSanitizer = map[string]func(string) string{
+	"background-image":    sanitizeBackgroundImage,
+	"font-family":         sanitizeFontFamily,
+	"display":             sanitizeEnum,
+	"background-color":    sanitizeRegular,
+	"background-position": sanitizeRegular,
+	"background-repeat":   sanitizeRegular,
+	"background-size":     sanitizeRegular,
+	"color":               sanitizeRegular,
+	"height":              sanitizeRegular,
+	"width":               sanitizeRegular,
+	"left":                sanitizeRegular,
+	"right":               sanitizeRegular,
+	"top":                 sanitizeRegular,
+	"bottom":              sanitizeRegular,
+	"font-weight":         sanitizeRegular,
+	"padding":             sanitizeRegular,
+	"z-index":             sanitizeRegular,
+}
+
+var validURLPrefixes = []string{
+	`url("`,
+	`url('`,
+	`url(`,
+}
+
+var validURLSuffixes = []string{
+	`")`,
+	`')`,
+	`)`,
+}
+
+func sanitizeBackgroundImage(v string) string {
+	// Check for <> as per https://github.com/google/safehtml/blob/be23134998433fcf0135dda53593fc8f8bf4df7c/style.go#L87C2-L89C3
+	if strings.ContainsAny(v, "<>") {
+		return InnocuousPropertyValue
+	}
+	for _, u := range strings.Split(v, ",") {
+		u = strings.TrimSpace(u)
+		var found bool
+		for i, prefix := range validURLPrefixes {
+			if strings.HasPrefix(u, prefix) && strings.HasSuffix(u, validURLSuffixes[i]) {
+				found = true
+				u = strings.TrimPrefix(u, validURLPrefixes[i])
+				u = strings.TrimSuffix(u, validURLSuffixes[i])
+				break
+			}
+		}
+		if !found || !urlIsSafe(u) {
+			return InnocuousPropertyValue
+		}
+	}
+	return v
+}
+
+func urlIsSafe(s string) bool {
+	u, err := url.Parse(s)
+	if err != nil {
+		return false
+	}
+	if u.IsAbs() {
+		if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") || strings.EqualFold(u.Scheme, "mailto") {
+			return true
+		}
+		return false
+	}
+	return true
+}
+
+var genericFontFamilyName = regexp.MustCompile(`^[a-zA-Z][- a-zA-Z]+$`)
+
+func sanitizeFontFamily(s string) string {
+	for _, f := range strings.Split(s, ",") {
+		f = strings.TrimSpace(f)
+		if strings.HasPrefix(f, `"`) {
+			if !strings.HasSuffix(f, `"`) {
+				return InnocuousPropertyValue
+			}
+			continue
+		}
+		if !genericFontFamilyName.MatchString(f) {
+			return InnocuousPropertyValue
+		}
+	}
+	return s
+}
+
+func sanitizeEnum(s string) string {
+	if !safeEnumPropertyValuePattern.MatchString(s) {
+		return InnocuousPropertyValue
+	}
+	return s
+}
+
+func sanitizeRegular(s string) string {
+	if !safeRegularPropertyValuePattern.MatchString(s) {
+		return InnocuousPropertyValue
+	}
+	return s
+}
+
+// InnocuousPropertyName is an innocuous property generated by a sanitizer when its input is unsafe.
+const InnocuousPropertyName = "zTemplUnsafeCSSPropertyName"
+
+// InnocuousPropertyValue is an innocuous property generated by a sanitizer when its input is unsafe.
+const InnocuousPropertyValue = "zTemplUnsafeCSSPropertyValue"
+
+// safeRegularPropertyValuePattern matches strings that are safe to use as property values.
+// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune
+// (i.e. alphanumerics or runes in the set [+-.!#%_ \t]). This regex ensures that the following
+// are disallowed:
+//   - "/*" and "*/", which are CSS comment markers.
+//   - "//", even though this is not a comment marker in the CSS specification. Disallowing
+//     this string minimizes the chance that browser peculiarities or parsing bugs will allow
+//     sanitization to be bypassed.
+//   - '(' and ')', which can be used to call functions.
+//   - ',', since it can be used to inject extra values into a property.
+//   - Runes which could be matched on CSS error recovery of a previously malformed token, such as '@'
+//     and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling.
+var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`)
+
+// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values.
+// Specifically, it matches strings that contain only alphabetic and '-' runes.
+var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`)

+ 143 - 0
backend/vendor/github.com/a-h/templ/scripttemplate.go

@@ -0,0 +1,143 @@
+package templ
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"io"
+	"strings"
+)
+
+// ComponentScript is a templ Script template.
+type ComponentScript struct {
+	// Name of the script, e.g. print.
+	Name string
+	// Function to render.
+	Function string
+	// Call of the function in JavaScript syntax, including parameters, and
+	// ensures parameters are HTML escaped; useful for injecting into HTML
+	// attributes like onclick, onhover, etc.
+	//
+	// Given:
+	//    functionName("some string",12345)
+	// It would render:
+	//    __templ_functionName_sha(&#34;some string&#34;,12345))
+	//
+	// This is can be injected into HTML attributes:
+	//    <button onClick="__templ_functionName_sha(&#34;some string&#34;,12345))">Click Me</button>
+	Call string
+	// Call of the function in JavaScript syntax, including parameters. It
+	// does not HTML escape parameters; useful for directly calling in script
+	// elements.
+	//
+	// Given:
+	//    functionName("some string",12345)
+	// It would render:
+	//    __templ_functionName_sha("some string",12345))
+	//
+	// This is can be used to call the function inside a script tag:
+	//    <script>__templ_functionName_sha("some string",12345))</script>
+	CallInline string
+}
+
+var _ Component = ComponentScript{}
+
+func writeScriptHeader(ctx context.Context, w io.Writer) (err error) {
+	var nonceAttr string
+	if nonce := GetNonce(ctx); nonce != "" {
+		nonceAttr = " nonce=\"" + EscapeString(nonce) + "\""
+	}
+	_, err = fmt.Fprintf(w, `<script type="text/javascript"%s>`, nonceAttr)
+	return err
+}
+
+func (c ComponentScript) Render(ctx context.Context, w io.Writer) error {
+	err := RenderScriptItems(ctx, w, c)
+	if err != nil {
+		return err
+	}
+	if len(c.Call) > 0 {
+		if err = writeScriptHeader(ctx, w); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, c.CallInline); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, `</script>`); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// RenderScriptItems renders a <script> element, if the script has not already been rendered.
+func RenderScriptItems(ctx context.Context, w io.Writer, scripts ...ComponentScript) (err error) {
+	if len(scripts) == 0 {
+		return nil
+	}
+	_, v := getContext(ctx)
+	sb := new(strings.Builder)
+	for _, s := range scripts {
+		if !v.hasScriptBeenRendered(s.Name) {
+			sb.WriteString(s.Function)
+			v.addScript(s.Name)
+		}
+	}
+	if sb.Len() > 0 {
+		if err = writeScriptHeader(ctx, w); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, sb.String()); err != nil {
+			return err
+		}
+		if _, err = io.WriteString(w, `</script>`); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// JSExpression represents a JavaScript expression intended for use as an argument for script templates.
+// The string value of JSExpression will be inserted directly as JavaScript code in function call arguments.
+type JSExpression string
+
+// SafeScript encodes unknown parameters for safety for inside HTML attributes.
+func SafeScript(functionName string, params ...any) string {
+	encodedParams := safeEncodeScriptParams(true, params)
+	sb := new(strings.Builder)
+	sb.WriteString(functionName)
+	sb.WriteRune('(')
+	sb.WriteString(strings.Join(encodedParams, ","))
+	sb.WriteRune(')')
+	return sb.String()
+}
+
+// SafeScript encodes unknown parameters for safety for inline scripts.
+func SafeScriptInline(functionName string, params ...any) string {
+	encodedParams := safeEncodeScriptParams(false, params)
+	sb := new(strings.Builder)
+	sb.WriteString(functionName)
+	sb.WriteRune('(')
+	sb.WriteString(strings.Join(encodedParams, ","))
+	sb.WriteRune(')')
+	return sb.String()
+}
+
+func safeEncodeScriptParams(escapeHTML bool, params []any) []string {
+	encodedParams := make([]string, len(params))
+	for i := 0; i < len(encodedParams); i++ {
+		if val, ok := params[i].(JSExpression); ok {
+			encodedParams[i] = string(val)
+			continue
+		}
+
+		enc, _ := json.Marshal(params[i])
+		if !escapeHTML {
+			encodedParams[i] = string(enc)
+			continue
+		}
+		encodedParams[i] = EscapeString(string(enc))
+	}
+
+	return encodedParams
+}

BIN=BIN
backend/vendor/github.com/a-h/templ/templ.png


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio