changed deployment
This commit is contained in:
parent
ea4e057b02
commit
70396c321a
22 changed files with 391 additions and 52 deletions
|
|
@ -3,8 +3,9 @@ WORKDIR /src
|
|||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/api ./cmd/api \
|
||||
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/seed ./cmd/seed
|
||||
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/api ./cmd/api/main.go \
|
||||
&& CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/seed ./cmd/seed/main.go
|
||||
|
||||
FROM golang:1.27.1-alpine AS tools
|
||||
RUN GOBIN=/out go install github.com/pressly/goose/v3/cmd/goose@v3.26.0
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ module github.com/danyel/go-guess/backend
|
|||
go 1.27.1
|
||||
|
||||
require (
|
||||
github.com/danyel/go-loose v0.0.0-20261002165652-46030a5b144f
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/jackc/pgx/v5 v5.11.0
|
||||
github.com/ledongthuc/pdf v0.0.0-20260907135840-6c8c28e0e8a0
|
||||
github.com/pressly/goose/v3 v3.26.0
|
||||
github.com/rabbitmq/amqp091-go v1.15.0
|
||||
|
|
@ -72,8 +73,8 @@ require (
|
|||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sync v0.23.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
golang.org/x/text v0.42.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GK
|
|||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/danyel/go-loose v0.0.0-20261002165652-46030a5b144f h1:9GV60iNpwRsZONzZcFHsGxYNeU0iItiitOvFNLqlkkA=
|
||||
github.com/danyel/go-loose v0.0.0-20261002165652-46030a5b144f/go.mod h1:qOO4ay0c2MWiiLwz7WHCXCqDwxxA3sKTKKUnqbFgsOk=
|
||||
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=
|
||||
|
|
@ -57,8 +59,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/pgx/v5 v5.11.0 h1:IzBBtyK9AHqf98cctWFifYSci2hgQR/cd56wB4p+ogg=
|
||||
github.com/jackc/pgx/v5 v5.11.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
|
|
@ -119,8 +121,8 @@ github.com/rabbitmq/amqp091-go v1.15.0 h1:LEQL4/yp48/Wigt6A6XOu18RQRo8ZHtB5I/KZJ
|
|||
github.com/rabbitmq/amqp091-go v1.15.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
||||
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs=
|
||||
|
|
@ -166,8 +168,8 @@ golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
|||
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
|
||||
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -176,8 +178,8 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
|||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI=
|
||||
golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"gorm.io/gorm"
|
||||
|
||||
|
|
@ -641,7 +642,7 @@ func (h *Handler) FinishInterview(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (h *Handler) frontendURLForRequest(r *http.Request) string {
|
||||
tenant := strings.ToLower(strings.TrimSpace(r.Header.Get("X-Tenant-Id")))
|
||||
tenant := strings.ToLower(strings.TrimSpace(r.Header.Get(client.XTenantId)))
|
||||
origin := strings.TrimRight(strings.TrimSpace(r.Header.Get("Origin")), "/")
|
||||
if tenant == "" || origin == "" {
|
||||
return h.frontendURL
|
||||
|
|
|
|||
|
|
@ -3,13 +3,15 @@ package handler
|
|||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
)
|
||||
|
||||
func TestFrontendURLForTenantRequest(t *testing.T) {
|
||||
handler := &Handler{frontendURL: "http://localhost:5173"}
|
||||
request := httptest.NewRequest("GET", "/api/jobs/2/invitations", nil)
|
||||
request.Header.Set("Origin", "http://ypto.guess.local:5173")
|
||||
request.Header.Set("X-Tenant-Id", "ypto")
|
||||
request.Header.Set(client.XTenantId, "ypto")
|
||||
|
||||
if value := handler.frontendURLForRequest(request); value != "http://ypto.guess.local:5173" {
|
||||
t.Fatalf("unexpected frontend URL %q", value)
|
||||
|
|
@ -20,7 +22,7 @@ func TestFrontendURLRejectsDifferentTenantOrigin(t *testing.T) {
|
|||
handler := &Handler{frontendURL: "http://localhost:5173"}
|
||||
request := httptest.NewRequest("GET", "/api/jobs/2/invitations", nil)
|
||||
request.Header.Set("Origin", "http://nmbs.guess.local:5173")
|
||||
request.Header.Set("X-Tenant-Id", "ypto")
|
||||
request.Header.Set(client.XTenantId, "ypto")
|
||||
|
||||
if value := handler.frontendURLForRequest(request); value != handler.frontendURL {
|
||||
t.Fatalf("expected configured frontend URL, got %q", value)
|
||||
|
|
|
|||
99
backend/internal/web/middleware/GoLooseMiddleware.go
Normal file
99
backend/internal/web/middleware/GoLooseMiddleware.go
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
)
|
||||
|
||||
const guessApplication = "guess"
|
||||
|
||||
// A tenant used in the Go Loose host must be one DNS label.
|
||||
var safeTenantLabel = regexp.MustCompile(`^[A-Za-z0-9](?:[A-Za-z0-9_-]{0,62})?$`)
|
||||
|
||||
// GoLooseMiddleware authorizes a request with Go Loose. The tenant is taken
|
||||
// from the X-Tenant-Id header, not from process configuration.
|
||||
//
|
||||
// GO_LOOSE_BASE_URL may contain one %s placeholder for that tenant. The
|
||||
// default is https://%s.auth.dev. A URL without %s is used as-is.
|
||||
func GoLooseMiddleware() func(http.Handler) http.Handler {
|
||||
pattern := os.Getenv("GO_LOOSE_BASE_URL")
|
||||
if pattern == "" {
|
||||
pattern = "https://%s.auth.dev"
|
||||
}
|
||||
httpClient := &http.Client{Timeout: 2 * time.Second}
|
||||
var (
|
||||
mu sync.Mutex
|
||||
clients = map[string]*client.Client{}
|
||||
)
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tenantID := strings.TrimSpace(r.Header.Get(client.XTenantId))
|
||||
if tenantID == "" {
|
||||
writeGoLooseError(w, http.StatusBadRequest, "Missing X-Tenant-Id header")
|
||||
return
|
||||
}
|
||||
baseURL, err := goLooseBaseURL(pattern, tenantID)
|
||||
if err != nil {
|
||||
writeGoLooseError(w, http.StatusBadRequest, "Invalid characters in tenant ID")
|
||||
return
|
||||
}
|
||||
access, err := goLooseClient(&mu, clients, httpClient, baseURL, tenantID)
|
||||
if err != nil {
|
||||
slog.Error("configure go-loose client", "error", err)
|
||||
writeGoLooseError(w, http.StatusInternalServerError, "authorization unavailable")
|
||||
return
|
||||
}
|
||||
access.Middleware(next).ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func goLooseBaseURL(pattern, tenantID string) (string, error) {
|
||||
if !strings.Contains(pattern, "%s") {
|
||||
return pattern, nil
|
||||
}
|
||||
if !safeTenantLabel.MatchString(tenantID) {
|
||||
return "", fmt.Errorf("invalid tenant %q", tenantID)
|
||||
}
|
||||
return fmt.Sprintf(pattern, tenantID), nil
|
||||
}
|
||||
|
||||
func goLooseClient(
|
||||
mu *sync.Mutex,
|
||||
clients map[string]*client.Client,
|
||||
httpClient *http.Client,
|
||||
baseURL, tenantID string,
|
||||
) (*client.Client, error) {
|
||||
key := tenantID + "\x00" + baseURL
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if existing, ok := clients[key]; ok {
|
||||
return existing, nil
|
||||
}
|
||||
created, err := client.New(client.Config{
|
||||
BaseURL: baseURL,
|
||||
Tenant: tenantID,
|
||||
Application: guessApplication,
|
||||
HTTPClient: httpClient,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clients[key] = created
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func writeGoLooseError(w http.ResponseWriter, status int, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": message})
|
||||
}
|
||||
97
backend/internal/web/middleware/GoLooseMiddleware_test.go
Normal file
97
backend/internal/web/middleware/GoLooseMiddleware_test.go
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
)
|
||||
|
||||
func TestGoLooseMiddlewareUsesTenantFromRequest(t *testing.T) {
|
||||
const apiKey = "gl_test_key_that_is_long_enough_for_client"
|
||||
var gotTenant, gotApplication, gotKey string
|
||||
authorization := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotKey = r.Header.Get(client.DefaultHeader)
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("read authorization body: %v", err)
|
||||
}
|
||||
var payload map[string]string
|
||||
if err := json.Unmarshal(body, &payload); err != nil {
|
||||
t.Errorf("decode authorization body: %v", err)
|
||||
}
|
||||
gotTenant = payload["tenant"]
|
||||
gotApplication = payload["application"]
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write([]byte(`{"allowed":true,"tenant_slug":"ypto","application_slug":"guess","api_key_name":"guess"}`))
|
||||
}))
|
||||
defer authorization.Close()
|
||||
t.Setenv("GO_LOOSE_BASE_URL", authorization.URL)
|
||||
|
||||
var principalTenant string
|
||||
handler := GoLooseMiddleware()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := client.PrincipalFromContext(r.Context())
|
||||
if !ok {
|
||||
t.Fatal("missing Go Loose principal")
|
||||
}
|
||||
principalTenant = principal.TenantSlug
|
||||
if r.Header.Get(client.DefaultHeader) != "" {
|
||||
t.Fatal("API key was forwarded to the application")
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/jobs", nil)
|
||||
request.Header.Set(client.XTenantId, "ypto")
|
||||
request.Header.Set(client.DefaultHeader, apiKey)
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
if response.Code != http.StatusNoContent {
|
||||
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
if gotTenant != "ypto" || gotApplication != "guess" || gotKey != apiKey || principalTenant != "ypto" {
|
||||
t.Fatalf("tenant=%q application=%q key=%q principal=%q", gotTenant, gotApplication, gotKey, principalTenant)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoLooseMiddlewareRejectsMissingTenantAndKey(t *testing.T) {
|
||||
t.Setenv("GO_LOOSE_BASE_URL", "http://go-loose.test")
|
||||
handler := GoLooseMiddleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("handler reached without authorization")
|
||||
}))
|
||||
|
||||
missingTenant := httptest.NewRecorder()
|
||||
handler.ServeHTTP(missingTenant, httptest.NewRequest(http.MethodGet, "/api/jobs", nil))
|
||||
if missingTenant.Code != http.StatusBadRequest {
|
||||
t.Fatalf("missing tenant status = %d", missingTenant.Code)
|
||||
}
|
||||
|
||||
missingKey := httptest.NewRequest(http.MethodGet, "/api/jobs", nil)
|
||||
missingKey.Header.Set(client.XTenantId, "nmbs")
|
||||
missingKeyResponse := httptest.NewRecorder()
|
||||
handler.ServeHTTP(missingKeyResponse, missingKey)
|
||||
if missingKeyResponse.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("missing key status = %d", missingKeyResponse.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoLooseMiddlewareRejectsUnsafeTenantInHost(t *testing.T) {
|
||||
t.Setenv("GO_LOOSE_BASE_URL", "https://%s.auth.dev")
|
||||
handler := GoLooseMiddleware()(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("handler reached for unsafe tenant")
|
||||
}))
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/jobs", nil)
|
||||
request.Header.Set(client.XTenantId, "nmbs.evil.example")
|
||||
request.Header.Set(client.DefaultHeader, "gl_test_key")
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(response, request)
|
||||
|
||||
if response.Code != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ func TenantSchemaMiddleware(db *gorm.DB) func(http.Handler) http.Handler {
|
|||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
tenantID := r.Header.Get("X-Tenant-Id")
|
||||
tenantID := r.Header.Get(client.XTenantId)
|
||||
if tenantID == "" {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/danyel/go-loose/client"
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
|
|
@ -86,7 +88,10 @@ func (b *Builder) Add(method, path string, operation Operation) {
|
|||
"responses": responses,
|
||||
}
|
||||
if operation.Protected {
|
||||
value["security"] = []map[string][]string{{"bearerAuth": {}}}
|
||||
value["security"] = []map[string][]string{{
|
||||
"apiKeyAuth": {},
|
||||
"bearerAuth": {},
|
||||
}}
|
||||
}
|
||||
if operation.Request != nil {
|
||||
requestContentType := operation.RequestContentType
|
||||
|
|
@ -126,6 +131,7 @@ func (b *Builder) Document() map[string]any {
|
|||
"paths": b.paths,
|
||||
"components": map[string]any{
|
||||
"securitySchemes": map[string]any{
|
||||
"apiKeyAuth": map[string]any{"type": "apiKey", "in": "header", "name": client.DefaultHeader},
|
||||
"bearerAuth": map[string]any{"type": "http", "scheme": "bearer", "bearerFormat": "JWT"},
|
||||
},
|
||||
"schemas": b.schemas,
|
||||
|
|
@ -137,7 +143,7 @@ func (b *Builder) parameters(path string, queries []string, tenantRequired bool)
|
|||
result := make([]map[string]any, 0)
|
||||
if tenantRequired {
|
||||
result = append(result, map[string]any{
|
||||
"name": "X-Tenant-Id", "in": "header", "required": true,
|
||||
"name": client.XTenantId, "in": "header", "required": true,
|
||||
"schema": map[string]any{"type": "string"},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ func New(h *handler.Handler, tokens security.ITokenManager, frontendURL string)
|
|||
register(router, spec, false, http.MethodGet, "/api/participant-meetings/{token}/events", h.ParticipantMeetingEvents,
|
||||
openapi.Operation{Summary: "Stream participant meeting events", Response: eventbus.InterviewEvent{}, ResponseContentType: "text/event-stream"})
|
||||
router.Group(func(protected chi.Router) {
|
||||
protected.Use(mw.GoLooseMiddleware())
|
||||
protected.Use(authenticate(tokens))
|
||||
register(protected, spec, true, http.MethodGet, "/api/jobs", h.ListJobs,
|
||||
openapi.Operation{Summary: "List job postings", Response: []webmodel.JobResponse{}})
|
||||
|
|
@ -161,7 +162,7 @@ func cors(origin string) func(http.Handler) http.Handler {
|
|||
if allowedCORSOrigin(requestOrigin, origin) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", requestOrigin)
|
||||
}
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Tenant-Id")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-API-Key, X-Tenant-Id")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")
|
||||
w.Header().Add("Vary", "Origin")
|
||||
if r.Method == http.MethodOptions {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue