feat: auth
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Compiled binaries and build artifacts
|
||||||
|
*.exe
|
||||||
|
*.bin
|
||||||
|
/bin/
|
||||||
|
/pkg/
|
||||||
|
|
||||||
|
# Module cache and dependencies (excluding go.mod and go.sum)
|
||||||
|
# go.mod and go.sum are typically tracked
|
||||||
|
# /vendor/ # Uncomment if you are not vendoring dependencies
|
||||||
|
|
||||||
|
# Editor/IDE specific files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# Log files and temporary data
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
180
api/auth/auth.go
Normal file
180
api/auth/auth.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Authenticator is used to authenticate our users.
|
||||||
|
type Authenticator struct {
|
||||||
|
*oidc.Provider
|
||||||
|
oauth2.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// New instantiates the *Authenticator.
|
||||||
|
func New() (*Authenticator, error) {
|
||||||
|
provider, err := oidc.NewProvider(
|
||||||
|
context.Background(),
|
||||||
|
"https://"+os.Getenv("AUTH0_DOMAIN")+"/",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf := oauth2.Config{
|
||||||
|
ClientID: os.Getenv("AUTH0_CLIENT_ID"),
|
||||||
|
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
|
||||||
|
RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"),
|
||||||
|
Endpoint: provider.Endpoint(),
|
||||||
|
Scopes: []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile", "email"},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Authenticator{
|
||||||
|
Provider: provider,
|
||||||
|
Config: conf,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyIDToken verifies that an *oauth2.Token is a valid *oidc.IDToken.
|
||||||
|
func (a *Authenticator) VerifyIDToken(ctx context.Context, token *oauth2.Token) (*oidc.IDToken, error) {
|
||||||
|
rawIDToken, ok := token.Extra("id_token").(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no id_token field in oauth2 token")
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcConfig := &oidc.Config{
|
||||||
|
ClientID: a.ClientID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.Verifier(oidcConfig).Verify(ctx, rawIDToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for our login.
|
||||||
|
func LoginHandler(auth *Authenticator) gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
state, err := generateRandomState()
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the state inside the session.
|
||||||
|
session := sessions.Default(ctx)
|
||||||
|
session.Set("state", state)
|
||||||
|
session.Options(sessions.Options{Path: "/"})
|
||||||
|
if err := session.Save(); err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
audience_url := "https://" + os.Getenv("AUTH0_DOMAIN") + "/api/v2/"
|
||||||
|
auth_url := auth.AuthCodeURL(state, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam("audience", audience_url))
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, auth_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler for our logout.
|
||||||
|
func LogoutHandler(ctx *gin.Context) {
|
||||||
|
logoutUrl, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/v2/logout")
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scheme := "http"
|
||||||
|
if ctx.Request.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
returnTo, err := url.Parse(scheme + "://" + ctx.Request.Host + os.Getenv("LOGOUT_CALLBACK_ENDPOINT"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters := url.Values{}
|
||||||
|
parameters.Add("returnTo", returnTo.String())
|
||||||
|
parameters.Add("client_id", os.Getenv("AUTH0_CLIENT_ID"))
|
||||||
|
logoutUrl.RawQuery = parameters.Encode()
|
||||||
|
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, logoutUrl.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogoutCallbackHandler(store cookie.Store) gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
session := sessions.Default(ctx)
|
||||||
|
session.Clear()
|
||||||
|
session.Options(sessions.Options{MaxAge: -1, Path: "/"})
|
||||||
|
err := session.Save()
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, "Failed to clear session")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(http.StatusSeeOther, os.Getenv("PUBLIC_LOCATION"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthenticationCallbackHandler(auth *Authenticator) gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
session := sessions.Default(ctx)
|
||||||
|
if ctx.Query("state") != session.Get("state") {
|
||||||
|
ctx.String(http.StatusBadRequest, "Invalid state parameter.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange an authorization code for a token.
|
||||||
|
token, err := auth.Exchange(ctx.Request.Context(), ctx.Query("code"))
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusUnauthorized, "Failed to convert an authorization code into a token.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idToken, err := auth.VerifyIDToken(ctx.Request.Context(), token)
|
||||||
|
if err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, "Failed to verify ID Token.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile map[string]interface{}
|
||||||
|
if err := idToken.Claims(&profile); err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(token.ExpiresIn)
|
||||||
|
session.Set("access_token", token.AccessToken)
|
||||||
|
session.Set("refresh_token", token.RefreshToken)
|
||||||
|
session.Set("profile", profile)
|
||||||
|
if err := session.Save(); err != nil {
|
||||||
|
ctx.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to logged in page.
|
||||||
|
ctx.Redirect(http.StatusTemporaryRedirect, "/user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomState() (string, error) {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
state := base64.StdEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
165
api/auth/middleware/authentication_middleware.go
Normal file
165
api/auth/middleware/authentication_middleware.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.conway.engineer/ada/ordr.git/auth"
|
||||||
|
"git.conway.engineer/ada/ordr.git/dto"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
|
|
||||||
|
"github.com/google/go-querystring/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TokenIsNotExpired(access_token string) bool {
|
||||||
|
parser := new(jwt.Parser)
|
||||||
|
|
||||||
|
token, err := parser.Parse(access_token, nil)
|
||||||
|
if err != nil && err.Error() != "no Keyfunc was provided." {
|
||||||
|
log.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
claims, claims_ok := token.Claims.(jwt.MapClaims)
|
||||||
|
if !claims_ok {
|
||||||
|
log.Fatal("failed to read claims")
|
||||||
|
}
|
||||||
|
|
||||||
|
exp, exp_ok := claims["exp"].(float64)
|
||||||
|
if !exp_ok {
|
||||||
|
log.Fatal("exp claim not present")
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Now().Before(time.Unix(int64(exp), 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserProfile(context *gin.Context) {
|
||||||
|
|
||||||
|
session := sessions.Default(context)
|
||||||
|
access_token := session.Get("access_token")
|
||||||
|
user_profile_client := http.Client{}
|
||||||
|
|
||||||
|
user_profile_url, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + os.Getenv("AUTH0_USER_INFO_ENDPOINT"))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to build user profile url " + err.Error())
|
||||||
|
context.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user_profile_request, user_profile_request_error := http.NewRequest("GET", user_profile_url.String(), nil)
|
||||||
|
if user_profile_request_error != nil {
|
||||||
|
log.Println("Failed to initialize validation request", user_profile_request_error.Error())
|
||||||
|
context.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
user_profile_request.Header.Add("Accept", "application/json")
|
||||||
|
user_profile_request.Header.Add("Authorization", "Bearer "+access_token.(string))
|
||||||
|
|
||||||
|
user_profile_response, user_profile_response_err := user_profile_client.Do(user_profile_request)
|
||||||
|
if user_profile_response_err != nil {
|
||||||
|
log.Println("Failed to validate user")
|
||||||
|
context.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer user_profile_response.Body.Close()
|
||||||
|
user_profile_bytes, _ := io.ReadAll(user_profile_response.Body)
|
||||||
|
|
||||||
|
var user_profile dto.UserProfileResponse
|
||||||
|
json.Unmarshal(user_profile_bytes, &user_profile)
|
||||||
|
context.Set("user_profile", user_profile)
|
||||||
|
context.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleRefreshToken(session sessions.Session) bool {
|
||||||
|
token_url, token_url_err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + os.Getenv("AUTH0_TOKEN_ENDPOINT"))
|
||||||
|
if token_url_err != nil {
|
||||||
|
log.Fatal("Failed to parse token url", token_url_err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_token := session.Get("refresh_token")
|
||||||
|
refresh_request_dto := dto.RefreshTokenRequest{
|
||||||
|
GrantType: "refresh_token",
|
||||||
|
ClientId: os.Getenv("AUTH0_CLIENT_ID"),
|
||||||
|
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
|
||||||
|
RefreshToken: refresh_token.(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_body, marshal_err := query.Values(refresh_request_dto)
|
||||||
|
if marshal_err != nil {
|
||||||
|
log.Fatal("failed to marshal object: ", marshal_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh_client := http.Client{}
|
||||||
|
refresh_request, refresh_request_err := http.NewRequest("POST", token_url.String(), bytes.NewReader([]byte(refresh_body.Encode())))
|
||||||
|
if refresh_request_err != nil {
|
||||||
|
log.Fatal("Failed to create a request: ", refresh_request_err.Error())
|
||||||
|
}
|
||||||
|
refresh_request.Header.Add("content-type", "application/x-www-form-urlencoded")
|
||||||
|
refresh_response, refresh_response_err := refresh_client.Do(refresh_request)
|
||||||
|
if refresh_response_err != nil {
|
||||||
|
log.Fatal("Failed to fetch refresh token and response: ", refresh_response_err.Error())
|
||||||
|
}
|
||||||
|
defer refresh_response.Body.Close()
|
||||||
|
|
||||||
|
if refresh_response.StatusCode == http.StatusOK {
|
||||||
|
var response_body dto.RefreshTokenResponse
|
||||||
|
json_decoder := json.NewDecoder(refresh_response.Body)
|
||||||
|
content_unmarshal_err := json_decoder.Decode(&response_body)
|
||||||
|
|
||||||
|
if content_unmarshal_err != nil {
|
||||||
|
log.Println("Faileed to unmarshal data")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Set("access_token", response_body.AccessToken)
|
||||||
|
session.Set("refresh_token", response_body.RefreshToken)
|
||||||
|
session.Options(sessions.Options{Path: "/"})
|
||||||
|
session_save_error := session.Save()
|
||||||
|
|
||||||
|
if session_save_error != nil {
|
||||||
|
log.Println("Failed to set session: " + session_save_error.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAuthenticated(auth *auth.Authenticator) gin.HandlerFunc {
|
||||||
|
return func(context *gin.Context) {
|
||||||
|
session := sessions.Default(context)
|
||||||
|
|
||||||
|
if session.Get("profile") == nil {
|
||||||
|
context.Redirect(http.StatusSeeOther, "/auth/login")
|
||||||
|
context.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
access_token := session.Get("access_token")
|
||||||
|
|
||||||
|
if access_token == nil {
|
||||||
|
context.Redirect(http.StatusSeeOther, "/auth/login")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if TokenIsNotExpired(access_token.(string)) {
|
||||||
|
context.Next()
|
||||||
|
} else {
|
||||||
|
if !HandleRefreshToken(session) {
|
||||||
|
context.String(http.StatusUnauthorized, "Failed to refresh access token")
|
||||||
|
} else {
|
||||||
|
context.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
api/go.mod
Normal file
48
api/go.mod
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
module git.conway.engineer/ada/ordr.git/api
|
||||||
|
|
||||||
|
go 1.25.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coreos/go-oidc/v3 v3.16.0
|
||||||
|
github.com/gin-contrib/sessions v1.0.4
|
||||||
|
github.com/gin-gonic/gin v1.11.0
|
||||||
|
golang.org/x/oauth2 v0.33.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.14.0 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||||
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/gorilla/sessions v1.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
golang.org/x/arch v0.20.0 // indirect
|
||||||
|
golang.org/x/crypto v0.40.0 // indirect
|
||||||
|
golang.org/x/mod v0.25.0 // indirect
|
||||||
|
golang.org/x/net v0.42.0 // indirect
|
||||||
|
golang.org/x/sync v0.16.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.27.0 // indirect
|
||||||
|
golang.org/x/tools v0.34.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.9 // indirect
|
||||||
|
)
|
||||||
94
api/go.sum
Normal file
94
api/go.sum
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||||
|
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||||
|
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||||
|
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||||
|
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||||
|
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
|
||||||
|
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||||
|
github.com/gin-contrib/sessions v1.0.4 h1:ha6CNdpYiTOK/hTp05miJLbpTSNfOnFg5Jm2kbcqy8U=
|
||||||
|
github.com/gin-contrib/sessions v1.0.4/go.mod h1:ccmkrb2z6iU2osiAHZG3x3J4suJK+OU27oqzlWOqQgs=
|
||||||
|
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-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
|
||||||
|
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||||
|
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||||
|
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||||
|
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||||
|
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||||
|
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
Reference in New Issue
Block a user