feat(internal/lib): common oapi middleware for go services (#3663)
This commit is contained in:
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -32,6 +32,7 @@ Where `PKG` is:
|
|||||||
- `deps`: For changes to dependencies
|
- `deps`: For changes to dependencies
|
||||||
- `docs`: For changes to the documentation
|
- `docs`: For changes to the documentation
|
||||||
- `examples`: For changes to the examples
|
- `examples`: For changes to the examples
|
||||||
|
- `internal/lib`: For changes to Nhost's common libraries (internal)
|
||||||
- `mintlify-openapi`: For changes to the Mintlify OpenAPI tool
|
- `mintlify-openapi`: For changes to the Mintlify OpenAPI tool
|
||||||
- `nhost-js`: For changes to the Nhost JavaScript SDK
|
- `nhost-js`: For changes to the Nhost JavaScript SDK
|
||||||
- `nixops`: For changes to the NixOps
|
- `nixops`: For changes to the NixOps
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ runs:
|
|||||||
|
|
||||||
# Define valid types and packages
|
# Define valid types and packages
|
||||||
VALID_TYPES="feat|fix|chore"
|
VALID_TYPES="feat|fix|chore"
|
||||||
VALID_PKGS="auth|ci|cli|codegen|dashboard|deps|docs|examples|mintlify-openapi|nhost-js|nixops|storage"
|
VALID_PKGS="auth|ci|cli|codegen|dashboard|deps|docs|examples|internal\/lib|mintlify-openapi|nhost-js|nixops|storage"
|
||||||
|
|
||||||
# Check if title matches the pattern TYPE(PKG): SUMMARY
|
# Check if title matches the pattern TYPE(PKG): SUMMARY
|
||||||
if [[ ! "$PR_TITLE" =~ ^(${VALID_TYPES})\((${VALID_PKGS})\):\ .+ ]]; then
|
if [[ ! "$PR_TITLE" =~ ^(${VALID_TYPES})\((${VALID_PKGS})\):\ .+ ]]; then
|
||||||
|
|||||||
1
.github/workflows/auth_checks.yaml
vendored
1
.github/workflows/auth_checks.yaml
vendored
@@ -17,6 +17,7 @@ on:
|
|||||||
- '.golangci.yaml'
|
- '.golangci.yaml'
|
||||||
- 'go.mod'
|
- 'go.mod'
|
||||||
- 'go.sum'
|
- 'go.sum'
|
||||||
|
- 'internal/lib/**'
|
||||||
- 'vendor/**'
|
- 'vendor/**'
|
||||||
|
|
||||||
# auth
|
# auth
|
||||||
|
|||||||
1
.github/workflows/storage_checks.yaml
vendored
1
.github/workflows/storage_checks.yaml
vendored
@@ -17,6 +17,7 @@ on:
|
|||||||
- '.golangci.yaml'
|
- '.golangci.yaml'
|
||||||
- 'go.mod'
|
- 'go.mod'
|
||||||
- 'go.sum'
|
- 'go.sum'
|
||||||
|
- 'internal/lib/**'
|
||||||
- 'vendor/**'
|
- 'vendor/**'
|
||||||
|
|
||||||
# storage
|
# storage
|
||||||
|
|||||||
@@ -119,6 +119,7 @@
|
|||||||
gofumpt
|
gofumpt
|
||||||
golangci-lint
|
golangci-lint
|
||||||
gqlgenc
|
gqlgenc
|
||||||
|
oapi-codegen
|
||||||
|
|
||||||
# internal packages
|
# internal packages
|
||||||
self.packages.${system}.codegen
|
self.packages.${system}.codegen
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -16,7 +16,6 @@ require (
|
|||||||
github.com/davidbyttow/govips/v2 v2.16.0
|
github.com/davidbyttow/govips/v2 v2.16.0
|
||||||
github.com/gabriel-vasile/mimetype v1.4.8
|
github.com/gabriel-vasile/mimetype v1.4.8
|
||||||
github.com/getkin/kin-openapi v0.133.0
|
github.com/getkin/kin-openapi v0.133.0
|
||||||
github.com/gin-contrib/cors v1.7.3
|
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/go-git/go-git/v5 v5.16.2
|
github.com/go-git/go-git/v5 v5.16.2
|
||||||
github.com/go-webauthn/webauthn v0.12.2
|
github.com/go-webauthn/webauthn v0.12.2
|
||||||
@@ -30,7 +29,6 @@ require (
|
|||||||
github.com/lmittmann/tint v1.0.7
|
github.com/lmittmann/tint v1.0.7
|
||||||
github.com/mark3labs/mcp-go v0.41.1
|
github.com/mark3labs/mcp-go v0.41.1
|
||||||
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48
|
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48
|
||||||
github.com/oapi-codegen/gin-middleware v1.0.2
|
|
||||||
github.com/oapi-codegen/runtime v1.1.1
|
github.com/oapi-codegen/runtime v1.1.1
|
||||||
github.com/pb33f/libopenapi v0.21.12
|
github.com/pb33f/libopenapi v0.21.12
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -162,8 +162,6 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
|
||||||
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
|
||||||
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns=
|
|
||||||
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4=
|
|
||||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
@@ -341,8 +339,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
|
|||||||
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48 h1:+Oh4Rbr1psWlBaQTakoBYFNB8jBioiXuimNMaNPLTHk=
|
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48 h1:+Oh4Rbr1psWlBaQTakoBYFNB8jBioiXuimNMaNPLTHk=
|
||||||
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48/go.mod h1:feVvqP3dft8hWbp9zNZExdGKbFEYv8aLYohfyAeINNQ=
|
github.com/nhost/be v0.0.0-20251021065906-8abc7d8dfa48/go.mod h1:feVvqP3dft8hWbp9zNZExdGKbFEYv8aLYohfyAeINNQ=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/oapi-codegen/gin-middleware v1.0.2 h1:/H99UzvHQAUxXK8pzdcGAZgjCVeXdFDAUUWaJT0k0eI=
|
|
||||||
github.com/oapi-codegen/gin-middleware v1.0.2/go.mod h1:2HJDQjH8jzK2/k/VKcWl+/T41H7ai2bKa6dN3AA2GpA=
|
|
||||||
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
|
||||||
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
|
||||||
|
|||||||
13
internal/lib/oapi/errors.go
Normal file
13
internal/lib/oapi/errors.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package oapi
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type AuthenticatorError struct {
|
||||||
|
Scheme string
|
||||||
|
Code string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AuthenticatorError) Error() string {
|
||||||
|
return fmt.Sprintf("security error [%s]: %s", e.Code, e.Message)
|
||||||
|
}
|
||||||
10
internal/lib/oapi/example/api/api.go
Normal file
10
internal/lib/oapi/example/api/api.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
//go:generate oapi-codegen -config server.cfg.yaml openapi.yaml
|
||||||
|
//go:generate oapi-codegen -config types.cfg.yaml openapi.yaml
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed openapi.yaml
|
||||||
|
var OpenAPISchema []byte
|
||||||
200
internal/lib/oapi/example/api/openapi.yaml
Normal file
200
internal/lib/oapi/example/api/openapi.yaml
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
openapi: "3.0.0"
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/signin/email-password:
|
||||||
|
post:
|
||||||
|
summary: Sign in with email and password
|
||||||
|
description: Authenticate a user with their email and password. Returns a session object or MFA challenge if two-factor authentication is enabled.
|
||||||
|
operationId: signInEmailPassword
|
||||||
|
requestBody:
|
||||||
|
description: User credentials for email and password authentication
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SignInEmailPasswordRequest"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/SignInEmailPasswordResponse"
|
||||||
|
description: "Authentication successful. If MFA is enabled, a challenge will be returned instead of a session."
|
||||||
|
default:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
description: "An error occurred while processing the request"
|
||||||
|
|
||||||
|
/user/email/change:
|
||||||
|
post:
|
||||||
|
summary: Change user email
|
||||||
|
description: Request to change the authenticated user's email address. A verification email will be sent to the new address to confirm the change. Requires elevated permissions.
|
||||||
|
operationId: changeUserEmail
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
security:
|
||||||
|
- BearerAuthElevated: []
|
||||||
|
requestBody:
|
||||||
|
description: New email address and optional redirect URL for email change
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserEmailChangeRequest"
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: >-
|
||||||
|
Email change requested. An email with a verification link has been sent to the new address
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/OKResponse"
|
||||||
|
default:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ErrorResponse"
|
||||||
|
description: "An error occurred while processing the request"
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
BearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
description: "Bearer authentication with JWT access token. Used to authenticate requests to protected endpoints."
|
||||||
|
BearerAuthElevated:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
description: "Bearer authentication that requires elevated permissions. Used for sensitive operations that may require additional security measures such as recent authentication. For details see https://docs.nhost.io/products/auth/elevated-permissions"
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
SignInEmailPasswordRequest:
|
||||||
|
type: object
|
||||||
|
description: "Request to authenticate using email and password"
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
description: "User's email address"
|
||||||
|
example: "john.smith@nhost.io"
|
||||||
|
format: email
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
description: "User's password"
|
||||||
|
example: "Str0ngPassw#ord-94|%"
|
||||||
|
minLength: 3
|
||||||
|
maxLength: 50
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- password
|
||||||
|
|
||||||
|
SignInEmailPasswordResponse:
|
||||||
|
type: object
|
||||||
|
description: "Response for email-password authentication that may include a session or MFA challenge"
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
session:
|
||||||
|
$ref: "#/components/schemas/Session"
|
||||||
|
|
||||||
|
Session:
|
||||||
|
type: object
|
||||||
|
description: "User authentication session containing tokens and user information"
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
accessToken:
|
||||||
|
type: string
|
||||||
|
description: "JWT token for authenticating API requests"
|
||||||
|
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||||
|
accessTokenExpiresIn:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: "Expiration time of the access token in seconds"
|
||||||
|
example: 900
|
||||||
|
refreshTokenId:
|
||||||
|
description: "Identifier for the refresh token"
|
||||||
|
example: "2c35b6f3-c4b9-48e3-978a-d4d0f1d42e24"
|
||||||
|
pattern: \b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b
|
||||||
|
type: string
|
||||||
|
refreshToken:
|
||||||
|
description: "Token used to refresh the access token"
|
||||||
|
example: "2c35b6f3-c4b9-48e3-978a-d4d0f1d42e24"
|
||||||
|
pattern: \b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- accessToken
|
||||||
|
- accessTokenExpiresIn
|
||||||
|
- refreshToken
|
||||||
|
- refreshTokenId
|
||||||
|
|
||||||
|
UserEmailChangeRequest:
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
newEmail:
|
||||||
|
description: A valid email
|
||||||
|
example: john.smith@nhost.io
|
||||||
|
format: email
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- newEmail
|
||||||
|
|
||||||
|
OKResponse:
|
||||||
|
type: string
|
||||||
|
additionalProperties: false
|
||||||
|
enum:
|
||||||
|
- OK
|
||||||
|
|
||||||
|
ErrorResponse:
|
||||||
|
type: object
|
||||||
|
description: "Standardized error response"
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
description: "HTTP status error code"
|
||||||
|
type: integer
|
||||||
|
example: 400
|
||||||
|
message:
|
||||||
|
description: "Human-friendly error message"
|
||||||
|
type: string
|
||||||
|
example: "Invalid email format"
|
||||||
|
error:
|
||||||
|
description: "Error code identifying the specific application error"
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- default-role-must-be-in-allowed-roles
|
||||||
|
- disabled-endpoint
|
||||||
|
- disabled-user
|
||||||
|
- email-already-in-use
|
||||||
|
- email-already-verified
|
||||||
|
- forbidden-anonymous
|
||||||
|
- internal-server-error
|
||||||
|
- invalid-email-password
|
||||||
|
- invalid-request
|
||||||
|
- locale-not-allowed
|
||||||
|
- password-too-short
|
||||||
|
- password-in-hibp-database
|
||||||
|
- redirectTo-not-allowed
|
||||||
|
- role-not-allowed
|
||||||
|
- signup-disabled
|
||||||
|
- unverified-user
|
||||||
|
- user-not-anonymous
|
||||||
|
- invalid-pat
|
||||||
|
- invalid-refresh-token
|
||||||
|
- invalid-ticket
|
||||||
|
- disabled-mfa-totp
|
||||||
|
- no-totp-secret
|
||||||
|
- invalid-totp
|
||||||
|
- mfa-type-not-found
|
||||||
|
- totp-already-active
|
||||||
|
- invalid-state
|
||||||
|
- oauth-token-echange-failed
|
||||||
|
- oauth-profile-fetch-failed
|
||||||
|
- oauth-provider-error
|
||||||
|
- invalid-otp
|
||||||
|
- cannot-send-sms
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
- message
|
||||||
|
- error
|
||||||
6
internal/lib/oapi/example/api/server.cfg.yaml
Normal file
6
internal/lib/oapi/example/api/server.cfg.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package: api
|
||||||
|
generate:
|
||||||
|
gin-server: true
|
||||||
|
embedded-spec: true
|
||||||
|
strict-server: true
|
||||||
|
output: server.gen.go
|
||||||
351
internal/lib/oapi/example/api/server.gen.go
Normal file
351
internal/lib/oapi/example/api/server.gen.go
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
// Package api provides primitives to interact with the openapi HTTP API.
|
||||||
|
//
|
||||||
|
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version 2.5.0 DO NOT EDIT.
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServerInterface represents all server handlers.
|
||||||
|
type ServerInterface interface {
|
||||||
|
// Sign in with email and password
|
||||||
|
// (POST /signin/email-password)
|
||||||
|
SignInEmailPassword(c *gin.Context)
|
||||||
|
// Change user email
|
||||||
|
// (POST /user/email/change)
|
||||||
|
ChangeUserEmail(c *gin.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInterfaceWrapper converts contexts to parameters.
|
||||||
|
type ServerInterfaceWrapper struct {
|
||||||
|
Handler ServerInterface
|
||||||
|
HandlerMiddlewares []MiddlewareFunc
|
||||||
|
ErrorHandler func(*gin.Context, error, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MiddlewareFunc func(c *gin.Context)
|
||||||
|
|
||||||
|
// SignInEmailPassword operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) SignInEmailPassword(c *gin.Context) {
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.SignInEmailPassword(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserEmail operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) ChangeUserEmail(c *gin.Context) {
|
||||||
|
|
||||||
|
c.Set(BearerAuthElevatedScopes, []string{})
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.ChangeUserEmail(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GinServerOptions provides options for the Gin server.
|
||||||
|
type GinServerOptions struct {
|
||||||
|
BaseURL string
|
||||||
|
Middlewares []MiddlewareFunc
|
||||||
|
ErrorHandler func(*gin.Context, error, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandlers creates http.Handler with routing matching OpenAPI spec.
|
||||||
|
func RegisterHandlers(router gin.IRouter, si ServerInterface) {
|
||||||
|
RegisterHandlersWithOptions(router, si, GinServerOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterHandlersWithOptions creates http.Handler with additional options
|
||||||
|
func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options GinServerOptions) {
|
||||||
|
errorHandler := options.ErrorHandler
|
||||||
|
if errorHandler == nil {
|
||||||
|
errorHandler = func(c *gin.Context, err error, statusCode int) {
|
||||||
|
c.JSON(statusCode, gin.H{"msg": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := ServerInterfaceWrapper{
|
||||||
|
Handler: si,
|
||||||
|
HandlerMiddlewares: options.Middlewares,
|
||||||
|
ErrorHandler: errorHandler,
|
||||||
|
}
|
||||||
|
|
||||||
|
router.POST(options.BaseURL+"/signin/email-password", wrapper.SignInEmailPassword)
|
||||||
|
router.POST(options.BaseURL+"/user/email/change", wrapper.ChangeUserEmail)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInEmailPasswordRequestObject struct {
|
||||||
|
Body *SignInEmailPasswordJSONRequestBody
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInEmailPasswordResponseObject interface {
|
||||||
|
VisitSignInEmailPasswordResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInEmailPassword200JSONResponse SignInEmailPasswordResponse
|
||||||
|
|
||||||
|
func (response SignInEmailPassword200JSONResponse) VisitSignInEmailPasswordResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignInEmailPassworddefaultJSONResponse struct {
|
||||||
|
Body ErrorResponse
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response SignInEmailPassworddefaultJSONResponse) VisitSignInEmailPasswordResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(response.StatusCode)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeUserEmailRequestObject struct {
|
||||||
|
Body *ChangeUserEmailJSONRequestBody
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeUserEmailResponseObject interface {
|
||||||
|
VisitChangeUserEmailResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeUserEmail200JSONResponse OKResponse
|
||||||
|
|
||||||
|
func (response ChangeUserEmail200JSONResponse) VisitChangeUserEmailResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeUserEmaildefaultJSONResponse struct {
|
||||||
|
Body ErrorResponse
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response ChangeUserEmaildefaultJSONResponse) VisitChangeUserEmailResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(response.StatusCode)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictServerInterface represents all server handlers.
|
||||||
|
type StrictServerInterface interface {
|
||||||
|
// Sign in with email and password
|
||||||
|
// (POST /signin/email-password)
|
||||||
|
SignInEmailPassword(ctx context.Context, request SignInEmailPasswordRequestObject) (SignInEmailPasswordResponseObject, error)
|
||||||
|
// Change user email
|
||||||
|
// (POST /user/email/change)
|
||||||
|
ChangeUserEmail(ctx context.Context, request ChangeUserEmailRequestObject) (ChangeUserEmailResponseObject, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StrictHandlerFunc = strictgin.StrictGinHandlerFunc
|
||||||
|
type StrictMiddlewareFunc = strictgin.StrictGinMiddlewareFunc
|
||||||
|
|
||||||
|
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
|
||||||
|
return &strictHandler{ssi: ssi, middlewares: middlewares}
|
||||||
|
}
|
||||||
|
|
||||||
|
type strictHandler struct {
|
||||||
|
ssi StrictServerInterface
|
||||||
|
middlewares []StrictMiddlewareFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignInEmailPassword operation middleware
|
||||||
|
func (sh *strictHandler) SignInEmailPassword(ctx *gin.Context) {
|
||||||
|
var request SignInEmailPasswordRequestObject
|
||||||
|
|
||||||
|
var body SignInEmailPasswordJSONRequestBody
|
||||||
|
if err := ctx.ShouldBindJSON(&body); err != nil {
|
||||||
|
ctx.Status(http.StatusBadRequest)
|
||||||
|
ctx.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
request.Body = &body
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.SignInEmailPassword(ctx, request.(SignInEmailPasswordRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "SignInEmailPassword")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(SignInEmailPasswordResponseObject); ok {
|
||||||
|
if err := validResponse.VisitSignInEmailPasswordResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeUserEmail operation middleware
|
||||||
|
func (sh *strictHandler) ChangeUserEmail(ctx *gin.Context) {
|
||||||
|
var request ChangeUserEmailRequestObject
|
||||||
|
|
||||||
|
var body ChangeUserEmailJSONRequestBody
|
||||||
|
if err := ctx.ShouldBindJSON(&body); err != nil {
|
||||||
|
ctx.Status(http.StatusBadRequest)
|
||||||
|
ctx.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
request.Body = &body
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.ChangeUserEmail(ctx, request.(ChangeUserEmailRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "ChangeUserEmail")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(ChangeUserEmailResponseObject); ok {
|
||||||
|
if err := validResponse.VisitChangeUserEmailResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64 encoded, gzipped, json marshaled Swagger object
|
||||||
|
var swaggerSpec = []string{
|
||||||
|
|
||||||
|
"H4sIAAAAAAAC/9RYUXPbuBH+KxhcO30RJMVW0lhP9WV8rXLXS8Z22s7YfoCApYiYBHhY0IrO1X/vLEBK",
|
||||||
|
"pEQ1vs5dp32yTBCL3f2+b3fBZ65cWTkLNiCfP3NUOZQy/rzy3vlrwMpZBHogtTbBOCuLj95V4IMB5PNM",
|
||||||
|
"FggjrgGVNxWt8zm/CdJq6bX5GTQDMsR8a2nEq872Zx6X6UffRDyeKaeBGQ02mGxj7IqFHBhWoExmFJNV",
|
||||||
|
"VRglaUc6hY842Lrk8zuuIZN1EYR3BYiyxiCWIIwVsijcGnR8jnzEtUG5LEALsLpyxobusxoh2iylKYQs",
|
||||||
|
"PEi9ISN1jKP/+Am8yQxoPuKZ80ujNVghrbOb0tV0krEBvJWFQPBP4EXrsbFPsjBaJHOVRFw7rzsLHn6q",
|
||||||
|
"AcmxwilZgLAutHFQOpsdIjgnMHc+dB8aK3KzrISWQS5l9NuDNh5UuHUHlmKu+o/QrGxdiTYjfMRr20ba",
|
||||||
|
"pof+pG29aJPzlQy9UDIPmIvgHsF2ngejHqGX+jKTIrhQ8RG3Lv4SCMpD11qzHl/dVMn1zNWW3Iw7Wmyk",
|
||||||
|
"CuYJOjsxyED/O1mHxhsBKpd2BSKTJkWaFivvMlOAyCCofGDxyegBMJNnSlryCcFqgSXyhxEnR/mcY/DG",
|
||||||
|
"rvh2xEtAlCs4VsBf6lJakXkDVhebRkbt2yMOX2RZFWRrkc5kkUAsc76MOT86iYKuceCg29uPLC02p5Ds",
|
||||||
|
"ukfMptOdPaLxCjzfbolJP9XGgybBNdb3AY0aae+DdsvPoAK58uH7F1eWVtAfvh9M3w0gxjB+UYH6hOAZ",
|
||||||
|
"IUiVpakgmCwx5WyQxsZqQ8RAJq1mRHJmbMouGTmsY1IpQLyNxD5K8fu/3yZjBE/vYLtilx8XrNE49oCF",
|
||||||
|
"zft8+WdlPpj3i08/L179aBa4sNev1bvFm8Vj9Y+/vXt/MR6Ph7DueHP1pTIecDHgVlxK0QdTAnNZLLBp",
|
||||||
|
"c+OwocwoZ3XPtwtiREO1yIk3M35MEWJIFPyJtMTHlFrNgmPNu0cu9HJyps5fL99k50LNlhdi9hbOxcUf",
|
||||||
|
"30qhZ3qavdKzMzibxfoXqNryOb+/X95NxYUU2cPz2+39/VLs/p1tT/7u7np1RtuGstyNbqGP41ukzmXA",
|
||||||
|
"R9wpsF2Q/8uRHUi7S+0T1DpA+ig1Q0Xgxqzswl5R1frYtKvrptX9MjU3u4hDHWkBq5HklcoiabjTWw9m",
|
||||||
|
"EHrlGD6qEn/A1oDWHrAv0M8ut2MsTcj/ZHOHYWwc78gimR3gzc6RU0d2PN2fdhP81K5iqr6h1n4x++fv",
|
||||||
|
"qd7KLz+AXYWcz19PR7w0tv33/GvAtg7ujnsxTP/RWNhui2LojzuHxTjkMrBSbpixqqg1MLmrz86zv353",
|
||||||
|
"yVQuiwLs6niixH1L+J2HjM/5N5P9mDtpZtxJ2zkoKUdBEw4x5HdxKHgpL/uOWFhfDTPrknVa9q9AqQNc",
|
||||||
|
"dwcf40lzAKjam7C5oUQkT78F6cFf1sSaQ1/T2iFAaxNyRm2tW6nH7FNTy3s6bJsbLVTeBVCB7gXNwI3U",
|
||||||
|
"wCIo5OcynraPMA+hIkD2Hl4V8CQD6Jd6GqnUZAcZNLtZBb40kQHYuE2sRLBoaFhkBGQ0gHsyNlbYngOs",
|
||||||
|
"TSYrQWJNJ2CtciaReVBgw4E3Y/ad80xDkKZAhgCMAsT5ZKKdwnEL+aTyTtcq4IS2T1qnRcfpryeNsKaJ",
|
||||||
|
"hc9tXRQj7iqwsjJ8zs/H0/E09ZI84j+hOd/YycEdZP7MK5dof8DfLrwyDUeRESEH4wdK7phdQ6g9DVN7",
|
||||||
|
"IUdKHumZmYyFtROZVMEdoWmQgY1XBKLNDiTqvkOViidhAIZvnd5QIDTegU1S3t8fJ58xVYxUHb5aO063",
|
||||||
|
"rpj2gWlTeYjTgCxwX/96OToIlXc1HXwNUeSpgkbQzqbT3zagpsgPRHR5MDrXsQhkdTFmiyzCucdpxGQH",
|
||||||
|
"3LUpCrakmkB0AM2MxQBS0+y5o8aYxxPjDf5XC7H/QWMoqOYrAnNK1d6DZuvcFEAli6JrPz/4HdAjjnVZ",
|
||||||
|
"Sr9puEezclTBwMhBb09IJ0ljk3TZPK2wzkyTXk1jcUd36U5yOJ+M2SVLF/T2w0hcbdOOVJKCi8YsrNtd",
|
||||||
|
"8RhnM+PLuJSOJNH+u7J5JMDULHe98zcS34nePIDoj7DuZydi4qqmdrcfQtin6x86mmyg+W/Kr3MfHgjj",
|
||||||
|
"quNWSz/QY3a5RzfkTPZxL4x9ZLlEtgSwp3D/v9FZ02X5/O55cBS4e9g+dOWYqJEa025mkiuk6Sh+tnrY",
|
||||||
|
"brfbfwUAAP//3ciGL/8UAAA=",
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSwagger returns the content of the embedded swagger specification file
|
||||||
|
// or error if failed to decode
|
||||||
|
func decodeSpec() ([]byte, error) {
|
||||||
|
zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, ""))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error base64 decoding spec: %w", err)
|
||||||
|
}
|
||||||
|
zr, err := gzip.NewReader(bytes.NewReader(zipped))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decompressing spec: %w", err)
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err = buf.ReadFrom(zr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error decompressing spec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawSpec = decodeSpecCached()
|
||||||
|
|
||||||
|
// a naive cached of a decoded swagger spec
|
||||||
|
func decodeSpecCached() func() ([]byte, error) {
|
||||||
|
data, err := decodeSpec()
|
||||||
|
return func() ([]byte, error) {
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructs a synthetic filesystem for resolving external references when loading openapi specifications.
|
||||||
|
func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) {
|
||||||
|
res := make(map[string]func() ([]byte, error))
|
||||||
|
if len(pathToFile) > 0 {
|
||||||
|
res[pathToFile] = rawSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSwagger returns the Swagger specification corresponding to the generated code
|
||||||
|
// in this file. The external references of Swagger specification are resolved.
|
||||||
|
// The logic of resolving external references is tightly connected to "import-mapping" feature.
|
||||||
|
// Externally referenced files must be embedded in the corresponding golang packages.
|
||||||
|
// Urls can be supported but this task was out of the scope.
|
||||||
|
func GetSwagger() (swagger *openapi3.T, err error) {
|
||||||
|
resolvePath := PathToRawSpec("")
|
||||||
|
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
loader.IsExternalRefsAllowed = true
|
||||||
|
loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) {
|
||||||
|
pathToFile := url.String()
|
||||||
|
pathToFile = path.Clean(pathToFile)
|
||||||
|
getSpec, ok := resolvePath[pathToFile]
|
||||||
|
if !ok {
|
||||||
|
err1 := fmt.Errorf("path not found: %s", pathToFile)
|
||||||
|
return nil, err1
|
||||||
|
}
|
||||||
|
return getSpec()
|
||||||
|
}
|
||||||
|
var specData []byte
|
||||||
|
specData, err = rawSpec()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
swagger, err = loader.LoadFromData(specData)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
4
internal/lib/oapi/example/api/types.cfg.yaml
Normal file
4
internal/lib/oapi/example/api/types.cfg.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package: api
|
||||||
|
generate:
|
||||||
|
models: true
|
||||||
|
output: types.gen.go
|
||||||
112
internal/lib/oapi/example/api/types.gen.go
Normal file
112
internal/lib/oapi/example/api/types.gen.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
// Package api provides primitives to interact with the openapi HTTP API.
|
||||||
|
//
|
||||||
|
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version 2.5.0 DO NOT EDIT.
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BearerAuthElevatedScopes = "BearerAuthElevated.Scopes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for ErrorResponseError.
|
||||||
|
const (
|
||||||
|
CannotSendSms ErrorResponseError = "cannot-send-sms"
|
||||||
|
DefaultRoleMustBeInAllowedRoles ErrorResponseError = "default-role-must-be-in-allowed-roles"
|
||||||
|
DisabledEndpoint ErrorResponseError = "disabled-endpoint"
|
||||||
|
DisabledMfaTotp ErrorResponseError = "disabled-mfa-totp"
|
||||||
|
DisabledUser ErrorResponseError = "disabled-user"
|
||||||
|
EmailAlreadyInUse ErrorResponseError = "email-already-in-use"
|
||||||
|
EmailAlreadyVerified ErrorResponseError = "email-already-verified"
|
||||||
|
ForbiddenAnonymous ErrorResponseError = "forbidden-anonymous"
|
||||||
|
InternalServerError ErrorResponseError = "internal-server-error"
|
||||||
|
InvalidEmailPassword ErrorResponseError = "invalid-email-password"
|
||||||
|
InvalidOtp ErrorResponseError = "invalid-otp"
|
||||||
|
InvalidPat ErrorResponseError = "invalid-pat"
|
||||||
|
InvalidRefreshToken ErrorResponseError = "invalid-refresh-token"
|
||||||
|
InvalidRequest ErrorResponseError = "invalid-request"
|
||||||
|
InvalidState ErrorResponseError = "invalid-state"
|
||||||
|
InvalidTicket ErrorResponseError = "invalid-ticket"
|
||||||
|
InvalidTotp ErrorResponseError = "invalid-totp"
|
||||||
|
LocaleNotAllowed ErrorResponseError = "locale-not-allowed"
|
||||||
|
MfaTypeNotFound ErrorResponseError = "mfa-type-not-found"
|
||||||
|
NoTotpSecret ErrorResponseError = "no-totp-secret"
|
||||||
|
OauthProfileFetchFailed ErrorResponseError = "oauth-profile-fetch-failed"
|
||||||
|
OauthProviderError ErrorResponseError = "oauth-provider-error"
|
||||||
|
OauthTokenEchangeFailed ErrorResponseError = "oauth-token-echange-failed"
|
||||||
|
PasswordInHibpDatabase ErrorResponseError = "password-in-hibp-database"
|
||||||
|
PasswordTooShort ErrorResponseError = "password-too-short"
|
||||||
|
RedirectToNotAllowed ErrorResponseError = "redirectTo-not-allowed"
|
||||||
|
RoleNotAllowed ErrorResponseError = "role-not-allowed"
|
||||||
|
SignupDisabled ErrorResponseError = "signup-disabled"
|
||||||
|
TotpAlreadyActive ErrorResponseError = "totp-already-active"
|
||||||
|
UnverifiedUser ErrorResponseError = "unverified-user"
|
||||||
|
UserNotAnonymous ErrorResponseError = "user-not-anonymous"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for OKResponse.
|
||||||
|
const (
|
||||||
|
OK OKResponse = "OK"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorResponse Standardized error response
|
||||||
|
type ErrorResponse struct {
|
||||||
|
// Error Error code identifying the specific application error
|
||||||
|
Error ErrorResponseError `json:"error"`
|
||||||
|
|
||||||
|
// Message Human-friendly error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
|
// Status HTTP status error code
|
||||||
|
Status int `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorResponseError Error code identifying the specific application error
|
||||||
|
type ErrorResponseError string
|
||||||
|
|
||||||
|
// OKResponse defines model for OKResponse.
|
||||||
|
type OKResponse string
|
||||||
|
|
||||||
|
// Session User authentication session containing tokens and user information
|
||||||
|
type Session struct {
|
||||||
|
// AccessToken JWT token for authenticating API requests
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
|
||||||
|
// AccessTokenExpiresIn Expiration time of the access token in seconds
|
||||||
|
AccessTokenExpiresIn int64 `json:"accessTokenExpiresIn"`
|
||||||
|
|
||||||
|
// RefreshToken Token used to refresh the access token
|
||||||
|
RefreshToken string `json:"refreshToken"`
|
||||||
|
|
||||||
|
// RefreshTokenId Identifier for the refresh token
|
||||||
|
RefreshTokenId string `json:"refreshTokenId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignInEmailPasswordRequest Request to authenticate using email and password
|
||||||
|
type SignInEmailPasswordRequest struct {
|
||||||
|
// Email User's email address
|
||||||
|
Email openapi_types.Email `json:"email"`
|
||||||
|
|
||||||
|
// Password User's password
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignInEmailPasswordResponse Response for email-password authentication that may include a session or MFA challenge
|
||||||
|
type SignInEmailPasswordResponse struct {
|
||||||
|
// Session User authentication session containing tokens and user information
|
||||||
|
Session *Session `json:"session,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserEmailChangeRequest defines model for UserEmailChangeRequest.
|
||||||
|
type UserEmailChangeRequest struct {
|
||||||
|
// NewEmail A valid email
|
||||||
|
NewEmail openapi_types.Email `json:"newEmail"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignInEmailPasswordJSONRequestBody defines body for SignInEmailPassword for application/json ContentType.
|
||||||
|
type SignInEmailPasswordJSONRequestBody = SignInEmailPasswordRequest
|
||||||
|
|
||||||
|
// ChangeUserEmailJSONRequestBody defines body for ChangeUserEmail for application/json ContentType.
|
||||||
|
type ChangeUserEmailJSONRequestBody = UserEmailChangeRequest
|
||||||
49
internal/lib/oapi/example/controller/controller.go
Normal file
49
internal/lib/oapi/example/controller/controller.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/example/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct{}
|
||||||
|
|
||||||
|
func NewController() *Controller {
|
||||||
|
return &Controller{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) SignInEmailPassword( //nolint:ireturn
|
||||||
|
_ context.Context, req api.SignInEmailPasswordRequestObject,
|
||||||
|
) (api.SignInEmailPasswordResponseObject, error) {
|
||||||
|
switch req.Body.Email {
|
||||||
|
case "bad@email.com":
|
||||||
|
return api.SignInEmailPassworddefaultJSONResponse{
|
||||||
|
Body: api.ErrorResponse{
|
||||||
|
Error: api.DisabledUser,
|
||||||
|
Message: "The user account is disabled.",
|
||||||
|
Status: http.StatusConflict,
|
||||||
|
},
|
||||||
|
StatusCode: http.StatusConflict,
|
||||||
|
}, nil
|
||||||
|
case "crash@email.com":
|
||||||
|
return nil, errors.New("simulated server crash") //nolint:err113
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.SignInEmailPassword200JSONResponse{
|
||||||
|
Session: &api.Session{
|
||||||
|
AccessToken: "access_token_example",
|
||||||
|
AccessTokenExpiresIn: 900, //nolint:mnd
|
||||||
|
RefreshToken: "refresh_token_example",
|
||||||
|
RefreshTokenId: "refresh_token_id_example",
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) ChangeUserEmail( //nolint:ireturn
|
||||||
|
_ context.Context,
|
||||||
|
_ api.ChangeUserEmailRequestObject,
|
||||||
|
) (api.ChangeUserEmailResponseObject, error) {
|
||||||
|
return api.ChangeUserEmail200JSONResponse(api.OK), nil
|
||||||
|
}
|
||||||
109
internal/lib/oapi/example/main.go
Normal file
109
internal/lib/oapi/example/main.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/lmittmann/tint"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/example/api"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/example/controller"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiPrefix = "/"
|
||||||
|
|
||||||
|
func getLogger() *slog.Logger {
|
||||||
|
handler := tint.NewHandler(os.Stdout, &tint.Options{
|
||||||
|
AddSource: true,
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
TimeFormat: time.StampMilli,
|
||||||
|
NoColor: false,
|
||||||
|
ReplaceAttr: nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
return slog.New(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authFn(
|
||||||
|
ctx context.Context,
|
||||||
|
input *openapi3filter.AuthenticationInput,
|
||||||
|
) error {
|
||||||
|
_, ok := ctx.Value(oapi.GinContextKey).(*gin.Context)
|
||||||
|
if !ok {
|
||||||
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "unable to get context",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "your access token is invalid",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRouter(logger *slog.Logger) (*gin.Engine, error) {
|
||||||
|
ctrl := controller.NewController()
|
||||||
|
handler := api.NewStrictHandler(ctrl, []api.StrictMiddlewareFunc{})
|
||||||
|
|
||||||
|
router, mw, err := oapi.NewRouter(
|
||||||
|
api.OpenAPISchema,
|
||||||
|
apiPrefix,
|
||||||
|
authFn,
|
||||||
|
middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
},
|
||||||
|
logger,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create oapi router: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.RegisterHandlersWithOptions(
|
||||||
|
router,
|
||||||
|
handler,
|
||||||
|
api.GinServerOptions{
|
||||||
|
BaseURL: apiPrefix,
|
||||||
|
Middlewares: []api.MiddlewareFunc{mw},
|
||||||
|
ErrorHandler: nil,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return router, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(ctx context.Context) error {
|
||||||
|
logger := getLogger()
|
||||||
|
|
||||||
|
router, err := setupRouter(logger) //nolint:contextcheck
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
server := &http.Server{ //nolint:exhaustruct
|
||||||
|
Addr: ":8080",
|
||||||
|
Handler: router,
|
||||||
|
ReadHeaderTimeout: 5 * time.Second, //nolint:mnd
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
logger.ErrorContext(ctx, "server failed", slog.String("error", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := run(context.Background()); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
177
internal/lib/oapi/example/main_test.go
Normal file
177
internal/lib/oapi/example/main_test.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeRequest(
|
||||||
|
router *gin.Engine,
|
||||||
|
method, path string,
|
||||||
|
headers map[string]string,
|
||||||
|
body io.Reader,
|
||||||
|
) *httptest.ResponseRecorder {
|
||||||
|
req := httptest.NewRequest(method, path, body)
|
||||||
|
|
||||||
|
for key, value := range headers {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequests(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logger := getLogger()
|
||||||
|
|
||||||
|
router, err := setupRouter(logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to set up router: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
headers map[string]string
|
||||||
|
body io.Reader
|
||||||
|
expectedStatus int
|
||||||
|
expectedResponse string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "success",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(`{"email": "asd@asd.com", "password": "p4ssw0rd"}`),
|
||||||
|
expectedStatus: http.StatusOK,
|
||||||
|
expectedResponse: "{\"session\":{\"accessToken\":\"access_token_example\",\"accessTokenExpiresIn\":900,\"refreshToken\":\"refresh_token_example\",\"refreshTokenId\":\"refresh_token_id_example\"}}\n", //nolint:lll
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "expected error",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(
|
||||||
|
`{"email": "bad@email.com", "password": "p4ssw0rd"}`,
|
||||||
|
),
|
||||||
|
expectedStatus: http.StatusConflict,
|
||||||
|
expectedResponse: "{\"error\":\"disabled-user\",\"message\":\"The user account is disabled.\",\"status\":409}\n", //nolint:lll
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "unexpected error",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(
|
||||||
|
`{"email": "crash@email.com", "password": "p4ssw0rd"}`,
|
||||||
|
),
|
||||||
|
expectedStatus: http.StatusInternalServerError,
|
||||||
|
expectedResponse: `{"errors":"internal-server-error","message":"simulated server crash"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "missing body",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: nil,
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedResponse: `{"error":"request-validation-error","reason":"value is required but missing"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "wrong param",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(
|
||||||
|
`{"wrong":"asd", "email": "asd@asd.com", "password": "p4ssw0rd"}`,
|
||||||
|
),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedResponse: `{"error":"schema-validation-error","reason":"property \"wrong\" is unsupported"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "missing param",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(`{"email": "asd@asd.com"}`),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedResponse: `{"error":"schema-validation-error","reason":"property \"password\" is missing"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "invalid param",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/signin/email-password",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(`{"email": "asdasd.com", "password": "p4ssw0rd"}`),
|
||||||
|
expectedStatus: http.StatusBadRequest,
|
||||||
|
expectedResponse: `{"errors":"bad-request","message":"email: failed to pass regex validation"}`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "needs security",
|
||||||
|
method: http.MethodPost,
|
||||||
|
path: "/user/email/change",
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: strings.NewReader(`{"newEmail": "new@asd.com"`),
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
expectedResponse: `{"error":"unauthorized","reason":"your access token is invalid","securityScheme":"BearerAuthElevated"}`, //nolint:lll
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
w := makeRequest(router, tc.method, tc.path, tc.headers, tc.body)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read response body: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != tc.expectedStatus {
|
||||||
|
t.Errorf("Expected status %d, got %d", tc.expectedStatus, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(string(body), tc.expectedResponse); diff != "" {
|
||||||
|
t.Errorf("Response body mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
140
internal/lib/oapi/middleware/cors.go
Normal file
140
internal/lib/oapi/middleware/cors.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CORSOptions configures the CORS middleware behavior.
|
||||||
|
//
|
||||||
|
// The middleware supports three strategies for handling Access-Control-Allow-Headers:
|
||||||
|
// - nil (default): Reflects the Access-Control-Request-Headers from the client
|
||||||
|
// - empty slice: Denies all headers (no Access-Control-Allow-Headers header is set)
|
||||||
|
// - non-empty slice: Uses the specified headers
|
||||||
|
type CORSOptions struct {
|
||||||
|
// AllowedOrigins is a list of origins permitted to make cross-origin requests.
|
||||||
|
// Use "*" or nil slice to allow all origins.
|
||||||
|
AllowedOrigins []string
|
||||||
|
|
||||||
|
// AllowedMethods is a list of HTTP methods the client is permitted to use.
|
||||||
|
// Common values: GET, POST, PUT, DELETE, PATCH, OPTIONS.
|
||||||
|
AllowedMethods []string
|
||||||
|
|
||||||
|
// AllowedHeaders controls which headers clients can use in requests.
|
||||||
|
// - nil: reflects client's Access-Control-Request-Headers (permissive)
|
||||||
|
// - empty slice: denies all headers
|
||||||
|
// - non-empty: allows only specified headers
|
||||||
|
AllowedHeaders []string
|
||||||
|
|
||||||
|
// ExposedHeaders lists headers that browsers are allowed to access.
|
||||||
|
// By default, browsers only expose simple response headers.
|
||||||
|
ExposedHeaders []string
|
||||||
|
|
||||||
|
// AllowCredentials indicates whether the request can include credentials
|
||||||
|
// (cookies, authorization headers, or TLS client certificates).
|
||||||
|
AllowCredentials bool
|
||||||
|
|
||||||
|
// MaxAge indicates how long (in seconds) the results of a preflight request
|
||||||
|
// can be cached. Empty string means no caching directive is sent.
|
||||||
|
MaxAge string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS returns a Gin middleware handler that implements Cross-Origin Resource Sharing (CORS).
|
||||||
|
//
|
||||||
|
// The middleware handles both preflight (OPTIONS) requests and actual requests, setting
|
||||||
|
// appropriate CORS headers based on the provided configuration. It automatically adds
|
||||||
|
// the "Vary: Origin, Access-Control-Request-Method" header for proper cache behavior.
|
||||||
|
//
|
||||||
|
// For preflight requests (OPTIONS), the middleware responds with 204 No Content and
|
||||||
|
// prevents further request processing. For actual requests, it sets CORS headers and
|
||||||
|
// continues the middleware chain.
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// router.Use(middleware.CORS(middleware.CORSOptions{
|
||||||
|
// AllowedOrigins: []string{"https://example.com", "https://app.example.com"},
|
||||||
|
// AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||||
|
// AllowedHeaders: nil, // reflects client headers
|
||||||
|
// AllowCredentials: true,
|
||||||
|
// MaxAge: "3600",
|
||||||
|
// }))
|
||||||
|
func CORS(opts CORSOptions) gin.HandlerFunc { //nolint:cyclop,funlen
|
||||||
|
allowedMethods := strings.Join(opts.AllowedMethods, ", ")
|
||||||
|
exposedHeaders := strings.Join(opts.ExposedHeaders, ", ")
|
||||||
|
|
||||||
|
allowCredentials := "false"
|
||||||
|
if opts.AllowCredentials {
|
||||||
|
allowCredentials = "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
headerStrategy string // "reflect", "specific", or "deny"
|
||||||
|
allowedHeaders string
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case opts.AllowedHeaders == nil:
|
||||||
|
headerStrategy = "reflect"
|
||||||
|
case len(opts.AllowedHeaders) == 0:
|
||||||
|
headerStrategy = "deny"
|
||||||
|
default:
|
||||||
|
headerStrategy = "specific"
|
||||||
|
allowedHeaders = strings.Join(opts.AllowedHeaders, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := func(c *gin.Context, origin string) {
|
||||||
|
if opts.AllowedOrigins != nil &&
|
||||||
|
!slices.Contains(opts.AllowedOrigins, origin) &&
|
||||||
|
!slices.Contains(opts.AllowedOrigins, "*") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("Access-Control-Allow-Origin", origin)
|
||||||
|
c.Header("Access-Control-Allow-Methods", allowedMethods)
|
||||||
|
|
||||||
|
// Handle allowed headers based on strategy
|
||||||
|
switch headerStrategy {
|
||||||
|
case "specific":
|
||||||
|
c.Header("Access-Control-Allow-Headers", allowedHeaders)
|
||||||
|
case "reflect":
|
||||||
|
headers := c.Request.Header.Get("Access-Control-Request-Headers")
|
||||||
|
if headers != "" {
|
||||||
|
c.Header("Access-Control-Allow-Headers", headers)
|
||||||
|
}
|
||||||
|
case "deny":
|
||||||
|
// Don't set the header at all
|
||||||
|
}
|
||||||
|
|
||||||
|
if exposedHeaders != "" {
|
||||||
|
c.Header("Access-Control-Expose-Headers", exposedHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("Access-Control-Allow-Credentials", allowCredentials)
|
||||||
|
|
||||||
|
if opts.MaxAge != "" {
|
||||||
|
c.Header("Access-Control-Max-Age", opts.MaxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Writer.Header().Add("Vary", "Origin, Access-Control-Request-Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
origin := c.Request.Header.Get("Origin")
|
||||||
|
if c.Request.Method == http.MethodOptions {
|
||||||
|
f(c, origin)
|
||||||
|
|
||||||
|
c.Header("Content-Length", "0")
|
||||||
|
c.AbortWithStatus(http.StatusNoContent)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if origin != "" {
|
||||||
|
f(c, origin)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
323
internal/lib/oapi/middleware/cors_test.go
Normal file
323
internal/lib/oapi/middleware/cors_test.go
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
package middleware_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCORS(t *testing.T) { //nolint:maintidx
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
opts middleware.CORSOptions
|
||||||
|
requestMethod string
|
||||||
|
requestOrigin string
|
||||||
|
requestHeaders map[string]string
|
||||||
|
wantStatus int
|
||||||
|
wantHeaders http.Header
|
||||||
|
expectNext bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with allowed origin",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET", "POST"},
|
||||||
|
AllowedHeaders: []string{"Content-Type", "Authorization"},
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Origin": []string{"https://example.com"},
|
||||||
|
"Access-Control-Allow-Methods": []string{"GET, POST"},
|
||||||
|
"Access-Control-Allow-Headers": []string{"Content-Type, Authorization"},
|
||||||
|
"Access-Control-Allow-Credentials": []string{"false"},
|
||||||
|
"Vary": []string{
|
||||||
|
"Origin, Access-Control-Request-Method",
|
||||||
|
},
|
||||||
|
"Content-Length": []string{"0"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with wildcard origin",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
requestOrigin: "https://any-origin.com",
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Origin": []string{"https://any-origin.com"},
|
||||||
|
"Access-Control-Allow-Methods": []string{"GET, POST, PUT, DELETE"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with disallowed origin",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET", "POST"},
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
requestOrigin: "https://malicious.com",
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with reflected headers (nil)",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"POST"},
|
||||||
|
AllowedHeaders: nil,
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{
|
||||||
|
"Access-Control-Request-Headers": "X-Custom-Header, X-Another-Header",
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Headers": []string{"X-Custom-Header, X-Another-Header"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with denied headers (empty slice)",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"POST"},
|
||||||
|
AllowedHeaders: []string{},
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{
|
||||||
|
"Access-Control-Request-Headers": "X-Custom-Header, X-Another-Header",
|
||||||
|
},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with nil headers and no request headers",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
AllowedHeaders: nil,
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with credentials enabled",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Credentials": []string{"true"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with MaxAge",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
MaxAge: "3600",
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Max-Age": []string{"3600"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OPTIONS request with exposed headers",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
ExposedHeaders: []string{"X-Custom-Response", "X-Total-Count"},
|
||||||
|
},
|
||||||
|
requestMethod: "OPTIONS",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusNoContent,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Expose-Headers": []string{"X-Custom-Response, X-Total-Count"},
|
||||||
|
},
|
||||||
|
expectNext: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET request with allowed origin",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET", "POST"},
|
||||||
|
AllowedHeaders: []string{"Content-Type"},
|
||||||
|
},
|
||||||
|
requestMethod: "GET",
|
||||||
|
requestOrigin: "https://example.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Origin": []string{"https://example.com"},
|
||||||
|
"Access-Control-Allow-Methods": []string{"GET, POST"},
|
||||||
|
"Access-Control-Allow-Headers": []string{"Content-Type"},
|
||||||
|
},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "POST request with disallowed origin",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET", "POST"},
|
||||||
|
},
|
||||||
|
requestMethod: "POST",
|
||||||
|
requestOrigin: "https://malicious.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET request without origin header",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{"https://example.com"},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
},
|
||||||
|
requestMethod: "GET",
|
||||||
|
requestOrigin: "",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET request with empty allowed origins (denies all)",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
},
|
||||||
|
requestMethod: "GET",
|
||||||
|
requestOrigin: "https://any-origin.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET request with nil allowed origins (allows all)",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: nil,
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
},
|
||||||
|
requestMethod: "GET",
|
||||||
|
requestOrigin: "https://any-origin.com",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Origin": []string{"https://any-origin.com"},
|
||||||
|
},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GET request with multiple allowed origins",
|
||||||
|
opts: middleware.CORSOptions{ //nolint:exhaustruct
|
||||||
|
AllowedOrigins: []string{
|
||||||
|
"https://example.com",
|
||||||
|
"https://another-example.com",
|
||||||
|
"https://third-example.com",
|
||||||
|
},
|
||||||
|
AllowedMethods: []string{"GET"},
|
||||||
|
},
|
||||||
|
requestMethod: "GET",
|
||||||
|
requestHeaders: map[string]string{},
|
||||||
|
requestOrigin: "https://another-example.com",
|
||||||
|
wantStatus: http.StatusOK,
|
||||||
|
wantHeaders: http.Header{
|
||||||
|
"Access-Control-Allow-Origin": []string{"https://another-example.com"},
|
||||||
|
},
|
||||||
|
expectNext: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Setup router with CORS middleware
|
||||||
|
router := gin.New()
|
||||||
|
nextCalled := false
|
||||||
|
|
||||||
|
router.Use(middleware.CORS(tc.opts))
|
||||||
|
router.Any("/test", func(c *gin.Context) {
|
||||||
|
nextCalled = true
|
||||||
|
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
req := httptest.NewRequest(tc.requestMethod, "/test", nil)
|
||||||
|
if tc.requestOrigin != "" {
|
||||||
|
req.Header.Set("Origin", tc.requestOrigin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add any additional request headers
|
||||||
|
for key, value := range tc.requestHeaders {
|
||||||
|
req.Header.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record response
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
// Check status code
|
||||||
|
if w.Code != tc.wantStatus {
|
||||||
|
t.Errorf("expected status %d, got %d", tc.wantStatus, w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check expected headers using cmp.Diff
|
||||||
|
// Only compare headers that are expected
|
||||||
|
gotHeaders := make(http.Header)
|
||||||
|
for key := range tc.wantHeaders {
|
||||||
|
if values := w.Header().Values(key); len(values) > 0 {
|
||||||
|
gotHeaders[key] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := cmp.Diff(tc.wantHeaders, gotHeaders); diff != "" {
|
||||||
|
t.Errorf("response headers mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Next() was called
|
||||||
|
if nextCalled != tc.expectNext {
|
||||||
|
t.Errorf("expected Next() called to be %v, got %v", tc.expectNext, nextCalled)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ func LoggerFromContext(ctx context.Context) *slog.Logger { //nolint:contextcheck
|
|||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logger is a Gin middleware that logs HTTP requests and responses using slog.
|
||||||
func Logger(logger *slog.Logger) gin.HandlerFunc {
|
func Logger(logger *slog.Logger) gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
startTime := time.Now()
|
startTime := time.Now()
|
||||||
69
internal/lib/oapi/oapi.go
Normal file
69
internal/lib/oapi/oapi.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package oapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/example/api"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
|
)
|
||||||
|
|
||||||
|
func surfaceErrorsMiddleWare(c *gin.Context) {
|
||||||
|
// this captures two cases as far as I can see:
|
||||||
|
// 1. request validation errors where the strict generated code fails
|
||||||
|
// to bind the request to the struct (i.e. "invalid param" test)
|
||||||
|
// 2. when a handler returns an error instead of a response
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
if len(c.Errors) > 0 && !c.IsAborted() {
|
||||||
|
var errorCode string
|
||||||
|
switch c.Writer.Status() {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
errorCode = "bad-request"
|
||||||
|
default:
|
||||||
|
errorCode = "internal-server-error"
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(
|
||||||
|
c.Writer.Status(),
|
||||||
|
gin.H{"errors": errorCode, "message": c.Errors[0].Error()},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter creates a Gin router with OpenAPI request validation middleware.
|
||||||
|
func NewRouter(
|
||||||
|
schema []byte,
|
||||||
|
apiPrefix string,
|
||||||
|
authenticationFunc openapi3filter.AuthenticationFunc,
|
||||||
|
corsOptions middleware.CORSOptions,
|
||||||
|
logger *slog.Logger,
|
||||||
|
) (*gin.Engine, func(c *gin.Context), error) {
|
||||||
|
router := gin.New()
|
||||||
|
|
||||||
|
loader := openapi3.NewLoader()
|
||||||
|
|
||||||
|
doc, err := loader.LoadFromData(schema)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to load OpenAPI schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.AddServer(&openapi3.Server{ //nolint:exhaustruct
|
||||||
|
URL: apiPrefix,
|
||||||
|
})
|
||||||
|
|
||||||
|
router.Use(
|
||||||
|
gin.Recovery(),
|
||||||
|
surfaceErrorsMiddleWare,
|
||||||
|
middleware.Logger(logger),
|
||||||
|
middleware.CORS(corsOptions),
|
||||||
|
)
|
||||||
|
|
||||||
|
mw := api.MiddlewareFunc(requestValidatorWithOptions(doc, authenticationFunc))
|
||||||
|
|
||||||
|
return router, mw, nil
|
||||||
|
}
|
||||||
128
internal/lib/oapi/request.go
Normal file
128
internal/lib/oapi/request.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package oapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/getkin/kin-openapi/openapi3"
|
||||||
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
|
"github.com/getkin/kin-openapi/routers"
|
||||||
|
"github.com/getkin/kin-openapi/routers/gorillamux"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
GinContextKey ContextKey = "nhost-oapi/gin-context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handleError(c *gin.Context, err error) {
|
||||||
|
var (
|
||||||
|
errReq *openapi3filter.RequestError
|
||||||
|
errSchema *openapi3.SchemaError
|
||||||
|
errAuth *AuthenticatorError
|
||||||
|
errSec *openapi3filter.SecurityRequirementsError
|
||||||
|
)
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &errSchema):
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "schema-validation-error",
|
||||||
|
"reason": errSchema.Reason,
|
||||||
|
})
|
||||||
|
case errors.As(err, &errReq):
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "request-validation-error",
|
||||||
|
"reason": errReq.Err.Error(),
|
||||||
|
})
|
||||||
|
case errors.As(err, &errAuth):
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": errAuth.Code,
|
||||||
|
"reason": errAuth.Message,
|
||||||
|
"securityScheme": errAuth.Scheme,
|
||||||
|
})
|
||||||
|
case errors.As(err, &errSec):
|
||||||
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
|
||||||
|
"error": "unauthorized",
|
||||||
|
"reason": errSec.Error(),
|
||||||
|
})
|
||||||
|
default:
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestValidatorWithOptions(
|
||||||
|
swagger *openapi3.T,
|
||||||
|
authFn openapi3filter.AuthenticationFunc,
|
||||||
|
) gin.HandlerFunc {
|
||||||
|
router, err := gorillamux.NewRouter(swagger)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if err := validateRequestFromContext(c, router, authFn); err != nil {
|
||||||
|
handleError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateRequestFromContext(
|
||||||
|
c *gin.Context,
|
||||||
|
router routers.Router,
|
||||||
|
authFn openapi3filter.AuthenticationFunc,
|
||||||
|
) error {
|
||||||
|
route, pathParams, err := router.FindRoute(c.Request)
|
||||||
|
if err != nil {
|
||||||
|
var e *routers.RouteError
|
||||||
|
switch {
|
||||||
|
case errors.As(err, &e):
|
||||||
|
return e
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("error validating route: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validationInput := &openapi3filter.RequestValidationInput{ //nolint:exhaustruct
|
||||||
|
Request: c.Request,
|
||||||
|
PathParams: pathParams,
|
||||||
|
Route: route,
|
||||||
|
Options: &openapi3filter.Options{
|
||||||
|
AuthenticationFunc: authFn,
|
||||||
|
ExcludeRequestBody: false,
|
||||||
|
ExcludeRequestQueryParams: false,
|
||||||
|
ExcludeResponseBody: false,
|
||||||
|
ExcludeReadOnlyValidations: false,
|
||||||
|
ExcludeWriteOnlyValidations: false,
|
||||||
|
IncludeResponseStatus: false,
|
||||||
|
MultiError: false,
|
||||||
|
RegexCompiler: nil,
|
||||||
|
SkipSettingDefaults: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestContext := context.WithValue(c.Request.Context(), GinContextKey, c)
|
||||||
|
if err := openapi3filter.ValidateRequest(requestContext, validationInput); err != nil {
|
||||||
|
return err //nolint:wrapcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGinContext(c context.Context) *gin.Context {
|
||||||
|
v := c.Value(GinContextKey)
|
||||||
|
if v == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ginCtx, ok := v.(*gin.Context)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ginCtx
|
||||||
|
}
|
||||||
@@ -114,13 +114,13 @@ in
|
|||||||
echo "➜ Running golangci-lint"
|
echo "➜ Running golangci-lint"
|
||||||
golangci-lint run \
|
golangci-lint run \
|
||||||
--timeout 600s \
|
--timeout 600s \
|
||||||
./${submodule}/...
|
./...
|
||||||
|
|
||||||
echo "➜ Running tests"
|
echo "➜ Running tests"
|
||||||
richgo test \
|
richgo test \
|
||||||
-tags="${pkgs.lib.strings.concatStringsSep " " tags}" \
|
-tags="${pkgs.lib.strings.concatStringsSep " " tags}" \
|
||||||
-ldflags="${pkgs.lib.strings.concatStringsSep " " ldflags}" \
|
-ldflags="${pkgs.lib.strings.concatStringsSep " " ldflags}" \
|
||||||
-v ${goTestFlags} ./${submodule}/...
|
-v ${goTestFlags} ./...
|
||||||
|
|
||||||
${extraCheck}
|
${extraCheck}
|
||||||
|
|
||||||
|
|||||||
@@ -115,13 +115,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) YourEndpoint( //nolint:ireturn
|
func (ctrl *Controller) YourEndpoint( //nolint:ireturn
|
||||||
ctx context.Context, request api.YourEndpointRequestObject,
|
ctx context.Context, request api.YourEndpointRequestObject,
|
||||||
) (api.YourEndpointResponseObject, error) {
|
) (api.YourEndpointResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
// Validate inputs
|
// Validate inputs
|
||||||
if apiErr := ctrl.wf.ValidateInput(request.Body.Field, logger); apiErr != nil {
|
if apiErr := ctrl.wf.ValidateInput(request.Body.Field, logger); apiErr != nil {
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func cors() gin.HandlerFunc {
|
|
||||||
f := func(c *gin.Context, origin string) {
|
|
||||||
c.Header("Access-Control-Allow-Origin", origin)
|
|
||||||
c.Header("Access-Control-Allow-Methods", "POST, GET")
|
|
||||||
headers := c.Request.Header.Get("Access-Control-Request-Headers")
|
|
||||||
c.Header("Access-Control-Allow-Headers", headers)
|
|
||||||
c.Header("Access-Control-Allow-Credentials", "true")
|
|
||||||
c.Header("Access-Control-Max-Age", "86400")
|
|
||||||
c.Writer.Header().Add("Vary", "Origin, Access-Control-Request-Method")
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
origin := c.Request.Header.Get("Origin")
|
|
||||||
if c.Request.Method == http.MethodOptions {
|
|
||||||
f(c, origin)
|
|
||||||
|
|
||||||
c.Header("Content-Length", "0")
|
|
||||||
c.AbortWithStatus(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
if origin != "" {
|
|
||||||
f(c, origin)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,9 +8,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bradfitz/gomemcache/memcache"
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
"github.com/getkin/kin-openapi/openapi3"
|
|
||||||
"github.com/getkin/kin-openapi/openapi3filter"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/docs"
|
"github.com/nhost/nhost/services/auth/docs"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/controller"
|
"github.com/nhost/nhost/services/auth/go/controller"
|
||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/nhost/nhost/services/auth/go/oidc"
|
"github.com/nhost/nhost/services/auth/go/oidc"
|
||||||
"github.com/nhost/nhost/services/auth/go/providers"
|
"github.com/nhost/nhost/services/auth/go/providers"
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
ginmiddleware "github.com/oapi-codegen/gin-middleware"
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1296,87 +1295,42 @@ func getDependencies( //nolint:ireturn
|
|||||||
return emailer, sms, jwtGetter, idTokenValidator, nil
|
return emailer, sms, jwtGetter, idTokenValidator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGoServer( //nolint:funlen
|
func getCORSOptions() oapimw.CORSOptions {
|
||||||
|
return oapimw.CORSOptions{
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
AllowedMethods: []string{"POST", "GET"},
|
||||||
|
AllowedHeaders: nil,
|
||||||
|
ExposedHeaders: []string{},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: "86400",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGoServer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cmd *cli.Command,
|
cmd *cli.Command,
|
||||||
db *sql.Queries,
|
db *sql.Queries,
|
||||||
encrypter *crypto.Encrypter,
|
encrypter *crypto.Encrypter,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
) (*http.Server, error) {
|
) (*http.Server, error) {
|
||||||
router := gin.New()
|
ctrl, jwtGetter, err := getController(ctx, cmd, db, encrypter, logger)
|
||||||
|
|
||||||
loader := openapi3.NewLoader()
|
|
||||||
|
|
||||||
doc, err := loader.LoadFromData(docs.OpenAPISchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load OpenAPI schema: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.AddServer(&openapi3.Server{ //nolint:exhaustruct
|
|
||||||
URL: cmd.String(flagAPIPrefix),
|
|
||||||
})
|
|
||||||
|
|
||||||
handlers := []gin.HandlerFunc{
|
|
||||||
// ginmiddleware.OapiRequestValidator(doc),
|
|
||||||
gin.Recovery(),
|
|
||||||
cors(),
|
|
||||||
middleware.Logger(logger), //nolint:contextcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.Bool(flagRateLimitEnable) {
|
|
||||||
handlers = append(handlers, getRateLimiter(cmd, logger)) //nolint:contextcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
if cmd.String(flagTurnstileSecret) != "" {
|
|
||||||
handlers = append(handlers, middleware.Tunrstile( //nolint:contextcheck
|
|
||||||
cmd.String(flagTurnstileSecret), cmd.String(flagAPIPrefix)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Use(handlers...)
|
|
||||||
|
|
||||||
config, err := getConfig(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("problem creating config: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
emailer, smsClient, jwtGetter, idTokenValidator, err := getDependencies(ctx, cmd, db, logger)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthProviders, err := getOauth2Providers(ctx, cmd, logger)
|
handler := api.NewStrictHandler(ctrl, []api.StrictMiddlewareFunc{})
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("problem creating oauth providers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctrl, err := controller.New(
|
router, mw, err := oapi.NewRouter( //nolint:contextcheck
|
||||||
db,
|
docs.OpenAPISchema,
|
||||||
config,
|
cmd.String(flagAPIPrefix),
|
||||||
jwtGetter,
|
jwtGetter.MiddlewareFunc,
|
||||||
emailer,
|
getCORSOptions(),
|
||||||
smsClient,
|
logger,
|
||||||
hibp.NewClient(),
|
|
||||||
oauthProviders,
|
|
||||||
idTokenValidator,
|
|
||||||
controller.NewTotp(cmd.String(flagMfaTotpIssuer), time.Now),
|
|
||||||
encrypter,
|
|
||||||
cmd.Root().Version,
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create controller: %w", err)
|
return nil, fmt.Errorf("failed to create router: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := api.NewStrictHandler(ctrl, []api.StrictMiddlewareFunc{})
|
|
||||||
mw := api.MiddlewareFunc(ginmiddleware.OapiRequestValidatorWithOptions(
|
|
||||||
doc,
|
|
||||||
&ginmiddleware.Options{ //nolint:exhaustruct
|
|
||||||
Options: openapi3filter.Options{ //nolint:exhaustruct
|
|
||||||
AuthenticationFunc: jwtGetter.MiddlewareFunc,
|
|
||||||
},
|
|
||||||
SilenceServersWarning: true,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
api.RegisterHandlersWithOptions(
|
api.RegisterHandlersWithOptions(
|
||||||
router,
|
router,
|
||||||
handler,
|
handler,
|
||||||
@@ -1387,6 +1341,16 @@ func getGoServer( //nolint:funlen
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if cmd.Bool(flagRateLimitEnable) {
|
||||||
|
router.Use(getRateLimiter(cmd, logger)) //nolint:contextcheck
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.String(flagTurnstileSecret) != "" {
|
||||||
|
router.Use(middleware.Tunrstile( //nolint:contextcheck
|
||||||
|
cmd.String(flagTurnstileSecret), cmd.String(flagAPIPrefix),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
if cmd.Bool(flagEnableChangeEnv) {
|
if cmd.Bool(flagEnableChangeEnv) {
|
||||||
router.POST(cmd.String(flagAPIPrefix)+"/change-env", ctrl.PostChangeEnv)
|
router.POST(cmd.String(flagAPIPrefix)+"/change-env", ctrl.PostChangeEnv)
|
||||||
}
|
}
|
||||||
@@ -1410,6 +1374,48 @@ func getGoServer( //nolint:funlen
|
|||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getController(
|
||||||
|
ctx context.Context,
|
||||||
|
cmd *cli.Command,
|
||||||
|
db *sql.Queries,
|
||||||
|
encrypter *crypto.Encrypter,
|
||||||
|
logger *slog.Logger,
|
||||||
|
) (*controller.Controller, *controller.JWTGetter, error) {
|
||||||
|
config, err := getConfig(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("problem creating config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailer, smsClient, jwtGetter, idTokenValidator, err := getDependencies(ctx, cmd, db, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthProviders, err := getOauth2Providers(ctx, cmd, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("problem creating oauth providers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl, err := controller.New(
|
||||||
|
db,
|
||||||
|
config,
|
||||||
|
jwtGetter,
|
||||||
|
emailer,
|
||||||
|
smsClient,
|
||||||
|
hibp.NewClient(),
|
||||||
|
oauthProviders,
|
||||||
|
idTokenValidator,
|
||||||
|
controller.NewTotp(cmd.String(flagMfaTotpIssuer), time.Now),
|
||||||
|
encrypter,
|
||||||
|
cmd.Root().Version,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to create controller: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl, jwtGetter, nil
|
||||||
|
}
|
||||||
|
|
||||||
func serve(ctx context.Context, cmd *cli.Command) error {
|
func serve(ctx context.Context, cmd *cli.Command) error {
|
||||||
logger := getLogger(cmd.Bool(flagDebug), cmd.Bool(flagLogFormatTEXT))
|
logger := getLogger(cmd.Bool(flagDebug), cmd.Bool(flagLogFormatTEXT))
|
||||||
logger.InfoContext(ctx, cmd.Root().Name+" v"+cmd.Root().Version)
|
logger.InfoContext(ctx, cmd.Root().Name+" v"+cmd.Root().Version)
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ import (
|
|||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) AddSecurityKey( //nolint:ireturn
|
func (ctrl *Controller) AddSecurityKey( //nolint:ireturn
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
_ api.AddSecurityKeyRequestObject,
|
_ api.AddSecurityKeyRequestObject,
|
||||||
) (api.AddSecurityKeyResponseObject, error) {
|
) (api.AddSecurityKeyResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) ChangeUserEmail( //nolint:ireturn
|
func (ctrl *Controller) ChangeUserEmail( //nolint:ireturn
|
||||||
ctx context.Context, request api.ChangeUserEmailRequestObject,
|
ctx context.Context, request api.ChangeUserEmailRequestObject,
|
||||||
) (api.ChangeUserEmailResponseObject, error) {
|
) (api.ChangeUserEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
options, apiErr := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
options, apiErr := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) ChangeUserMfa( //nolint:ireturn
|
func (ctrl *Controller) ChangeUserMfa( //nolint:ireturn
|
||||||
ctx context.Context, _ api.ChangeUserMfaRequestObject,
|
ctx context.Context, _ api.ChangeUserMfaRequestObject,
|
||||||
) (api.ChangeUserMfaResponseObject, error) {
|
) (api.ChangeUserMfaResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.MfaEnabled {
|
if !ctrl.config.MfaEnabled {
|
||||||
logger.WarnContext(ctx, "mfa disabled")
|
logger.WarnContext(ctx, "mfa disabled")
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) postUserPasswordAuthenticated( //nolint:ireturn
|
func (ctrl *Controller) postUserPasswordAuthenticated( //nolint:ireturn
|
||||||
@@ -61,7 +61,7 @@ func (ctrl *Controller) ChangeUserPassword( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.ChangeUserPasswordRequestObject,
|
request api.ChangeUserPasswordRequestObject,
|
||||||
) (api.ChangeUserPasswordResponseObject, error) {
|
) (api.ChangeUserPasswordResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
jwtToken, ok := ctrl.wf.jwtGetter.FromContext(ctx)
|
jwtToken, ok := ctrl.wf.jwtGetter.FromContext(ctx)
|
||||||
if ok {
|
if ok {
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) CreatePAT( //nolint:ireturn
|
func (ctrl *Controller) CreatePAT( //nolint:ireturn
|
||||||
ctx context.Context, request api.CreatePATRequestObject,
|
ctx context.Context, request api.CreatePATRequestObject,
|
||||||
) (api.CreatePATResponseObject, error) {
|
) (api.CreatePATResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func (ctrl *Controller) postUserDeanonymizeValidateRequest( //nolint:cyclop
|
|||||||
func (ctrl *Controller) DeanonymizeUser( //nolint:funlen
|
func (ctrl *Controller) DeanonymizeUser( //nolint:funlen
|
||||||
ctx context.Context, request api.DeanonymizeUserRequestObject,
|
ctx context.Context, request api.DeanonymizeUserRequestObject,
|
||||||
) (api.DeanonymizeUserResponseObject, error) {
|
) (api.DeanonymizeUserResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
userID, password, options, apiError := ctrl.postUserDeanonymizeValidateRequest(
|
userID, password, options, apiError := ctrl.postUserDeanonymizeValidateRequest(
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) ElevateWebauthn( //nolint:ireturn
|
func (ctrl *Controller) ElevateWebauthn( //nolint:ireturn
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
_ api.ElevateWebauthnRequestObject,
|
_ api.ElevateWebauthnRequestObject,
|
||||||
) (api.ElevateWebauthnResponseObject, error) {
|
) (api.ElevateWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ func (e *APIError) Error() string {
|
|||||||
return fmt.Sprintf("API error: %s", e.t)
|
return fmt.Sprintf("API error: %s", e.t)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrElevatedClaimRequired = errors.New("elevated-claim-required")
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrJWTConfiguration = errors.New("jwt-configuration")
|
ErrJWTConfiguration = errors.New("jwt-configuration")
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ func (ctrl *Controller) GetProviderTokens( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req api.GetProviderTokensRequestObject,
|
req api.GetProviderTokensRequestObject,
|
||||||
) (api.GetProviderTokensResponseObject, error) {
|
) (api.GetProviderTokensResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
logger = logger.With("provider", req.Provider)
|
logger = logger.With("provider", req.Provider)
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/oapi-codegen/runtime/types"
|
"github.com/oapi-codegen/runtime/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) GetUser( //nolint:ireturn
|
func (ctrl *Controller) GetUser( //nolint:ireturn
|
||||||
ctx context.Context, _ api.GetUserRequestObject,
|
ctx context.Context, _ api.GetUserRequestObject,
|
||||||
) (api.GetUserResponseObject, error) {
|
) (api.GetUserResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
// Get authenticated user from JWT
|
// Get authenticated user from JWT
|
||||||
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
user, apiErr := ctrl.wf.GetUserFromJWTInContext(ctx, logger)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -17,8 +16,8 @@ import (
|
|||||||
"github.com/getkin/kin-openapi/openapi3filter"
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
ginmiddleware "github.com/oapi-codegen/gin-middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const JWTContextKey = "nhost/auth/jwt"
|
const JWTContextKey = "nhost/auth/jwt"
|
||||||
@@ -340,7 +339,7 @@ func (j *JWTGetter) Validate(accessToken string) (*jwt.Token, error) {
|
|||||||
func (j *JWTGetter) FromContext(ctx context.Context) (*jwt.Token, bool) {
|
func (j *JWTGetter) FromContext(ctx context.Context) (*jwt.Token, bool) {
|
||||||
token, ok := ctx.Value(JWTContextKey).(*jwt.Token)
|
token, ok := ctx.Value(JWTContextKey).(*jwt.Token)
|
||||||
if !ok { //nolint:nestif
|
if !ok { //nolint:nestif
|
||||||
c := ginmiddleware.GetGinContext(ctx)
|
c := oapi.GetGinContext(ctx)
|
||||||
if c != nil {
|
if c != nil {
|
||||||
a, ok := c.Get(JWTContextKey)
|
a, ok := c.Get(JWTContextKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -415,16 +414,28 @@ func (j *JWTGetter) MiddlewareFunc(
|
|||||||
|
|
||||||
parts := strings.Split(authHeader, " ")
|
parts := strings.Split(authHeader, " ")
|
||||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
return errors.New("invalid authorization header") //nolint:err113
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "missing or malformed authorization header",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtToken, err := j.Validate(parts[1])
|
jwtToken, err := j.Validate(parts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error validating token: %w", err)
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: fmt.Sprintf("error validating token: %s", err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !jwtToken.Valid {
|
if !jwtToken.Valid {
|
||||||
return errors.New("invalid token") //nolint:err113
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "invalid token",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SecuritySchemeName == "BearerAuthElevated" {
|
if input.SecuritySchemeName == "BearerAuthElevated" {
|
||||||
@@ -435,15 +446,23 @@ func (j *JWTGetter) MiddlewareFunc(
|
|||||||
|
|
||||||
found, err := j.verifyElevatedClaim(ctx, jwtToken, requestPath)
|
found, err := j.verifyElevatedClaim(ctx, jwtToken, requestPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error verifying elevated claim: %w", err)
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: fmt.Sprintf("error verifying elevated claim: %s", err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return ErrElevatedClaimRequired
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "elevated claim required",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := ginmiddleware.GetGinContext(ctx)
|
c := oapi.GetGinContext(ctx)
|
||||||
c.Set(JWTContextKey, jwtToken)
|
c.Set(JWTContextKey, jwtToken)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package controller_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"errors"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -16,9 +15,9 @@ import (
|
|||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
"github.com/nhost/nhost/services/auth/go/controller"
|
"github.com/nhost/nhost/services/auth/go/controller"
|
||||||
"github.com/nhost/nhost/services/auth/go/controller/mock"
|
"github.com/nhost/nhost/services/auth/go/controller/mock"
|
||||||
ginmiddleware "github.com/oapi-codegen/gin-middleware"
|
|
||||||
"go.uber.org/mock/gomock"
|
"go.uber.org/mock/gomock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -535,8 +534,12 @@ func TestMiddlewareFunc(t *testing.T) { //nolint:maintidx
|
|||||||
SecurityScheme: nil,
|
SecurityScheme: nil,
|
||||||
Scopes: []string{},
|
Scopes: []string{},
|
||||||
},
|
},
|
||||||
expected: nil,
|
expected: nil,
|
||||||
expectedErr: controller.ErrElevatedClaimRequired,
|
expectedErr: &oapi.AuthenticatorError{
|
||||||
|
Scheme: "BearerAuthElevated",
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "elevated claim required",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -559,8 +562,12 @@ func TestMiddlewareFunc(t *testing.T) { //nolint:maintidx
|
|||||||
SecurityScheme: nil,
|
SecurityScheme: nil,
|
||||||
Scopes: []string{},
|
Scopes: []string{},
|
||||||
},
|
},
|
||||||
expected: nil,
|
expected: nil,
|
||||||
expectedErr: controller.ErrElevatedClaimRequired,
|
expectedErr: &oapi.AuthenticatorError{
|
||||||
|
Scheme: "BearerAuthElevated",
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "elevated claim required",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -763,13 +770,13 @@ func TestMiddlewareFunc(t *testing.T) { //nolint:maintidx
|
|||||||
//nolint
|
//nolint
|
||||||
ctx := context.WithValue(
|
ctx := context.WithValue(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
ginmiddleware.GinContextKey,
|
oapi.GinContextKey,
|
||||||
&gin.Context{},
|
&gin.Context{},
|
||||||
)
|
)
|
||||||
|
|
||||||
err = jwtGetter.MiddlewareFunc(ctx, tc.request)
|
err = jwtGetter.MiddlewareFunc(ctx, tc.request)
|
||||||
if !errors.Is(err, tc.expectedErr) {
|
if diff := cmp.Diff(err, tc.expectedErr); diff != "" {
|
||||||
t.Errorf("err = %v; want %v", err, tc.expectedErr)
|
t.Errorf("err mismatch (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
got, _ := jwtGetter.FromContext(ctx)
|
got, _ := jwtGetter.FromContext(ctx)
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) LinkIdToken( //nolint:ireturn,revive
|
func (ctrl *Controller) LinkIdToken( //nolint:ireturn,revive
|
||||||
ctx context.Context, req api.LinkIdTokenRequestObject,
|
ctx context.Context, req api.LinkIdTokenRequestObject,
|
||||||
) (api.LinkIdTokenResponseObject, error) {
|
) (api.LinkIdTokenResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
profile, apiErr := ctrl.wf.GetOIDCProfileFromIDToken(
|
profile, apiErr := ctrl.wf.GetOIDCProfileFromIDToken(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) RefreshProviderToken( //nolint:ireturn
|
func (ctrl *Controller) RefreshProviderToken( //nolint:ireturn
|
||||||
ctx context.Context, req api.RefreshProviderTokenRequestObject,
|
ctx context.Context, req api.RefreshProviderTokenRequestObject,
|
||||||
) (api.RefreshProviderTokenResponseObject, error) {
|
) (api.RefreshProviderTokenResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
logger = logger.With("provider", req.Provider)
|
logger = logger.With("provider", req.Provider)
|
||||||
|
|
||||||
provider := ctrl.Providers.Get(string(req.Provider))
|
provider := ctrl.Providers.Get(string(req.Provider))
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) RefreshToken( //nolint:ireturn
|
func (ctrl *Controller) RefreshToken( //nolint:ireturn
|
||||||
ctx context.Context, request api.RefreshTokenRequestObject,
|
ctx context.Context, request api.RefreshTokenRequestObject,
|
||||||
) (api.RefreshTokenResponseObject, error) {
|
) (api.RefreshTokenResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserByRefreshTokenHash(
|
user, apiErr := ctrl.wf.GetUserByRefreshTokenHash(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ func (ctrl *Controller) SendPasswordResetEmail( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SendPasswordResetEmailRequestObject,
|
request api.SendPasswordResetEmailRequestObject,
|
||||||
) (api.SendPasswordResetEmailResponseObject, error) {
|
) (api.SendPasswordResetEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
options, err := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
options, err := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ func (ctrl *Controller) SendVerificationEmail( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SendVerificationEmailRequestObject,
|
request api.SendVerificationEmailRequestObject,
|
||||||
) (api.SendVerificationEmailResponseObject, error) {
|
) (api.SendVerificationEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
options, apiErr := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
options, apiErr := ctrl.wf.ValidateOptionsRedirectTo(ctx, request.Body.Options, logger)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) postSigninAnonymousValidateRequest(
|
func (ctrl *Controller) postSigninAnonymousValidateRequest(
|
||||||
@@ -49,7 +49,7 @@ func (ctrl *Controller) postSigninAnonymousValidateRequest(
|
|||||||
func (ctrl *Controller) SignInAnonymous( //nolint:ireturn
|
func (ctrl *Controller) SignInAnonymous( //nolint:ireturn
|
||||||
ctx context.Context, req api.SignInAnonymousRequestObject,
|
ctx context.Context, req api.SignInAnonymousRequestObject,
|
||||||
) (api.SignInAnonymousResponseObject, error) {
|
) (api.SignInAnonymousResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
req, apiErr := ctrl.postSigninAnonymousValidateRequest(ctx, req, logger)
|
req, apiErr := ctrl.postSigninAnonymousValidateRequest(ctx, req, logger)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) postSigninEmailPasswordWithTOTP( //nolint:ireturn
|
func (ctrl *Controller) postSigninEmailPasswordWithTOTP( //nolint:ireturn
|
||||||
@@ -33,7 +33,7 @@ func (ctrl *Controller) postSigninEmailPasswordWithTOTP( //nolint:ireturn
|
|||||||
func (ctrl *Controller) SignInEmailPassword( //nolint:ireturn
|
func (ctrl *Controller) SignInEmailPassword( //nolint:ireturn
|
||||||
ctx context.Context, request api.SignInEmailPasswordRequestObject,
|
ctx context.Context, request api.SignInEmailPasswordRequestObject,
|
||||||
) (api.SignInEmailPasswordResponseObject, error) {
|
) (api.SignInEmailPasswordResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserByEmail(ctx, string(request.Body.Email), logger)
|
user, apiErr := ctrl.wf.GetUserByEmail(ctx, string(request.Body.Email), logger)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/oidc"
|
"github.com/nhost/nhost/services/auth/go/oidc"
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
"github.com/oapi-codegen/runtime/types"
|
"github.com/oapi-codegen/runtime/types"
|
||||||
@@ -45,7 +45,7 @@ func (ctrl *Controller) postSigninIdtokenCheckUserExists(
|
|||||||
func (ctrl *Controller) SignInIdToken( //nolint:ireturn,revive
|
func (ctrl *Controller) SignInIdToken( //nolint:ireturn,revive
|
||||||
ctx context.Context, req api.SignInIdTokenRequestObject,
|
ctx context.Context, req api.SignInIdTokenRequestObject,
|
||||||
) (api.SignInIdTokenResponseObject, error) {
|
) (api.SignInIdTokenResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
profile, apiError := ctrl.wf.GetOIDCProfileFromIDToken(
|
profile, apiError := ctrl.wf.GetOIDCProfileFromIDToken(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func (ctrl *Controller) SignInOTPEmail( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignInOTPEmailRequestObject,
|
request api.SignInOTPEmailRequestObject,
|
||||||
) (api.SignInOTPEmailResponseObject, error) {
|
) (api.SignInOTPEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
if !ctrl.config.OTPEmailEnabled {
|
if !ctrl.config.OTPEmailEnabled {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/notifications"
|
"github.com/nhost/nhost/services/auth/go/notifications"
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
@@ -19,7 +19,7 @@ func (ctrl *Controller) SignInPasswordlessEmail( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignInPasswordlessEmailRequestObject,
|
request api.SignInPasswordlessEmailRequestObject,
|
||||||
) (api.SignInPasswordlessEmailResponseObject, error) {
|
) (api.SignInPasswordlessEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
if !ctrl.config.EmailPasswordlessEnabled {
|
if !ctrl.config.EmailPasswordlessEnabled {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ func (ctrl *Controller) SignInPasswordlessSms( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignInPasswordlessSmsRequestObject,
|
request api.SignInPasswordlessSmsRequestObject,
|
||||||
) (api.SignInPasswordlessSmsResponseObject, error) {
|
) (api.SignInPasswordlessSmsResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("phoneNumber", request.Body.PhoneNumber))
|
With(slog.String("phoneNumber", request.Body.PhoneNumber))
|
||||||
|
|
||||||
if !ctrl.config.SMSPasswordlessEnabled {
|
if !ctrl.config.SMSPasswordlessEnabled {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ func (ctrl *Controller) SignInPAT( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignInPATRequestObject,
|
request api.SignInPATRequestObject,
|
||||||
) (api.SignInPATResponseObject, error) {
|
) (api.SignInPATResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserByRefreshTokenHash(
|
user, apiErr := ctrl.wf.GetUserByRefreshTokenHash(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) getSigninProviderValidateRequest(
|
func (ctrl *Controller) getSigninProviderValidateRequest(
|
||||||
@@ -42,7 +42,7 @@ func (ctrl *Controller) SignInProvider( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req api.SignInProviderRequestObject,
|
req api.SignInProviderRequestObject,
|
||||||
) (api.SignInProviderResponseObject, error) {
|
) (api.SignInProviderResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("provider", string(req.Provider)))
|
With(slog.String("provider", string(req.Provider)))
|
||||||
|
|
||||||
redirectTo, apiErr := ctrl.getSigninProviderValidateRequest(ctx, req, logger)
|
redirectTo, apiErr := ctrl.getSigninProviderValidateRequest(ctx, req, logger)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/oidc"
|
"github.com/nhost/nhost/services/auth/go/oidc"
|
||||||
"github.com/nhost/nhost/services/auth/go/providers"
|
"github.com/nhost/nhost/services/auth/go/providers"
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
@@ -220,7 +220,7 @@ func (ctrl *Controller) signinProviderProviderCallback(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req providerCallbackData,
|
req providerCallbackData,
|
||||||
) (*url.URL, *APIError) {
|
) (*url.URL, *APIError) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
options, connnect, redirectTo, apiErr := ctrl.signinProviderProviderCallbackValidate(
|
options, connnect, redirectTo, apiErr := ctrl.signinProviderProviderCallbackValidate(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ func (ctrl *Controller) SignInWebauthn( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignInWebauthnRequestObject,
|
request api.SignInWebauthnRequestObject,
|
||||||
) (api.SignInWebauthnResponseObject, error) {
|
) (api.SignInWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) SignOut( //nolint:ireturn
|
func (ctrl *Controller) SignOut( //nolint:ireturn
|
||||||
ctx context.Context, request api.SignOutRequestObject,
|
ctx context.Context, request api.SignOutRequestObject,
|
||||||
) (api.SignOutResponseObject, error) {
|
) (api.SignOutResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if deptr(request.Body.All) {
|
if deptr(request.Body.All) {
|
||||||
userID, apiErr := ctrl.wf.GetJWTInContext(ctx, logger)
|
userID, apiErr := ctrl.wf.GetJWTInContext(ctx, logger)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ func (ctrl *Controller) SignUpEmailPassword( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req api.SignUpEmailPasswordRequestObject,
|
req api.SignUpEmailPasswordRequestObject,
|
||||||
) (api.SignUpEmailPasswordResponseObject, error) {
|
) (api.SignUpEmailPasswordResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).With(slog.String("email", string(req.Body.Email)))
|
logger := oapimw.LoggerFromContext(ctx).With(slog.String("email", string(req.Body.Email)))
|
||||||
|
|
||||||
req, apiError := ctrl.postSignupEmailPasswordValidateRequest(ctx, req, logger)
|
req, apiError := ctrl.postSignupEmailPasswordValidateRequest(ctx, req, logger)
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) postSignupWebauthnValidateRequest(
|
func (ctrl *Controller) postSignupWebauthnValidateRequest(
|
||||||
@@ -42,7 +42,7 @@ func (ctrl *Controller) SignUpWebauthn( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.SignUpWebauthnRequestObject,
|
request api.SignUpWebauthnRequestObject,
|
||||||
) (api.SignUpWebauthnResponseObject, error) {
|
) (api.SignUpWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
options, apiErr := ctrl.postSignupWebauthnValidateRequest(ctx, request, logger)
|
options, apiErr := ctrl.postSignupWebauthnValidateRequest(ctx, request, logger)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ func (ctrl *Controller) VerifyAddSecurityKey( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifyAddSecurityKeyRequestObject,
|
request api.VerifyAddSecurityKeyRequestObject,
|
||||||
) (api.VerifyAddSecurityKeyResponseObject, error) {
|
) (api.VerifyAddSecurityKeyResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ func (ctrl *Controller) postUserMfaActivate( //nolint:ireturn
|
|||||||
func (ctrl *Controller) VerifyChangeUserMfa( //nolint:ireturn
|
func (ctrl *Controller) VerifyChangeUserMfa( //nolint:ireturn
|
||||||
ctx context.Context, req api.VerifyChangeUserMfaRequestObject,
|
ctx context.Context, req api.VerifyChangeUserMfaRequestObject,
|
||||||
) (api.VerifyChangeUserMfaResponseObject, error) {
|
) (api.VerifyChangeUserMfaResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.MfaEnabled {
|
if !ctrl.config.MfaEnabled {
|
||||||
logger.WarnContext(ctx, "mfa disabled")
|
logger.WarnContext(ctx, "mfa disabled")
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ func (ctrl *Controller) VerifyElevateWebauthn( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifyElevateWebauthnRequestObject,
|
request api.VerifyElevateWebauthnRequestObject,
|
||||||
) (api.VerifyElevateWebauthnResponseObject, error) {
|
) (api.VerifyElevateWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) VerifySignInMfaTotp( //nolint:ireturn
|
func (ctrl *Controller) VerifySignInMfaTotp( //nolint:ireturn
|
||||||
ctx context.Context, req api.VerifySignInMfaTotpRequestObject,
|
ctx context.Context, req api.VerifySignInMfaTotpRequestObject,
|
||||||
) (api.VerifySignInMfaTotpResponseObject, error) {
|
) (api.VerifySignInMfaTotpResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.MfaEnabled {
|
if !ctrl.config.MfaEnabled {
|
||||||
logger.WarnContext(ctx, "mfa disabled")
|
logger.WarnContext(ctx, "mfa disabled")
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) VerifySignInOTPEmail( //nolint:ireturn
|
func (ctrl *Controller) VerifySignInOTPEmail( //nolint:ireturn
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifySignInOTPEmailRequestObject,
|
request api.VerifySignInOTPEmailRequestObject,
|
||||||
) (api.VerifySignInOTPEmailResponseObject, error) {
|
) (api.VerifySignInOTPEmailResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("email", string(request.Body.Email)))
|
With(slog.String("email", string(request.Body.Email)))
|
||||||
|
|
||||||
user, apiErr := ctrl.wf.GetUserByEmailAndTicket(
|
user, apiErr := ctrl.wf.GetUserByEmailAndTicket(
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) VerifySignInPasswordlessSms( //nolint:ireturn
|
func (ctrl *Controller) VerifySignInPasswordlessSms( //nolint:ireturn
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifySignInPasswordlessSmsRequestObject,
|
request api.VerifySignInPasswordlessSmsRequestObject,
|
||||||
) (api.VerifySignInPasswordlessSmsResponseObject, error) {
|
) (api.VerifySignInPasswordlessSmsResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx).
|
logger := oapimw.LoggerFromContext(ctx).
|
||||||
With(slog.String("phoneNumber", request.Body.PhoneNumber))
|
With(slog.String("phoneNumber", request.Body.PhoneNumber))
|
||||||
|
|
||||||
if !ctrl.config.SMSPasswordlessEnabled {
|
if !ctrl.config.SMSPasswordlessEnabled {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) VerifySignInWebauthnUserHandle(
|
func (ctrl *Controller) VerifySignInWebauthnUserHandle(
|
||||||
@@ -70,7 +70,7 @@ func (ctrl *Controller) VerifySignInWebauthn( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifySignInWebauthnRequestObject,
|
request api.VerifySignInWebauthnRequestObject,
|
||||||
) (api.VerifySignInWebauthnResponseObject, error) {
|
) (api.VerifySignInWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if !ctrl.config.WebauthnEnabled {
|
if !ctrl.config.WebauthnEnabled {
|
||||||
logger.ErrorContext(ctx, "webauthn is disabled")
|
logger.ErrorContext(ctx, "webauthn is disabled")
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ func (ctrl *Controller) VerifySignUpWebauthn( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.VerifySignUpWebauthnRequestObject,
|
request api.VerifySignUpWebauthnRequestObject,
|
||||||
) (api.VerifySignUpWebauthnResponseObject, error) {
|
) (api.VerifySignUpWebauthnResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
credData, options, nickname, apiErr := ctrl.postSignupWebauthnVerifyValidateRequest(
|
credData, options, nickname, apiErr := ctrl.postSignupWebauthnVerifyValidateRequest(
|
||||||
ctx,
|
ctx,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
"github.com/nhost/nhost/services/auth/go/sql"
|
"github.com/nhost/nhost/services/auth/go/sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ func (ctrl *Controller) getVerifyHandleTicketType(
|
|||||||
func (ctrl *Controller) VerifyTicket( //nolint:ireturn
|
func (ctrl *Controller) VerifyTicket( //nolint:ireturn
|
||||||
ctx context.Context, req api.VerifyTicketRequestObject,
|
ctx context.Context, req api.VerifyTicketRequestObject,
|
||||||
) (api.VerifyTicketResponseObject, error) {
|
) (api.VerifyTicketResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
user, ticketType, redirectTo, apiErr := ctrl.getVerifyValidateRequest(ctx, req, logger)
|
user, ticketType, redirectTo, apiErr := ctrl.getVerifyValidateRequest(ctx, req, logger)
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ package controller
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/auth/go/api"
|
"github.com/nhost/nhost/services/auth/go/api"
|
||||||
"github.com/nhost/nhost/services/auth/go/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) VerifyToken( //nolint:ireturn
|
func (ctrl *Controller) VerifyToken( //nolint:ireturn
|
||||||
ctx context.Context, request api.VerifyTokenRequestObject,
|
ctx context.Context, request api.VerifyTokenRequestObject,
|
||||||
) (api.VerifyTokenResponseObject, error) {
|
) (api.VerifyTokenResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
if request.Body != nil && request.Body.Token != nil {
|
if request.Body != nil && request.Body.Token != nil {
|
||||||
if apiErr := ctrl.wf.VerifyJWTToken(ctx, *request.Body.Token, logger); apiErr != nil {
|
if apiErr := ctrl.wf.VerifyJWTToken(ctx, *request.Body.Token, logger); apiErr != nil {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ let
|
|||||||
./vacuum.yaml
|
./vacuum.yaml
|
||||||
./vacuum-ignore.yaml
|
./vacuum-ignore.yaml
|
||||||
|
|
||||||
|
(inDirectory ../../internal/lib/oapi)
|
||||||
|
|
||||||
./go/api/server.cfg.yaml
|
./go/api/server.cfg.yaml
|
||||||
./go/api/types.cfg.yaml
|
./go/api/types.cfg.yaml
|
||||||
./go/sql/schema.sh
|
./go/sql/schema.sh
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ describe('personal access token', () => {
|
|||||||
await request
|
await request
|
||||||
.post('/pat')
|
.post('/pat')
|
||||||
.send({ expiresAt: new Date() })
|
.send({ expiresAt: new Date() })
|
||||||
.expect(StatusCodes.BAD_REQUEST);
|
.expect(StatusCodes.UNAUTHORIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should be able to add metadata to a personal access token', async () => {
|
test('should be able to add metadata to a personal access token', async () => {
|
||||||
@@ -65,7 +65,6 @@ describe('personal access token', () => {
|
|||||||
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
|
||||||
metadata: { name: 'Test PAT' },
|
metadata: { name: 'Test PAT' },
|
||||||
})
|
})
|
||||||
.expect(StatusCodes.OK);
|
|
||||||
|
|
||||||
const { rows } = await client.query(
|
const { rows } = await client.query(
|
||||||
'SELECT * FROM auth.refresh_tokens WHERE refresh_token_hash=$1;',
|
'SELECT * FROM auth.refresh_tokens WHERE refresh_token_hash=$1;',
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe('user email', () => {
|
|||||||
.post('/user/email/change')
|
.post('/user/email/change')
|
||||||
// .set('Authorization', `Bearer ${accessToken}`)
|
// .set('Authorization', `Bearer ${accessToken}`)
|
||||||
.send({ newEmail })
|
.send({ newEmail })
|
||||||
.expect(StatusCodes.BAD_REQUEST);
|
.expect(StatusCodes.UNAUTHORIZED);
|
||||||
|
|
||||||
await request
|
await request
|
||||||
.post('/user/email/change')
|
.post('/user/email/change')
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('user password', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should not get user data if not signed in', async () => {
|
it('should not get user data if not signed in', async () => {
|
||||||
await request.get('/user').expect(StatusCodes.BAD_REQUEST);
|
await request.get('/user').expect(StatusCodes.UNAUTHORIZED);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get user data if signed in', async () => {
|
it('should get user data if signed in', async () => {
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/config"
|
"github.com/aws/aws-sdk-go-v2/config"
|
||||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
"github.com/getkin/kin-openapi/openapi3"
|
|
||||||
"github.com/getkin/kin-openapi/openapi3filter"
|
|
||||||
"github.com/gin-contrib/cors"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/controller"
|
"github.com/nhost/nhost/services/storage/controller"
|
||||||
"github.com/nhost/nhost/services/storage/image"
|
"github.com/nhost/nhost/services/storage/image"
|
||||||
@@ -23,7 +22,6 @@ import (
|
|||||||
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
||||||
"github.com/nhost/nhost/services/storage/migrations"
|
"github.com/nhost/nhost/services/storage/migrations"
|
||||||
"github.com/nhost/nhost/services/storage/storage"
|
"github.com/nhost/nhost/services/storage/storage"
|
||||||
ginmiddleware "github.com/oapi-codegen/gin-middleware"
|
|
||||||
"github.com/urfave/cli/v3"
|
"github.com/urfave/cli/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,88 +51,39 @@ const (
|
|||||||
flagHasuraDBName = "hasura-db-name"
|
flagHasuraDBName = "hasura-db-name"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getCorsMiddleware(
|
func getCORSOptions(cmd *cli.Command) oapimw.CORSOptions {
|
||||||
corsAllowOrigins []string,
|
return oapimw.CORSOptions{
|
||||||
corsAllowCredentials bool,
|
AllowedOrigins: cmd.StringSlice(flagCorsAllowOrigins),
|
||||||
) gin.HandlerFunc {
|
AllowedMethods: []string{"GET", "PUT", "POST", "HEAD", "DELETE"},
|
||||||
return cors.New(cors.Config{ //nolint:exhaustruct
|
AllowedHeaders: []string{
|
||||||
AllowOrigins: corsAllowOrigins,
|
|
||||||
AllowMethods: []string{"GET", "PUT", "POST", "HEAD", "DELETE"},
|
|
||||||
AllowHeaders: []string{
|
|
||||||
"Authorization", "Origin", "if-match", "if-none-match", "if-modified-since", "if-unmodified-since",
|
"Authorization", "Origin", "if-match", "if-none-match", "if-modified-since", "if-unmodified-since",
|
||||||
"x-hasura-admin-secret", "x-nhost-bucket-id", "x-nhost-file-name", "x-nhost-file-id",
|
"x-hasura-admin-secret", "x-nhost-bucket-id", "x-nhost-file-name", "x-nhost-file-id",
|
||||||
"x-hasura-role",
|
"x-hasura-role",
|
||||||
},
|
},
|
||||||
ExposeHeaders: []string{
|
ExposedHeaders: []string{
|
||||||
"Content-Length", "Content-Type", "Cache-Control", "ETag", "Last-Modified", "X-Error",
|
"Content-Length", "Content-Type", "Cache-Control", "ETag", "Last-Modified", "X-Error",
|
||||||
},
|
},
|
||||||
AllowCredentials: corsAllowCredentials,
|
AllowCredentials: cmd.Bool(flagCorsAllowCredentials),
|
||||||
MaxAge: 12 * time.Hour, //nolint: mnd
|
MaxAge: "86400",
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getGin( //nolint:funlen
|
func getServer(
|
||||||
bind string,
|
cmd *cli.Command,
|
||||||
publicURL string,
|
|
||||||
apiRootPrefix string,
|
|
||||||
hasuraAdminSecret string,
|
|
||||||
metadataStorage controller.MetadataStorage,
|
metadataStorage controller.MetadataStorage,
|
||||||
contentStorage controller.ContentStorage,
|
contentStorage controller.ContentStorage,
|
||||||
imageTransformer *image.Transformer,
|
imageTransformer *image.Transformer,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
debug bool,
|
|
||||||
corsAllowOrigins []string,
|
|
||||||
corsAllowCredentials bool,
|
|
||||||
fastlyService string,
|
|
||||||
fastlyKey string,
|
|
||||||
clamavServer string,
|
|
||||||
) (*http.Server, error) {
|
) (*http.Server, error) {
|
||||||
router := gin.New()
|
av, err := getAv(cmd.String(flagClamavServer))
|
||||||
|
|
||||||
router.GET("/healthz", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "ok")
|
|
||||||
})
|
|
||||||
|
|
||||||
if !debug {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
loader := openapi3.NewLoader()
|
|
||||||
|
|
||||||
doc, err := loader.LoadFromData(controller.OpenAPISchema)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load OpenAPI schema: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.AddServer(&openapi3.Server{ //nolint:exhaustruct
|
|
||||||
URL: apiRootPrefix,
|
|
||||||
})
|
|
||||||
|
|
||||||
handlers := []gin.HandlerFunc{
|
|
||||||
middleware.Logger(logger),
|
|
||||||
getCorsMiddleware(corsAllowOrigins, corsAllowCredentials),
|
|
||||||
gin.Recovery(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if fastlyService != "" {
|
|
||||||
logger.InfoContext(context.Background(), "enabling fastly middleware")
|
|
||||||
handlers = append(
|
|
||||||
handlers,
|
|
||||||
fastly.New(fastlyService, fastlyKey, logger),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
router.Use(handlers...)
|
|
||||||
|
|
||||||
av, err := getAv(clamavServer)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("problem trying to get av: %w", err)
|
return nil, fmt.Errorf("problem trying to get av: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrl := controller.New(
|
ctrl := controller.New(
|
||||||
publicURL,
|
cmd.String(flagPublicURL),
|
||||||
apiRootPrefix,
|
cmd.String(flagAPIRootPrefix),
|
||||||
hasuraAdminSecret,
|
cmd.String(flagHasuraAdminSecret),
|
||||||
metadataStorage,
|
metadataStorage,
|
||||||
contentStorage,
|
contentStorage,
|
||||||
imageTransformer,
|
imageTransformer,
|
||||||
@@ -143,27 +92,41 @@ func getGin( //nolint:funlen
|
|||||||
)
|
)
|
||||||
|
|
||||||
handler := api.NewStrictHandler(ctrl, []api.StrictMiddlewareFunc{})
|
handler := api.NewStrictHandler(ctrl, []api.StrictMiddlewareFunc{})
|
||||||
mw := api.MiddlewareFunc(ginmiddleware.OapiRequestValidatorWithOptions(
|
|
||||||
doc,
|
router, mw, err := oapi.NewRouter(
|
||||||
&ginmiddleware.Options{ //nolint:exhaustruct
|
controller.OpenAPISchema,
|
||||||
Options: openapi3filter.Options{ //nolint:exhaustruct
|
cmd.String(flagAPIRootPrefix),
|
||||||
AuthenticationFunc: middleware.AuthenticationFunc(hasuraAdminSecret),
|
middleware.AuthenticationFunc(cmd.String(flagHasuraAdminSecret)),
|
||||||
},
|
getCORSOptions(cmd),
|
||||||
SilenceServersWarning: true,
|
logger,
|
||||||
},
|
)
|
||||||
))
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create router: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.GET("/healthz", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "ok")
|
||||||
|
})
|
||||||
|
|
||||||
|
if cmd.String(flagFastlyService) != "" {
|
||||||
|
logger.InfoContext(context.Background(), "enabling fastly middleware")
|
||||||
|
router.Use(
|
||||||
|
fastly.New(cmd.String(flagFastlyService), cmd.String(flagFastlyKey), logger),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
api.RegisterHandlersWithOptions(
|
api.RegisterHandlersWithOptions(
|
||||||
router,
|
router,
|
||||||
handler,
|
handler,
|
||||||
api.GinServerOptions{
|
api.GinServerOptions{
|
||||||
BaseURL: apiRootPrefix,
|
BaseURL: cmd.String(flagAPIRootPrefix),
|
||||||
Middlewares: []api.MiddlewareFunc{mw},
|
Middlewares: []api.MiddlewareFunc{mw},
|
||||||
ErrorHandler: nil,
|
ErrorHandler: nil,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
server := &http.Server{ //nolint:exhaustruct
|
server := &http.Server{ //nolint:exhaustruct
|
||||||
Addr: bind,
|
Addr: cmd.String(flagBind),
|
||||||
Handler: router,
|
Handler: router,
|
||||||
ReadHeaderTimeout: 5 * time.Second, //nolint:mnd
|
ReadHeaderTimeout: 5 * time.Second, //nolint:mnd
|
||||||
}
|
}
|
||||||
@@ -453,21 +416,12 @@ func serve(ctx context.Context, cmd *cli.Command) error { //nolint:funlen
|
|||||||
cmd.String(flagHasuraEndpoint) + "/graphql",
|
cmd.String(flagHasuraEndpoint) + "/graphql",
|
||||||
)
|
)
|
||||||
|
|
||||||
server, err := getGin( //nolint: contextcheck
|
server, err := getServer( //nolint: contextcheck
|
||||||
cmd.String(flagBind),
|
cmd,
|
||||||
cmd.String(flagPublicURL),
|
|
||||||
cmd.String(flagAPIRootPrefix),
|
|
||||||
cmd.String(flagHasuraAdminSecret),
|
|
||||||
metadataStorage,
|
metadataStorage,
|
||||||
contentStorage,
|
contentStorage,
|
||||||
imageTransformer,
|
imageTransformer,
|
||||||
logger,
|
logger,
|
||||||
cmd.Bool(flagDebug),
|
|
||||||
cmd.StringSlice(flagCorsAllowOrigins),
|
|
||||||
cmd.Bool(flagCorsAllowCredentials),
|
|
||||||
cmd.String(flagFastlyService),
|
|
||||||
cmd.String(flagFastlyKey),
|
|
||||||
cmd.String(flagClamavServer),
|
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) deleteBrokenMetadata(
|
func (ctrl *Controller) deleteBrokenMetadata(
|
||||||
@@ -29,7 +29,7 @@ func (ctrl *Controller) DeleteBrokenMetadata( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
_ api.DeleteBrokenMetadataRequestObject,
|
_ api.DeleteBrokenMetadataRequestObject,
|
||||||
) (api.DeleteBrokenMetadataResponseObject, error) {
|
) (api.DeleteBrokenMetadataResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
files, apiErr := ctrl.deleteBrokenMetadata(ctx)
|
files, apiErr := ctrl.deleteBrokenMetadata(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
||||||
@@ -13,7 +14,7 @@ func (ctrl *Controller) DeleteFile( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.DeleteFileRequestObject,
|
request api.DeleteFileRequestObject,
|
||||||
) (api.DeleteFileResponseObject, error) {
|
) (api.DeleteFileResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
||||||
|
|
||||||
apiErr := ctrl.metadataStorage.DeleteFileByID(ctx, request.Id, sessionHeaders)
|
apiErr := ctrl.metadataStorage.DeleteFileByID(ctx, request.Id, sessionHeaders)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) deleteOrphans(ctx context.Context) ([]string, *APIError) {
|
func (ctrl *Controller) deleteOrphans(ctx context.Context) ([]string, *APIError) {
|
||||||
@@ -27,7 +27,7 @@ func (ctrl *Controller) DeleteOrphanedFiles( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
_ api.DeleteOrphanedFilesRequestObject,
|
_ api.DeleteOrphanedFilesRequestObject,
|
||||||
) (api.DeleteOrphanedFilesResponseObject, error) {
|
) (api.DeleteOrphanedFilesResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
files, apiErr := ctrl.deleteOrphans(ctx)
|
files, apiErr := ctrl.deleteOrphans(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/image"
|
"github.com/nhost/nhost/services/storage/image"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
@@ -324,7 +325,7 @@ func (ctrl *Controller) GetFile( //nolint:ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.GetFileRequestObject,
|
request api.GetFileRequestObject,
|
||||||
) (api.GetFileResponseObject, error) {
|
) (api.GetFileResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
||||||
acceptHeader := middleware.AcceptHeaderFromContext(ctx)
|
acceptHeader := middleware.AcceptHeaderFromContext(ctx)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
)
|
)
|
||||||
@@ -24,7 +25,7 @@ type GetFilePresignedURLRequest struct {
|
|||||||
func (ctrl *Controller) GetFilePresignedURL( //nolint:ireturn
|
func (ctrl *Controller) GetFilePresignedURL( //nolint:ireturn
|
||||||
ctx context.Context, request api.GetFilePresignedURLRequestObject,
|
ctx context.Context, request api.GetFilePresignedURLRequestObject,
|
||||||
) (api.GetFilePresignedURLResponseObject, error) {
|
) (api.GetFilePresignedURLResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
logger = logger.With("file_id", request.Id)
|
logger = logger.With("file_id", request.Id)
|
||||||
|
|
||||||
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
)
|
)
|
||||||
@@ -151,7 +152,7 @@ func (ctrl *Controller) GetFileWithPresignedURL( //nolint: ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.GetFileWithPresignedURLRequestObject,
|
request api.GetFileWithPresignedURLRequestObject,
|
||||||
) (api.GetFileWithPresignedURLResponseObject, error) {
|
) (api.GetFileWithPresignedURLResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
acceptHeader := middleware.AcceptHeaderFromContext(ctx)
|
acceptHeader := middleware.AcceptHeaderFromContext(ctx)
|
||||||
|
|
||||||
fileMetadata, _, apiErr := ctrl.getFileMetadata(
|
fileMetadata, _, apiErr := ctrl.getFileMetadata(
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListBrokenMetadataResponse struct {
|
type ListBrokenMetadataResponse struct {
|
||||||
@@ -60,7 +60,7 @@ func fileListSummary(files []FileSummary) *[]api.FileSummary {
|
|||||||
func (ctrl *Controller) ListBrokenMetadata( //nolint:ireturn
|
func (ctrl *Controller) ListBrokenMetadata( //nolint:ireturn
|
||||||
ctx context.Context, _ api.ListBrokenMetadataRequestObject,
|
ctx context.Context, _ api.ListBrokenMetadataRequestObject,
|
||||||
) (api.ListBrokenMetadataResponseObject, error) {
|
) (api.ListBrokenMetadataResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
files, apiErr := ctrl.listBrokenMetadata(ctx)
|
files, apiErr := ctrl.listBrokenMetadata(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) listNotUploaded(ctx context.Context) ([]FileSummary, *APIError) {
|
func (ctrl *Controller) listNotUploaded(ctx context.Context) ([]FileSummary, *APIError) {
|
||||||
@@ -52,7 +52,7 @@ func (ctrl *Controller) ListNotUploaded(ctx *gin.Context) {
|
|||||||
func (ctrl *Controller) ListFilesNotUploaded( //nolint:ireturn
|
func (ctrl *Controller) ListFilesNotUploaded( //nolint:ireturn
|
||||||
ctx context.Context, _ api.ListFilesNotUploadedRequestObject,
|
ctx context.Context, _ api.ListFilesNotUploadedRequestObject,
|
||||||
) (api.ListFilesNotUploadedResponseObject, error) {
|
) (api.ListFilesNotUploadedResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
files, apiErr := ctrl.listNotUploaded(ctx)
|
files, apiErr := ctrl.listNotUploaded(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ctrl *Controller) listOrphans(ctx context.Context) ([]string, *APIError) {
|
func (ctrl *Controller) listOrphans(ctx context.Context) ([]string, *APIError) {
|
||||||
@@ -47,7 +47,7 @@ func (ctrl *Controller) listOrphans(ctx context.Context) ([]string, *APIError) {
|
|||||||
func (ctrl *Controller) ListOrphanedFiles( //nolint:ireturn
|
func (ctrl *Controller) ListOrphanedFiles( //nolint:ireturn
|
||||||
ctx context.Context, _ api.ListOrphanedFilesRequestObject,
|
ctx context.Context, _ api.ListOrphanedFilesRequestObject,
|
||||||
) (api.ListOrphanedFilesResponseObject, error) {
|
) (api.ListOrphanedFilesResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
|
|
||||||
files, apiErr := ctrl.listOrphans(ctx)
|
files, apiErr := ctrl.listOrphans(ctx)
|
||||||
if apiErr != nil {
|
if apiErr != nil {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
"github.com/nhost/nhost/services/storage/middleware/cdn/fastly"
|
||||||
@@ -67,7 +68,7 @@ func (ctrl *Controller) ReplaceFile( //nolint:funlen,ireturn
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
request api.ReplaceFileRequestObject,
|
request api.ReplaceFileRequestObject,
|
||||||
) (api.ReplaceFileResponseObject, error) {
|
) (api.ReplaceFileResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
||||||
|
|
||||||
file, apiErr := replaceFileParseRequest(request)
|
file, apiErr := replaceFileParseRequest(request)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gabriel-vasile/mimetype"
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
oapimw "github.com/nhost/nhost/internal/lib/oapi/middleware"
|
||||||
"github.com/nhost/nhost/services/storage/api"
|
"github.com/nhost/nhost/services/storage/api"
|
||||||
"github.com/nhost/nhost/services/storage/middleware"
|
"github.com/nhost/nhost/services/storage/middleware"
|
||||||
)
|
)
|
||||||
@@ -249,7 +250,7 @@ func parseUploadRequest(form *multipart.Form) (uploadFileRequest, *APIError) {
|
|||||||
func (ctrl *Controller) UploadFiles( //nolint:ireturn
|
func (ctrl *Controller) UploadFiles( //nolint:ireturn
|
||||||
ctx context.Context, request api.UploadFilesRequestObject,
|
ctx context.Context, request api.UploadFilesRequestObject,
|
||||||
) (api.UploadFilesResponseObject, error) {
|
) (api.UploadFilesResponseObject, error) {
|
||||||
logger := middleware.LoggerFromContext(ctx)
|
logger := oapimw.LoggerFromContext(ctx)
|
||||||
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
sessionHeaders := middleware.SessionHeadersFromContext(ctx)
|
||||||
|
|
||||||
form, err := request.Body.ReadForm(maxFormMemory)
|
form, err := request.Body.ReadForm(maxFormMemory)
|
||||||
|
|||||||
@@ -2,18 +2,15 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/getkin/kin-openapi/openapi3filter"
|
"github.com/getkin/kin-openapi/openapi3filter"
|
||||||
ginmiddleware "github.com/oapi-codegen/gin-middleware"
|
"github.com/nhost/nhost/internal/lib/oapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
const HeadersContextKey = "request.headers"
|
const HeadersContextKey = "request.headers"
|
||||||
|
|
||||||
var ErrUnauthorized = errors.New("unauthorized")
|
|
||||||
|
|
||||||
func SessionHeadersFromContext(ctx context.Context) http.Header {
|
func SessionHeadersFromContext(ctx context.Context) http.Header {
|
||||||
headers, _ := ctx.Value(HeadersContextKey).(http.Header)
|
headers, _ := ctx.Value(HeadersContextKey).(http.Header)
|
||||||
|
|
||||||
@@ -51,11 +48,15 @@ func AuthenticationFunc(adminSecret string) openapi3filter.AuthenticationFunc {
|
|||||||
"X-Hasura-Admin-Secret",
|
"X-Hasura-Admin-Secret",
|
||||||
)
|
)
|
||||||
if adminSecretHeader != adminSecret {
|
if adminSecretHeader != adminSecret {
|
||||||
return ErrUnauthorized
|
return &oapi.AuthenticatorError{
|
||||||
|
Scheme: input.SecuritySchemeName,
|
||||||
|
Code: "unauthorized",
|
||||||
|
Message: "invalid credentials",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := ginmiddleware.GetGinContext(ctx)
|
c := oapi.GetGinContext(ctx)
|
||||||
c.Set(HeadersContextKey, input.RequestValidationInput.Request.Header)
|
c.Set(HeadersContextKey, input.RequestValidationInput.Request.Header)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type loggerCtxKey struct{}
|
|
||||||
|
|
||||||
// Stores the logger in the context.
|
|
||||||
func LoggerToContext(ctx context.Context, logger *slog.Logger) context.Context {
|
|
||||||
return context.WithValue(ctx, loggerCtxKey{}, logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves the logger from the context. It creates a new one if it can't be found.
|
|
||||||
func LoggerFromContext(ctx context.Context) *slog.Logger { //nolint:contextcheck
|
|
||||||
ginCtx, ok := ctx.(*gin.Context)
|
|
||||||
if ok {
|
|
||||||
ctx = ginCtx.Request.Context()
|
|
||||||
}
|
|
||||||
|
|
||||||
logger, ok := ctx.Value(loggerCtxKey{}).(*slog.Logger)
|
|
||||||
if !ok {
|
|
||||||
return slog.Default()
|
|
||||||
}
|
|
||||||
|
|
||||||
return logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func Logger(logger *slog.Logger) gin.HandlerFunc {
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
startTime := time.Now()
|
|
||||||
|
|
||||||
trace := TraceFromHTTPHeaders(ctx.Request.Header)
|
|
||||||
|
|
||||||
clientIP := ctx.ClientIP()
|
|
||||||
reqMethod := ctx.Request.Method
|
|
||||||
reqURL := ctx.Request.RequestURI
|
|
||||||
logger := logger.With(
|
|
||||||
slog.Group(
|
|
||||||
"trace",
|
|
||||||
slog.String("trace_id", trace.TraceID),
|
|
||||||
slog.String("span_id", trace.SpanID),
|
|
||||||
slog.String("parent_span_id", trace.ParentSpanID),
|
|
||||||
),
|
|
||||||
slog.Group(
|
|
||||||
"request",
|
|
||||||
slog.String("client_ip", clientIP),
|
|
||||||
slog.String("method", reqMethod),
|
|
||||||
slog.String("url", reqURL),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
ctx.Request = ctx.Request.WithContext(
|
|
||||||
LoggerToContext(ctx.Request.Context(), logger),
|
|
||||||
)
|
|
||||||
ctx.Next()
|
|
||||||
|
|
||||||
latencyTime := time.Since(startTime)
|
|
||||||
statusCode := ctx.Writer.Status()
|
|
||||||
|
|
||||||
logger = logger.With(slog.Group(
|
|
||||||
"response",
|
|
||||||
slog.Int("status_code", statusCode),
|
|
||||||
slog.Duration("latency_time", latencyTime),
|
|
||||||
slog.Any("errors", ctx.Errors.Errors()),
|
|
||||||
))
|
|
||||||
|
|
||||||
TraceToHTTPHeaders(trace, ctx.Writer.Header())
|
|
||||||
|
|
||||||
if len(ctx.Errors.Errors()) > 0 {
|
|
||||||
logger.ErrorContext(ctx, "call completed with errors")
|
|
||||||
} else {
|
|
||||||
logger.InfoContext(ctx, "call completed")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
headerTraceID = "X-B3-TraceId"
|
|
||||||
headerSpanID = "X-B3-SpanId"
|
|
||||||
headerParentSpanID = "X-B3-ParentSpanId"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Trace struct {
|
|
||||||
TraceID string
|
|
||||||
ParentSpanID string
|
|
||||||
SpanID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTrace creates a new trace with a new `TraceID`.
|
|
||||||
func NewTrace() Trace {
|
|
||||||
return Trace{
|
|
||||||
TraceID: uuid.New().String(),
|
|
||||||
ParentSpanID: "",
|
|
||||||
SpanID: "",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSpan create a new trace with the same `TraceID`, `ParentSpanID` set as the current `SpanID`
|
|
||||||
// and a new `SpanID`.
|
|
||||||
func (t Trace) NewSpan() Trace {
|
|
||||||
return Trace{
|
|
||||||
TraceID: t.TraceID,
|
|
||||||
ParentSpanID: t.SpanID,
|
|
||||||
SpanID: uuid.New().String(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FromHTTPHeaders extracts tracing information from HTTP headers.
|
|
||||||
// If no tracing information is found, a new trace is created with only `TraceID` set.
|
|
||||||
func TraceFromHTTPHeaders(headers http.Header) Trace {
|
|
||||||
traceID := headers.Get(headerTraceID)
|
|
||||||
if traceID == "" {
|
|
||||||
traceID = uuid.New().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
spanID := headers.Get(headerSpanID)
|
|
||||||
parentSpanID := headers.Get(headerParentSpanID)
|
|
||||||
|
|
||||||
return Trace{
|
|
||||||
TraceID: traceID,
|
|
||||||
ParentSpanID: parentSpanID,
|
|
||||||
SpanID: spanID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHTTPHeaders adds tracing information to HTTP headers.
|
|
||||||
func TraceToHTTPHeaders(trace Trace, header http.Header) {
|
|
||||||
header.Set(headerTraceID, trace.TraceID)
|
|
||||||
header.Set(headerParentSpanID, trace.ParentSpanID)
|
|
||||||
header.Set(headerSpanID, trace.SpanID)
|
|
||||||
}
|
|
||||||
@@ -33,6 +33,8 @@ let
|
|||||||
(inDirectory "${submodule}/client/testdata")
|
(inDirectory "${submodule}/client/testdata")
|
||||||
(inDirectory "${submodule}/image/testdata")
|
(inDirectory "${submodule}/image/testdata")
|
||||||
(inDirectory "${submodule}/storage/testdata")
|
(inDirectory "${submodule}/storage/testdata")
|
||||||
|
|
||||||
|
(inDirectory ../../internal/lib/oapi)
|
||||||
];
|
];
|
||||||
|
|
||||||
exclude = with nix-filter.lib; [
|
exclude = with nix-filter.lib; [
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
version: "2"
|
|
||||||
linters:
|
|
||||||
default: all
|
|
||||||
settings:
|
|
||||||
funlen:
|
|
||||||
lines: 65
|
|
||||||
disable:
|
|
||||||
- canonicalheader
|
|
||||||
- depguard
|
|
||||||
- gomoddirectives
|
|
||||||
- musttag
|
|
||||||
- nlreturn
|
|
||||||
- tagliatelle
|
|
||||||
- varnamelen
|
|
||||||
- wsl
|
|
||||||
- noinlineerr
|
|
||||||
- funcorder
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
presets:
|
|
||||||
- comments
|
|
||||||
- common-false-positives
|
|
||||||
- legacy
|
|
||||||
- std-error-handling
|
|
||||||
rules:
|
|
||||||
- linters:
|
|
||||||
- funlen
|
|
||||||
- ireturn
|
|
||||||
path: _test\.go
|
|
||||||
- linters:
|
|
||||||
- lll
|
|
||||||
source: '^//go:generate '
|
|
||||||
- linters:
|
|
||||||
- gochecknoglobals
|
|
||||||
text: Version is a global variable
|
|
||||||
- linters:
|
|
||||||
- ireturn
|
|
||||||
- lll
|
|
||||||
path: schema\.resolvers\.go
|
|
||||||
paths:
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
formatters:
|
|
||||||
enable:
|
|
||||||
- gofmt
|
|
||||||
- gofumpt
|
|
||||||
- goimports
|
|
||||||
exclusions:
|
|
||||||
generated: lax
|
|
||||||
paths:
|
|
||||||
- third_party$
|
|
||||||
- builtin$
|
|
||||||
- examples$
|
|
||||||
- schema\.resolvers\.go
|
|
||||||
@@ -11,7 +11,7 @@ let
|
|||||||
"go.mod"
|
"go.mod"
|
||||||
"go.sum"
|
"go.sum"
|
||||||
(inDirectory "vendor")
|
(inDirectory "vendor")
|
||||||
"${submodule}/.golangci.yaml"
|
".golangci.yaml"
|
||||||
isDirectory
|
isDirectory
|
||||||
(and
|
(and
|
||||||
(inDirectory submodule)
|
(inDirectory submodule)
|
||||||
|
|||||||
25
vendor/github.com/gin-contrib/cors/.gitignore
generated
vendored
25
vendor/github.com/gin-contrib/cors/.gitignore
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
*.o
|
|
||||||
*.a
|
|
||||||
*.so
|
|
||||||
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.prof
|
|
||||||
|
|
||||||
coverage.out
|
|
||||||
|
|
||||||
.idea
|
|
||||||
39
vendor/github.com/gin-contrib/cors/.golangci.yml
generated
vendored
39
vendor/github.com/gin-contrib/cors/.golangci.yml
generated
vendored
@@ -1,39 +0,0 @@
|
|||||||
linters:
|
|
||||||
enable-all: false
|
|
||||||
disable-all: true
|
|
||||||
fast: false
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- errcheck
|
|
||||||
- exportloopref
|
|
||||||
- exhaustive
|
|
||||||
- gochecknoinits
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- lll
|
|
||||||
- misspell
|
|
||||||
- nakedret
|
|
||||||
- noctx
|
|
||||||
- nolintlint
|
|
||||||
- rowserrcheck
|
|
||||||
- staticcheck
|
|
||||||
- stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- whitespace
|
|
||||||
- gofumpt
|
|
||||||
|
|
||||||
run:
|
|
||||||
timeout: 3m
|
|
||||||
28
vendor/github.com/gin-contrib/cors/.goreleaser.yaml
generated
vendored
28
vendor/github.com/gin-contrib/cors/.goreleaser.yaml
generated
vendored
@@ -1,28 +0,0 @@
|
|||||||
builds:
|
|
||||||
- # If true, skip the build.
|
|
||||||
# Useful for library projects.
|
|
||||||
# Default is false
|
|
||||||
skip: true
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
use: github
|
|
||||||
groups:
|
|
||||||
- title: Features
|
|
||||||
regexp: "^.*feat[(\\w)]*:+.*$"
|
|
||||||
order: 0
|
|
||||||
- title: "Bug fixes"
|
|
||||||
regexp: "^.*fix[(\\w)]*:+.*$"
|
|
||||||
order: 1
|
|
||||||
- title: "Enhancements"
|
|
||||||
regexp: "^.*chore[(\\w)]*:+.*$"
|
|
||||||
order: 2
|
|
||||||
- title: "Refactor"
|
|
||||||
regexp: "^.*refactor[(\\w)]*:+.*$"
|
|
||||||
order: 3
|
|
||||||
- title: "Build process updates"
|
|
||||||
regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
|
|
||||||
order: 4
|
|
||||||
- title: "Documentation updates"
|
|
||||||
regexp: ^.*?docs?(\(.+\))??!?:.+$
|
|
||||||
order: 4
|
|
||||||
- title: Others
|
|
||||||
21
vendor/github.com/gin-contrib/cors/LICENSE
generated
vendored
21
vendor/github.com/gin-contrib/cors/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2016 Gin-Gonic
|
|
||||||
|
|
||||||
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.
|
|
||||||
95
vendor/github.com/gin-contrib/cors/README.md
generated
vendored
95
vendor/github.com/gin-contrib/cors/README.md
generated
vendored
@@ -1,95 +0,0 @@
|
|||||||
# CORS gin's middleware
|
|
||||||
|
|
||||||
[](https://github.com/gin-contrib/cors/actions/workflows/go.yml)
|
|
||||||
[](https://codecov.io/gh/gin-contrib/cors)
|
|
||||||
[](https://goreportcard.com/report/github.com/gin-contrib/cors)
|
|
||||||
[](https://godoc.org/github.com/gin-contrib/cors)
|
|
||||||
|
|
||||||
Gin middleware/handler to enable CORS support.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Start using it
|
|
||||||
|
|
||||||
Download and install it:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get github.com/gin-contrib/cors
|
|
||||||
```
|
|
||||||
|
|
||||||
Import it in your code:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/gin-contrib/cors"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Canonical example
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
// CORS for https://foo.com and https://github.com origins, allowing:
|
|
||||||
// - PUT and PATCH methods
|
|
||||||
// - Origin header
|
|
||||||
// - Credentials share
|
|
||||||
// - Preflight requests cached for 12 hours
|
|
||||||
router.Use(cors.New(cors.Config{
|
|
||||||
AllowOrigins: []string{"https://foo.com"},
|
|
||||||
AllowMethods: []string{"PUT", "PATCH"},
|
|
||||||
AllowHeaders: []string{"Origin"},
|
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
AllowOriginFunc: func(origin string) bool {
|
|
||||||
return origin == "https://github.com"
|
|
||||||
},
|
|
||||||
MaxAge: 12 * time.Hour,
|
|
||||||
}))
|
|
||||||
router.Run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using DefaultConfig as start point
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
// - No origin allowed by default
|
|
||||||
// - GET,POST, PUT, HEAD methods
|
|
||||||
// - Credentials share disabled
|
|
||||||
// - Preflight requests cached for 12 hours
|
|
||||||
config := cors.DefaultConfig()
|
|
||||||
config.AllowOrigins = []string{"http://google.com"}
|
|
||||||
// config.AllowOrigins = []string{"http://google.com", "http://facebook.com"}
|
|
||||||
// config.AllowAllOrigins = true
|
|
||||||
|
|
||||||
router.Use(cors.New(config))
|
|
||||||
router.Run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: while Default() allows all origins, DefaultConfig() does not and you will still have to use AllowAllOrigins.
|
|
||||||
|
|
||||||
### Default() allows all origins
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
router := gin.Default()
|
|
||||||
// same as
|
|
||||||
// config := cors.DefaultConfig()
|
|
||||||
// config.AllowAllOrigins = true
|
|
||||||
// router.Use(cors.New(config))
|
|
||||||
router.Use(cors.Default())
|
|
||||||
router.Run()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Using all origins disables the ability for Gin to set cookies for clients. When dealing with credentials, don't allow all origins.
|
|
||||||
155
vendor/github.com/gin-contrib/cors/config.go
generated
vendored
155
vendor/github.com/gin-contrib/cors/config.go
generated
vendored
@@ -1,155 +0,0 @@
|
|||||||
package cors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cors struct {
|
|
||||||
allowAllOrigins bool
|
|
||||||
allowCredentials bool
|
|
||||||
allowOriginFunc func(string) bool
|
|
||||||
allowOriginWithContextFunc func(*gin.Context, string) bool
|
|
||||||
allowOrigins []string
|
|
||||||
normalHeaders http.Header
|
|
||||||
preflightHeaders http.Header
|
|
||||||
wildcardOrigins [][]string
|
|
||||||
optionsResponseStatusCode int
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultSchemas = []string{
|
|
||||||
"http://",
|
|
||||||
"https://",
|
|
||||||
}
|
|
||||||
ExtensionSchemas = []string{
|
|
||||||
"chrome-extension://",
|
|
||||||
"safari-extension://",
|
|
||||||
"moz-extension://",
|
|
||||||
"ms-browser-extension://",
|
|
||||||
}
|
|
||||||
FileSchemas = []string{
|
|
||||||
"file://",
|
|
||||||
}
|
|
||||||
WebSocketSchemas = []string{
|
|
||||||
"ws://",
|
|
||||||
"wss://",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newCors(config Config) *cors {
|
|
||||||
if err := config.Validate(); err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, origin := range config.AllowOrigins {
|
|
||||||
if origin == "*" {
|
|
||||||
config.AllowAllOrigins = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.OptionsResponseStatusCode == 0 {
|
|
||||||
config.OptionsResponseStatusCode = http.StatusNoContent
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cors{
|
|
||||||
allowOriginFunc: config.AllowOriginFunc,
|
|
||||||
allowOriginWithContextFunc: config.AllowOriginWithContextFunc,
|
|
||||||
allowAllOrigins: config.AllowAllOrigins,
|
|
||||||
allowCredentials: config.AllowCredentials,
|
|
||||||
allowOrigins: normalize(config.AllowOrigins),
|
|
||||||
normalHeaders: generateNormalHeaders(config),
|
|
||||||
preflightHeaders: generatePreflightHeaders(config),
|
|
||||||
wildcardOrigins: config.parseWildcardRules(),
|
|
||||||
optionsResponseStatusCode: config.OptionsResponseStatusCode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) applyCors(c *gin.Context) {
|
|
||||||
origin := c.Request.Header.Get("Origin")
|
|
||||||
if len(origin) == 0 {
|
|
||||||
// request is not a CORS request
|
|
||||||
return
|
|
||||||
}
|
|
||||||
host := c.Request.Host
|
|
||||||
|
|
||||||
if origin == "http://"+host || origin == "https://"+host {
|
|
||||||
// request is not a CORS request but have origin header.
|
|
||||||
// for example, use fetch api
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cors.isOriginValid(c, origin) {
|
|
||||||
c.AbortWithStatus(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Request.Method == "OPTIONS" {
|
|
||||||
cors.handlePreflight(c)
|
|
||||||
defer c.AbortWithStatus(cors.optionsResponseStatusCode)
|
|
||||||
} else {
|
|
||||||
cors.handleNormal(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cors.allowAllOrigins {
|
|
||||||
c.Header("Access-Control-Allow-Origin", origin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) validateWildcardOrigin(origin string) bool {
|
|
||||||
for _, w := range cors.wildcardOrigins {
|
|
||||||
if w[0] == "*" && strings.HasSuffix(origin, w[1]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if w[1] == "*" && strings.HasPrefix(origin, w[0]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(origin, w[0]) && strings.HasSuffix(origin, w[1]) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) isOriginValid(c *gin.Context, origin string) bool {
|
|
||||||
valid := cors.validateOrigin(origin)
|
|
||||||
if !valid && cors.allowOriginWithContextFunc != nil {
|
|
||||||
valid = cors.allowOriginWithContextFunc(c, origin)
|
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) validateOrigin(origin string) bool {
|
|
||||||
if cors.allowAllOrigins {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, value := range cors.allowOrigins {
|
|
||||||
if value == origin {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(cors.wildcardOrigins) > 0 && cors.validateWildcardOrigin(origin) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if cors.allowOriginFunc != nil {
|
|
||||||
return cors.allowOriginFunc(origin)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) handlePreflight(c *gin.Context) {
|
|
||||||
header := c.Writer.Header()
|
|
||||||
for key, value := range cors.preflightHeaders {
|
|
||||||
header[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cors *cors) handleNormal(c *gin.Context) {
|
|
||||||
header := c.Writer.Header()
|
|
||||||
for key, value := range cors.normalHeaders {
|
|
||||||
header[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
198
vendor/github.com/gin-contrib/cors/cors.go
generated
vendored
198
vendor/github.com/gin-contrib/cors/cors.go
generated
vendored
@@ -1,198 +0,0 @@
|
|||||||
package cors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents all available options for the middleware.
|
|
||||||
type Config struct {
|
|
||||||
AllowAllOrigins bool
|
|
||||||
|
|
||||||
// AllowOrigins is a list of origins a cross-domain request can be executed from.
|
|
||||||
// If the special "*" value is present in the list, all origins will be allowed.
|
|
||||||
// Default value is []
|
|
||||||
AllowOrigins []string
|
|
||||||
|
|
||||||
// AllowOriginFunc is a custom function to validate the origin. It takes the origin
|
|
||||||
// as an argument and returns true if allowed or false otherwise. If this option is
|
|
||||||
// set, the content of AllowOrigins is ignored.
|
|
||||||
AllowOriginFunc func(origin string) bool
|
|
||||||
|
|
||||||
// Same as AllowOriginFunc except also receives the full request context.
|
|
||||||
// This function should use the context as a read only source and not
|
|
||||||
// have any side effects on the request, such as aborting or injecting
|
|
||||||
// values on the request.
|
|
||||||
AllowOriginWithContextFunc func(c *gin.Context, origin string) bool
|
|
||||||
|
|
||||||
// AllowMethods is a list of methods the client is allowed to use with
|
|
||||||
// cross-domain requests. Default value is simple methods (GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS)
|
|
||||||
AllowMethods []string
|
|
||||||
|
|
||||||
// AllowPrivateNetwork indicates whether the response should include allow private network header
|
|
||||||
AllowPrivateNetwork bool
|
|
||||||
|
|
||||||
// AllowHeaders is list of non simple headers the client is allowed to use with
|
|
||||||
// cross-domain requests.
|
|
||||||
AllowHeaders []string
|
|
||||||
|
|
||||||
// AllowCredentials indicates whether the request can include user credentials like
|
|
||||||
// cookies, HTTP authentication or client side SSL certificates.
|
|
||||||
AllowCredentials bool
|
|
||||||
|
|
||||||
// ExposeHeaders indicates which headers are safe to expose to the API of a CORS
|
|
||||||
// API specification
|
|
||||||
ExposeHeaders []string
|
|
||||||
|
|
||||||
// MaxAge indicates how long (with second-precision) the results of a preflight request
|
|
||||||
// can be cached
|
|
||||||
MaxAge time.Duration
|
|
||||||
|
|
||||||
// Allows to add origins like http://some-domain/*, https://api.* or http://some.*.subdomain.com
|
|
||||||
AllowWildcard bool
|
|
||||||
|
|
||||||
// Allows usage of popular browser extensions schemas
|
|
||||||
AllowBrowserExtensions bool
|
|
||||||
|
|
||||||
// Allows to add custom schema like tauri://
|
|
||||||
CustomSchemas []string
|
|
||||||
|
|
||||||
// Allows usage of WebSocket protocol
|
|
||||||
AllowWebSockets bool
|
|
||||||
|
|
||||||
// Allows usage of file:// schema (dangerous!) use it only when you 100% sure it's needed
|
|
||||||
AllowFiles bool
|
|
||||||
|
|
||||||
// Allows to pass custom OPTIONS response status code for old browsers / clients
|
|
||||||
OptionsResponseStatusCode int
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAllowMethods is allowed to add custom methods
|
|
||||||
func (c *Config) AddAllowMethods(methods ...string) {
|
|
||||||
c.AllowMethods = append(c.AllowMethods, methods...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAllowHeaders is allowed to add custom headers
|
|
||||||
func (c *Config) AddAllowHeaders(headers ...string) {
|
|
||||||
c.AllowHeaders = append(c.AllowHeaders, headers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddExposeHeaders is allowed to add custom expose headers
|
|
||||||
func (c *Config) AddExposeHeaders(headers ...string) {
|
|
||||||
c.ExposeHeaders = append(c.ExposeHeaders, headers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) getAllowedSchemas() []string {
|
|
||||||
allowedSchemas := DefaultSchemas
|
|
||||||
if c.AllowBrowserExtensions {
|
|
||||||
allowedSchemas = append(allowedSchemas, ExtensionSchemas...)
|
|
||||||
}
|
|
||||||
if c.AllowWebSockets {
|
|
||||||
allowedSchemas = append(allowedSchemas, WebSocketSchemas...)
|
|
||||||
}
|
|
||||||
if c.AllowFiles {
|
|
||||||
allowedSchemas = append(allowedSchemas, FileSchemas...)
|
|
||||||
}
|
|
||||||
if c.CustomSchemas != nil {
|
|
||||||
allowedSchemas = append(allowedSchemas, c.CustomSchemas...)
|
|
||||||
}
|
|
||||||
return allowedSchemas
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) validateAllowedSchemas(origin string) bool {
|
|
||||||
allowedSchemas := c.getAllowedSchemas()
|
|
||||||
for _, schema := range allowedSchemas {
|
|
||||||
if strings.HasPrefix(origin, schema) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate is check configuration of user defined.
|
|
||||||
func (c Config) Validate() error {
|
|
||||||
hasOriginFn := c.AllowOriginFunc != nil
|
|
||||||
hasOriginFn = hasOriginFn || c.AllowOriginWithContextFunc != nil
|
|
||||||
|
|
||||||
if c.AllowAllOrigins && (hasOriginFn || len(c.AllowOrigins) > 0) {
|
|
||||||
originFields := strings.Join([]string{
|
|
||||||
"AllowOriginFunc",
|
|
||||||
"AllowOriginFuncWithContext",
|
|
||||||
"AllowOrigins",
|
|
||||||
}, " or ")
|
|
||||||
return fmt.Errorf(
|
|
||||||
"conflict settings: all origins enabled. %s is not needed",
|
|
||||||
originFields,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if !c.AllowAllOrigins && !hasOriginFn && len(c.AllowOrigins) == 0 {
|
|
||||||
return errors.New("conflict settings: all origins disabled")
|
|
||||||
}
|
|
||||||
for _, origin := range c.AllowOrigins {
|
|
||||||
if !strings.Contains(origin, "*") && !c.validateAllowedSchemas(origin) {
|
|
||||||
return errors.New("bad origin: origins must contain '*' or include " + strings.Join(c.getAllowedSchemas(), ","))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) parseWildcardRules() [][]string {
|
|
||||||
var wRules [][]string
|
|
||||||
|
|
||||||
if !c.AllowWildcard {
|
|
||||||
return wRules
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range c.AllowOrigins {
|
|
||||||
if !strings.Contains(o, "*") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if c := strings.Count(o, "*"); c > 1 {
|
|
||||||
panic(errors.New("only one * is allowed").Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
i := strings.Index(o, "*")
|
|
||||||
if i == 0 {
|
|
||||||
wRules = append(wRules, []string{"*", o[1:]})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i == (len(o) - 1) {
|
|
||||||
wRules = append(wRules, []string{o[:i], "*"})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wRules = append(wRules, []string{o[:i], o[i+1:]})
|
|
||||||
}
|
|
||||||
|
|
||||||
return wRules
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultConfig returns a generic default configuration mapped to localhost.
|
|
||||||
func DefaultConfig() Config {
|
|
||||||
return Config{
|
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
|
|
||||||
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
|
|
||||||
AllowCredentials: false,
|
|
||||||
MaxAge: 12 * time.Hour,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default returns the location middleware with default configuration.
|
|
||||||
func Default() gin.HandlerFunc {
|
|
||||||
config := DefaultConfig()
|
|
||||||
config.AllowAllOrigins = true
|
|
||||||
return New(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns the location middleware with user-defined custom configuration.
|
|
||||||
func New(config Config) gin.HandlerFunc {
|
|
||||||
cors := newCors(config)
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
cors.applyCors(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
90
vendor/github.com/gin-contrib/cors/utils.go
generated
vendored
90
vendor/github.com/gin-contrib/cors/utils.go
generated
vendored
@@ -1,90 +0,0 @@
|
|||||||
package cors
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type converter func(string) string
|
|
||||||
|
|
||||||
func generateNormalHeaders(c Config) http.Header {
|
|
||||||
headers := make(http.Header)
|
|
||||||
if c.AllowCredentials {
|
|
||||||
headers.Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
}
|
|
||||||
if len(c.ExposeHeaders) > 0 {
|
|
||||||
exposeHeaders := convert(normalize(c.ExposeHeaders), http.CanonicalHeaderKey)
|
|
||||||
headers.Set("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ","))
|
|
||||||
}
|
|
||||||
if c.AllowAllOrigins {
|
|
||||||
headers.Set("Access-Control-Allow-Origin", "*")
|
|
||||||
} else {
|
|
||||||
headers.Set("Vary", "Origin")
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
|
|
||||||
func generatePreflightHeaders(c Config) http.Header {
|
|
||||||
headers := make(http.Header)
|
|
||||||
if c.AllowCredentials {
|
|
||||||
headers.Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
}
|
|
||||||
if len(c.AllowMethods) > 0 {
|
|
||||||
allowMethods := convert(normalize(c.AllowMethods), strings.ToUpper)
|
|
||||||
value := strings.Join(allowMethods, ",")
|
|
||||||
headers.Set("Access-Control-Allow-Methods", value)
|
|
||||||
}
|
|
||||||
if len(c.AllowHeaders) > 0 {
|
|
||||||
allowHeaders := convert(normalize(c.AllowHeaders), http.CanonicalHeaderKey)
|
|
||||||
value := strings.Join(allowHeaders, ",")
|
|
||||||
headers.Set("Access-Control-Allow-Headers", value)
|
|
||||||
}
|
|
||||||
if c.MaxAge > time.Duration(0) {
|
|
||||||
value := strconv.FormatInt(int64(c.MaxAge/time.Second), 10)
|
|
||||||
headers.Set("Access-Control-Max-Age", value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AllowPrivateNetwork {
|
|
||||||
headers.Set("Access-Control-Allow-Private-Network", "true")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.AllowAllOrigins {
|
|
||||||
headers.Set("Access-Control-Allow-Origin", "*")
|
|
||||||
} else {
|
|
||||||
// Always set Vary headers
|
|
||||||
// see https://github.com/rs/cors/issues/10,
|
|
||||||
// https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001
|
|
||||||
|
|
||||||
headers.Add("Vary", "Origin")
|
|
||||||
headers.Add("Vary", "Access-Control-Request-Method")
|
|
||||||
headers.Add("Vary", "Access-Control-Request-Headers")
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize(values []string) []string {
|
|
||||||
if values == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
distinctMap := make(map[string]bool, len(values))
|
|
||||||
normalized := make([]string, 0, len(values))
|
|
||||||
for _, value := range values {
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
value = strings.ToLower(value)
|
|
||||||
if _, seen := distinctMap[value]; !seen {
|
|
||||||
normalized = append(normalized, value)
|
|
||||||
distinctMap[value] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return normalized
|
|
||||||
}
|
|
||||||
|
|
||||||
func convert(s []string, c converter) []string {
|
|
||||||
var out []string
|
|
||||||
for _, i := range s {
|
|
||||||
out = append(out, c(i))
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
1
vendor/github.com/oapi-codegen/gin-middleware/.gitignore
generated
vendored
1
vendor/github.com/oapi-codegen/gin-middleware/.gitignore
generated
vendored
@@ -1 +0,0 @@
|
|||||||
bin/
|
|
||||||
201
vendor/github.com/oapi-codegen/gin-middleware/LICENSE
generated
vendored
201
vendor/github.com/oapi-codegen/gin-middleware/LICENSE
generated
vendored
@@ -1,201 +0,0 @@
|
|||||||
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.
|
|
||||||
32
vendor/github.com/oapi-codegen/gin-middleware/Makefile
generated
vendored
32
vendor/github.com/oapi-codegen/gin-middleware/Makefile
generated
vendored
@@ -1,32 +0,0 @@
|
|||||||
GOBASE=$(shell pwd)
|
|
||||||
GOBIN=$(GOBASE)/bin
|
|
||||||
|
|
||||||
help:
|
|
||||||
@echo "This is a helper makefile for oapi-codegen"
|
|
||||||
@echo "Targets:"
|
|
||||||
@echo " generate: regenerate all generated files"
|
|
||||||
@echo " test: run all tests"
|
|
||||||
@echo " gin_example generate gin example server code"
|
|
||||||
@echo " tidy tidy go mod"
|
|
||||||
|
|
||||||
$(GOBIN)/golangci-lint:
|
|
||||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOBIN) v1.59.0
|
|
||||||
|
|
||||||
.PHONY: tools
|
|
||||||
tools: $(GOBIN)/golangci-lint
|
|
||||||
|
|
||||||
lint: tools
|
|
||||||
$(GOBIN)/golangci-lint run ./...
|
|
||||||
|
|
||||||
lint-ci: tools
|
|
||||||
$(GOBIN)/golangci-lint run ./... --out-format=github-actions --timeout=5m
|
|
||||||
|
|
||||||
generate:
|
|
||||||
go generate ./...
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -cover ./...
|
|
||||||
|
|
||||||
tidy:
|
|
||||||
@echo "tidy..."
|
|
||||||
go mod tidy
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user