From 6bf6a8ffc20925a70b33320f14d32a06bc2ed986 Mon Sep 17 00:00:00 2001 From: AmirMahdi Qiasvand Date: Tue, 9 Sep 2025 13:31:41 +0330 Subject: [PATCH] chore: update session and challenge repo -- using Redis for caching session and challenge verifying -- added api handler docs --- .gitignore | 2 + cmd/api/main.go | 22 ++ docs/docs.go | 206 +++++++++++++++++++ docs/swagger.json | 182 ++++++++++++++++ docs/swagger.yaml | 120 +++++++++++ go.mod | 52 +++-- go.sum | 122 ++++++++--- internal/api/dto/auth.go | 1 - internal/api/http/handlers/auth_handler.go | 44 ++-- internal/api/http/setup.go | 28 ++- internal/app/app.go | 65 ++++++ internal/domain/user.go | 10 +- internal/repository/cache/.gitkeep | 0 internal/repository/cache/challenge_repo.go | 80 +++++++ internal/repository/cache/session_repo.go | 135 ++++++++++++ internal/repository/storage/session_repo.go | 56 ----- internal/repository/storage/types/session.go | 44 ---- internal/usecase/auth_service.go | 45 +++- pkg/cache/redis.go | 36 ++++ sample.config.yaml | 20 ++ 20 files changed, 1077 insertions(+), 193 deletions(-) create mode 100644 .gitignore create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml delete mode 100644 internal/repository/cache/.gitkeep create mode 100644 internal/repository/cache/challenge_repo.go create mode 100644 internal/repository/cache/session_repo.go delete mode 100644 internal/repository/storage/session_repo.go delete mode 100644 internal/repository/storage/types/session.go create mode 100644 pkg/cache/redis.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9ea977 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +config.yaml +bin \ No newline at end of file diff --git a/cmd/api/main.go b/cmd/api/main.go index 06ab7d0..e380090 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -1 +1,23 @@ package main + +import ( + "backend/config" + "backend/internal/api/http" + "backend/internal/app" + "log" +) + +func main() { + cfg, err := config.ReadStandard("config.yaml") + if err != nil { + log.Fatalf("Failed to load configuration: %v", err) + } + + appContainer, err := app.NewAppContainer(cfg) + if err != nil { + log.Fatalf("Failed to initialize application: %v", err) + } + defer appContainer.Close() + + http.Run(cfg.Server, appContainer) +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..a709b06 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,206 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/authenticate": { + "post": { + "description": "Authenticate user with wallet signature", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Authenticate user", + "parameters": [ + { + "description": "Authentication Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AuthenticateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AuthenticateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/auth/challenge": { + "post": { + "description": "Generate a challenge message for wallet authentication", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Generate authentication challenge", + "parameters": [ + { + "description": "Challenge Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChallengeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ChallengeResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "definitions": { + "dto.AuthenticateRequest": { + "type": "object", + "required": [ + "pubKey", + "signature" + ], + "properties": { + "pubKey": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + }, + "dto.AuthenticateResponse": { + "type": "object", + "properties": { + "authorizationToken": { + "type": "string" + }, + "expiresAt": { + "type": "integer" + }, + "refreshToken": { + "type": "string" + } + } + }, + "dto.ChallengeRequest": { + "type": "object", + "required": [ + "pubKey" + ], + "properties": { + "pubKey": { + "type": "string" + } + } + }, + "dto.ChallengeResponse": { + "type": "object", + "properties": { + "expiresAt": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timeStamp": { + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/api", + Schemes: []string{}, + Title: "DeZone API", + Description: "This is the DeZone backend API server.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..62aae28 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,182 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is the DeZone backend API server.", + "title": "DeZone API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "url": "http://www.swagger.io/support", + "email": "support@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/api", + "paths": { + "/auth/authenticate": { + "post": { + "description": "Authenticate user with wallet signature", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Authenticate user", + "parameters": [ + { + "description": "Authentication Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AuthenticateRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AuthenticateResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/auth/challenge": { + "post": { + "description": "Generate a challenge message for wallet authentication", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Generate authentication challenge", + "parameters": [ + { + "description": "Challenge Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChallengeRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ChallengeResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "definitions": { + "dto.AuthenticateRequest": { + "type": "object", + "required": [ + "pubKey", + "signature" + ], + "properties": { + "pubKey": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + }, + "dto.AuthenticateResponse": { + "type": "object", + "properties": { + "authorizationToken": { + "type": "string" + }, + "expiresAt": { + "type": "integer" + }, + "refreshToken": { + "type": "string" + } + } + }, + "dto.ChallengeRequest": { + "type": "object", + "required": [ + "pubKey" + ], + "properties": { + "pubKey": { + "type": "string" + } + } + }, + "dto.ChallengeResponse": { + "type": "object", + "properties": { + "expiresAt": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timeStamp": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..45c3627 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,120 @@ +basePath: /api +definitions: + dto.AuthenticateRequest: + properties: + pubKey: + type: string + signature: + type: string + required: + - pubKey + - signature + type: object + dto.AuthenticateResponse: + properties: + authorizationToken: + type: string + expiresAt: + type: integer + refreshToken: + type: string + type: object + dto.ChallengeRequest: + properties: + pubKey: + type: string + required: + - pubKey + type: object + dto.ChallengeResponse: + properties: + expiresAt: + type: string + message: + type: string + timeStamp: + type: string + type: object +host: localhost:8080 +info: + contact: + email: support@swagger.io + name: API Support + url: http://www.swagger.io/support + description: This is the DeZone backend API server. + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + termsOfService: http://swagger.io/terms/ + title: DeZone API + version: "1.0" +paths: + /auth/authenticate: + post: + consumes: + - application/json + description: Authenticate user with wallet signature + parameters: + - description: Authentication Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.AuthenticateRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.AuthenticateResponse' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "401": + description: Unauthorized + schema: + additionalProperties: + type: string + type: object + summary: Authenticate user + tags: + - auth + /auth/challenge: + post: + consumes: + - application/json + description: Generate a challenge message for wallet authentication + parameters: + - description: Challenge Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.ChallengeRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.ChallengeResponse' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Generate authentication challenge + tags: + - auth +swagger: "2.0" diff --git a/go.mod b/go.mod index 50d1353..756f094 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,39 @@ module backend -go 1.25.0 +go 1.25.1 require ( github.com/ethereum/go-ethereum v1.16.3 + github.com/gofiber/adaptor/v2 v2.2.1 github.com/gofiber/fiber/v2 v2.52.9 github.com/golang-jwt/jwt/v5 v5.3.0 github.com/google/uuid v1.6.0 - github.com/spf13/viper v1.20.1 + github.com/redis/go-redis/v9 v9.13.0 + github.com/spf13/viper v1.21.0 + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.16.6 gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.30.2 + gorm.io/gorm v1.30.5 ) require ( + github.com/KyleBanks/depth v1.2.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/holiman/uint256 v1.3.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -30,27 +41,32 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/supranational/blst v0.3.14 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.40.0 // indirect + golang.org/x/mod v0.26.0 // indirect + golang.org/x/net v0.42.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/tools v0.35.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index cd661d5..5f64a9a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDOSA= github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= @@ -6,6 +8,10 @@ github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1 github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= @@ -14,6 +20,7 @@ github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -21,6 +28,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= @@ -33,12 +42,24 @@ github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeD github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= +github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= @@ -65,18 +86,27 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -88,39 +118,49 @@ github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.13.0 h1:PpmlVykE0ODh8P43U0HqC+2NXHXwG+GUtQyz+MPKGRg= +github.com/redis/go-redis/v9 v9.13.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -131,29 +171,43 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +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.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg= +golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +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.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= +golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs= -gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gorm.io/gorm v1.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s= +gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= diff --git a/internal/api/dto/auth.go b/internal/api/dto/auth.go index c1470ef..2d08e7f 100644 --- a/internal/api/dto/auth.go +++ b/internal/api/dto/auth.go @@ -13,7 +13,6 @@ type ChallengeResponse struct { type AuthenticateRequest struct { PubKey string `json:"pubKey" validate:"required,eth_pubkey"` Signature string `json:"signature" validate:"required,eth_signature"` - Message string `json:"message" validate:"required,uuid"` } type AuthenticateResponse struct { diff --git a/internal/api/http/handlers/auth_handler.go b/internal/api/http/handlers/auth_handler.go index 347409b..383e41a 100644 --- a/internal/api/http/handlers/auth_handler.go +++ b/internal/api/http/handlers/auth_handler.go @@ -2,12 +2,9 @@ package http import ( "backend/internal/api/dto" - "backend/internal/domain" "backend/internal/usecase" - "time" "github.com/gofiber/fiber/v2" - "github.com/google/uuid" ) type AuthHandler struct { @@ -20,6 +17,17 @@ func NewAuthHandler(authService *usecase.AuthService) *AuthHandler { } } +// GenerateChallenge generates a challenge for authentication +// @Summary Generate authentication challenge +// @Description Generate a challenge message for wallet authentication +// @Tags auth +// @Accept json +// @Produce json +// @Param request body dto.ChallengeRequest true "Challenge Request" +// @Success 200 {object} dto.ChallengeResponse +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /auth/challenge [post] func (h *AuthHandler) GenerateChallenge(c *fiber.Ctx) error { var req dto.ChallengeRequest if err := c.BodyParser(&req); err != nil { @@ -30,18 +38,29 @@ func (h *AuthHandler) GenerateChallenge(c *fiber.Ctx) error { challenge, err := h.authService.GenerateChallenge(c.Context(), req.PubKey) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ - "error": "failed to generate challenge", + "error": err.Error(), }) } return c.Status(fiber.StatusOK).JSON( dto.ChallengeResponse{ - Message: challenge.Message.String(), + Message: challenge.Message, TimeStamp: challenge.TimeStamp.String(), ExpiresAt: challenge.ExpiresAt.String(), }, ) } +// Authenticate authenticates a user with signed challenge +// @Summary Authenticate user +// @Description Authenticate user with wallet signature +// @Tags auth +// @Accept json +// @Produce json +// @Param request body dto.AuthenticateRequest true "Authentication Request" +// @Success 200 {object} dto.AuthenticateResponse +// @Failure 400 {object} map[string]string +// @Failure 401 {object} map[string]string +// @Router /auth/authenticate [post] func (h *AuthHandler) Authenticate(c *fiber.Ctx) error { var req dto.AuthenticateRequest if err := c.BodyParser(&req); err != nil { @@ -50,25 +69,10 @@ func (h *AuthHandler) Authenticate(c *fiber.Ctx) error { }) } - messageUUID, err := uuid.Parse(req.Message) - if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ - "error": "invalid message format", - }) - } - - challenge := &domain.Challenge{ - Message: messageUUID, - //TODO: fetch from cache - TimeStamp: time.Now().UTC(), - ExpiresAt: time.Now().Add(5 * time.Minute), - } - userToken, err := h.authService.Authenticate( c.Context(), req.PubKey, req.Signature, - challenge, // add chainID to cfg 1, c.IP(), diff --git a/internal/api/http/setup.go b/internal/api/http/setup.go index 8610cb5..bbbee41 100644 --- a/internal/api/http/setup.go +++ b/internal/api/http/setup.go @@ -2,26 +2,40 @@ package http import ( "backend/config" + "backend/docs" + httpHandlers "backend/internal/api/http/handlers" "backend/internal/app" "fmt" "log" + "github.com/gofiber/adaptor/v2" "github.com/gofiber/fiber/v2" + httpSwagger "github.com/swaggo/http-swagger" ) func Run(cfg config.Server, app *app.AppContainer) { - fiberApp := fiber.New() - api := fiberApp.Group("/api") - // register routes here + router := fiber.New() + + api := router.Group("/api") + registerPublicRoutes(api, app) - log.Fatal(fiberApp.Listen(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))) + docs.SwaggerInfo.Host = "" + docs.SwaggerInfo.Schemes = []string{} + docs.SwaggerInfo.BasePath = "/api" + + router.Get("/swagger/*", adaptor.HTTPHandler(httpSwagger.Handler())) + + log.Fatal(router.Listen(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))) } func registerPublicRoutes(router fiber.Router, app *app.AppContainer) { authgroup := router.Group("/auth") - //TODO: implement handlers - authgroup.Post("/challenge") - authgroup.Post("/authenticate") + authService := app.AuthService() + authHandler := httpHandlers.NewAuthHandler(&authService) + + // Register auth routes + authgroup.Post("/challenge", authHandler.GenerateChallenge) + authgroup.Post("/authenticate", authHandler.Authenticate) } diff --git a/internal/app/app.go b/internal/app/app.go index 9436396..c457e52 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,7 +2,13 @@ package app import ( "backend/config" + "backend/internal/repository/cache" + "backend/internal/repository/storage" "backend/internal/usecase" + cachePackage "backend/pkg/cache" + "backend/pkg/postgres" + "fmt" + "log" "gorm.io/gorm" ) @@ -10,5 +16,64 @@ import ( type AppContainer struct { cfg config.Config dbConn *gorm.DB + redis *cachePackage.Redis authService usecase.AuthService } + +func NewAppContainer(cfg config.Config) (*AppContainer, error) { + dbOptions := postgres.DBConnOptions{ + User: cfg.DB.User, + Pass: cfg.DB.Pass, + Host: cfg.DB.Host, + Port: uint(cfg.DB.Port), + DBName: cfg.DB.DBName, + } + dbConn, err := postgres.NewPsqlGormConnection(dbOptions) + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + redis, err := cachePackage.NewRedis(cfg.Redis) + if err != nil { + return nil, fmt.Errorf("failed to connect to Redis: %w", err) + } + + userRepo := storage.NewUserRepository(dbConn) + sessionRepo := cache.NewSessionRepository(redis) + challengeRepo := cache.NewChallengeRepository(redis) + + authService := usecase.NewAuthService( + userRepo, + sessionRepo, + challengeRepo, + 30, + cfg.JWT, + ) + + return &AppContainer{ + cfg: cfg, + dbConn: dbConn, + redis: redis, + authService: authService, + }, nil +} + +func (app *AppContainer) AuthService() usecase.AuthService { + return app.authService +} + +func (app *AppContainer) Close() { + if app.redis != nil { + if err := app.redis.Close(); err != nil { + log.Printf("Error closing Redis connection: %v", err) + } + } + + if app.dbConn != nil { + if sqlDB, err := app.dbConn.DB(); err == nil { + if err := sqlDB.Close(); err != nil { + log.Printf("Error closing database connection: %v", err) + } + } + } +} diff --git a/internal/domain/user.go b/internal/domain/user.go index 22cc526..06e728b 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -32,6 +32,12 @@ type UserRepo interface { Delete(ctx context.Context, id uuid.UUID) error } +type ChallengeRepo interface { + Create(ctx context.Context, pubKey string, challenge *Challenge) error + GetByPubKey(ctx context.Context, pubKey string) (*Challenge, error) + Delete(ctx context.Context, pubKey string) error +} + type User struct { ID uuid.UUID PubKey string @@ -51,7 +57,7 @@ type User struct { // TODO: move to another file? type Challenge struct { - Message uuid.UUID + Message string TimeStamp time.Time ExpiresAt time.Time } @@ -70,7 +76,7 @@ func (c *Challenge) Verify(address string, signedMsg string) (bool, error) { return false, fmt.Errorf("decode signature: %w", err) } - challengeBytes := []byte(c.Message.String()) + challengeBytes := []byte(c.Message) return c.VerifySignedBytes(address, challengeBytes, signature) } diff --git a/internal/repository/cache/.gitkeep b/internal/repository/cache/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/internal/repository/cache/challenge_repo.go b/internal/repository/cache/challenge_repo.go new file mode 100644 index 0000000..b84f269 --- /dev/null +++ b/internal/repository/cache/challenge_repo.go @@ -0,0 +1,80 @@ +package cache + +import ( + "backend/internal/domain" + "backend/pkg/cache" + "context" + "encoding/json" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +type ChallengeRepository struct { + redis *cache.Redis +} + +func NewChallengeRepository(redis *cache.Redis) domain.ChallengeRepo { + return &ChallengeRepository{ + redis: redis, + } +} + +func (r *ChallengeRepository) Create(ctx context.Context, pubKey string, challenge *domain.Challenge) error { + key := r.getChallengeKey(pubKey) + + challengeData, err := json.Marshal(challenge) + if err != nil { + return fmt.Errorf("failed to marshal challenge: %w", err) + } + + ttl := time.Until(challenge.ExpiresAt) + if ttl <= 0 { + return fmt.Errorf("challenge already expired") + } + + if err := r.redis.Client().Set(ctx, key, challengeData, ttl).Err(); err != nil { + return fmt.Errorf("failed to store challenge in Redis: %w", err) + } + + return nil +} + +func (r *ChallengeRepository) GetByPubKey(ctx context.Context, pubKey string) (*domain.Challenge, error) { + key := r.getChallengeKey(pubKey) + + result, err := r.redis.Client().Get(ctx, key).Result() + if err != nil { + if err == redis.Nil { + return nil, fmt.Errorf("challenge not found") + } + return nil, fmt.Errorf("failed to get challenge from Redis: %w", err) + } + + var challenge domain.Challenge + if err := json.Unmarshal([]byte(result), &challenge); err != nil { + return nil, fmt.Errorf("failed to unmarshal challenge: %w", err) + } + + if challenge.IsExpired() { + r.Delete(ctx, pubKey) + return nil, fmt.Errorf("challenge expired") + } + + return &challenge, nil +} + +func (r *ChallengeRepository) Delete(ctx context.Context, pubKey string) error { + key := r.getChallengeKey(pubKey) + + if err := r.redis.Client().Del(ctx, key).Err(); err != nil { + return fmt.Errorf("failed to delete challenge from Redis: %w", err) + } + + return nil +} + +func (r *ChallengeRepository) getChallengeKey(pubKey string) string { + return fmt.Sprintf("challenge:%s", pubKey) +} diff --git a/internal/repository/cache/session_repo.go b/internal/repository/cache/session_repo.go new file mode 100644 index 0000000..937676a --- /dev/null +++ b/internal/repository/cache/session_repo.go @@ -0,0 +1,135 @@ +package cache + +import ( + "backend/internal/domain" + "backend/pkg/cache" + "context" + "encoding/json" + "fmt" + "time" + + "github.com/google/uuid" + "github.com/redis/go-redis/v9" +) + +type SessionRepository struct { + redis *cache.Redis +} + +func NewSessionRepository(redis *cache.Redis) domain.SessionRepo { + return &SessionRepository{ + redis: redis, + } +} + +func (r *SessionRepository) Create(ctx context.Context, session *domain.UserSession) error { + key := r.getSessionKey(session.ID) + + sessionData, err := json.Marshal(session) + if err != nil { + return fmt.Errorf("failed to marshal session: %w", err) + } + + ttl := time.Until(session.ExpiresAt) + if ttl <= 0 { + return fmt.Errorf("session already expired") + } + + if err := r.redis.Client().Set(ctx, key, sessionData, ttl).Err(); err != nil { + return fmt.Errorf("failed to store session in Redis: %w", err) + } + + userSessionsKey := r.getUserSessionsKey(session.UserID) + if err := r.redis.Client().SAdd(ctx, userSessionsKey, session.ID.String()).Err(); err != nil { + return fmt.Errorf("failed to add session to user sessions set: %w", err) + } + + if err := r.redis.Client().Expire(ctx, userSessionsKey, ttl).Err(); err != nil { + return fmt.Errorf("failed to set TTL for user sessions set: %w", err) + } + + return nil +} + +func (r *SessionRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.UserSession, error) { + key := r.getSessionKey(id) + + result, err := r.redis.Client().Get(ctx, key).Result() + if err != nil { + if err == redis.Nil { + return nil, fmt.Errorf("session not found") + } + return nil, fmt.Errorf("failed to get session from Redis: %w", err) + } + + var session domain.UserSession + if err := json.Unmarshal([]byte(result), &session); err != nil { + return nil, fmt.Errorf("failed to unmarshal session: %w", err) + } + + if time.Now().After(session.ExpiresAt) { + r.Delete(ctx, id) + return nil, fmt.Errorf("session expired") + } + + return &session, nil +} + +func (r *SessionRepository) Delete(ctx context.Context, id uuid.UUID) error { + key := r.getSessionKey(id) + + session, err := r.GetByID(ctx, id) + if err != nil { + return nil + } + + if err := r.redis.Client().Del(ctx, key).Err(); err != nil { + return fmt.Errorf("failed to delete session from Redis: %w", err) + } + + userSessionsKey := r.getUserSessionsKey(session.UserID) + if err := r.redis.Client().SRem(ctx, userSessionsKey, id.String()).Err(); err != nil { + return fmt.Errorf("failed to remove session from user sessions set: %w", err) + } + + return nil +} + +func (r *SessionRepository) GetUserSessions(ctx context.Context, userID uuid.UUID) ([]domain.UserSession, error) { + userSessionsKey := r.getUserSessionsKey(userID) + + sessionIDs, err := r.redis.Client().SMembers(ctx, userSessionsKey).Result() + if err != nil { + if err == redis.Nil { + return []domain.UserSession{}, nil + } + return nil, fmt.Errorf("failed to get user sessions from Redis: %w", err) + } + + var sessions []domain.UserSession + for _, sessionIDStr := range sessionIDs { + sessionID, err := uuid.Parse(sessionIDStr) + if err != nil { + r.redis.Client().SRem(ctx, userSessionsKey, sessionIDStr) + continue + } + + session, err := r.GetByID(ctx, sessionID) + if err != nil { + r.redis.Client().SRem(ctx, userSessionsKey, sessionIDStr) + continue + } + + sessions = append(sessions, *session) + } + + return sessions, nil +} + +func (r *SessionRepository) getSessionKey(sessionID uuid.UUID) string { + return fmt.Sprintf("session:%s", sessionID.String()) +} + +func (r *SessionRepository) getUserSessionsKey(userID uuid.UUID) string { + return fmt.Sprintf("user_sessions:%s", userID.String()) +} diff --git a/internal/repository/storage/session_repo.go b/internal/repository/storage/session_repo.go deleted file mode 100644 index 60a6729..0000000 --- a/internal/repository/storage/session_repo.go +++ /dev/null @@ -1,56 +0,0 @@ -package storage - -import ( - "backend/internal/domain" - "backend/internal/repository/storage/types" - "context" - - "github.com/google/uuid" - "gorm.io/gorm" -) - -type SessionRepository struct { - db *gorm.DB -} - -func NewSessionRepository(db *gorm.DB) domain.SessionRepo { - return &SessionRepository{ - db: db, - } -} - -func (r *SessionRepository) Create(ctx context.Context, session *domain.UserSession) error { - model := types.CastSessionToStorage(*session) - return r.db.WithContext(ctx).Create(&model).Error -} - -func (r *SessionRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.UserSession, error) { - var session types.UserSession - if err := r.db.WithContext(ctx).Preload("User").First(&session, "id = ?", id).Error; err != nil { - return nil, err - } - - return types.CastSessionToDomain(session), nil -} - -func (r *SessionRepository) Delete(ctx context.Context, id uuid.UUID) error { - var session types.UserSession - if err := r.db.WithContext(ctx).First(&session, "id = ?", id).Error; err != nil { - return err - } - - return r.db.WithContext(ctx).Delete(&session).Error -} - -func (r *SessionRepository) GetUserSessions(ctx context.Context, userID uuid.UUID) ([]domain.UserSession, error) { - var sessions []types.UserSession - if err := r.db.WithContext(ctx).Preload("User").Find(&sessions, "user_id = ?", userID).Error; err != nil { - return nil, err - } - - result := make([]domain.UserSession, len(sessions)) - for i, session := range sessions { - result[i] = *types.CastSessionToDomain(session) - } - return result, nil -} diff --git a/internal/repository/storage/types/session.go b/internal/repository/storage/types/session.go deleted file mode 100644 index 23b6a2a..0000000 --- a/internal/repository/storage/types/session.go +++ /dev/null @@ -1,44 +0,0 @@ -package types - -import ( - "backend/internal/domain" - "time" - - "github.com/google/uuid" -) - -type UserSession struct { - Base - UserID uuid.UUID - User *User - WalletID string - ExpireAt time.Time -} - -func CastSessionToStorage(s domain.UserSession) *UserSession { - return &UserSession{ - Base: Base{ - ID: s.ID, - CreatedAt: s.CreatedAt, - UpdatedAt: s.UpdatedAt, - DeletedAt: s.DeletedAt, - }, - UserID: s.UserID, - User: CastUserToStorage(s.User), - WalletID: s.WalletID, - ExpireAt: s.ExpiresAt, - } -} - -func CastSessionToDomain(s UserSession) *domain.UserSession { - return &domain.UserSession{ - ID: s.ID, - UserID: s.UserID, - User: *CastUserToDomain(*s.User), - WalletID: s.WalletID, - ExpiresAt: s.ExpireAt, - CreatedAt: s.CreatedAt, - UpdatedAt: s.UpdatedAt, - DeletedAt: s.DeletedAt, - } -} diff --git a/internal/usecase/auth_service.go b/internal/usecase/auth_service.go index 6d24978..275db71 100644 --- a/internal/usecase/auth_service.go +++ b/internal/usecase/auth_service.go @@ -17,14 +17,15 @@ import ( type AuthService interface { GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) - Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error) + Authenticate(ctx context.Context, pubKey string, signature string, chainID uint, ipAddress, userAgent string) (*UserToken, error) } type authService struct { - userRepo domain.UserRepo - sessionRepo domain.SessionRepo - challengeExp uint - cfg config.JWT + userRepo domain.UserRepo + sessionRepo domain.SessionRepo + challengeRepo domain.ChallengeRepo + challengeExp uint + cfg config.JWT } type UserToken struct { @@ -36,14 +37,16 @@ type UserToken struct { func NewAuthService( userRepo domain.UserRepo, sessionRepo domain.SessionRepo, + challengeRepo domain.ChallengeRepo, challengeExp uint, cfg config.JWT, ) AuthService { return &authService{ - userRepo: userRepo, - sessionRepo: sessionRepo, - challengeExp: challengeExp, - cfg: cfg, + userRepo: userRepo, + sessionRepo: sessionRepo, + challengeRepo: challengeRepo, + challengeExp: challengeExp, + cfg: cfg, } } @@ -54,19 +57,32 @@ func (s *authService) GenerateChallenge(ctx context.Context, pubKey string) (*do } challenge := &domain.Challenge{ - Message: uuid.New(), + Message: uuid.New().String(), TimeStamp: time.Now().UTC(), ExpiresAt: time.Now().Add(time.Duration(s.challengeExp) * time.Minute), } + + // Save challenge to Redis + err = s.challengeRepo.Create(ctx, pubKey, challenge) + if err != nil { + return nil, fmt.Errorf("failed to save challenge: %w", err) + } + return challenge, nil } -func (s *authService) Authenticate(ctx context.Context, pubKey string, signature string, challenge *domain.Challenge, chainID uint, ipAddress, userAgent string) (*UserToken, error) { +func (s *authService) Authenticate(ctx context.Context, pubKey string, signature string, chainID uint, ipAddress, userAgent string) (*UserToken, error) { _, err := common.IsValidPublicKey(pubKey) if err != nil { return nil, fmt.Errorf("invalid public key: %w", err) } + // Retrieve challenge from Redis + challenge, err := s.challengeRepo.GetByPubKey(ctx, pubKey) + if err != nil { + return nil, fmt.Errorf("failed to retrieve challenge: %w", err) + } + isValid, err := challenge.Verify(pubKey, signature) if err != nil { return nil, fmt.Errorf("signature verification failed: %w", err) @@ -75,6 +91,13 @@ func (s *authService) Authenticate(ctx context.Context, pubKey string, signature if !isValid { return nil, errors.New("invalid signature") } + + // Delete the challenge after successful verification to prevent replay attacks + err = s.challengeRepo.Delete(ctx, pubKey) + if err != nil { + return nil, fmt.Errorf("failed to delete challenge: %w", err) + } + user, err := s.userRepo.GetByPubKey(ctx, pubKey) if err != nil { return nil, err diff --git a/pkg/cache/redis.go b/pkg/cache/redis.go new file mode 100644 index 0000000..e030e02 --- /dev/null +++ b/pkg/cache/redis.go @@ -0,0 +1,36 @@ +package cache + +import ( + "backend/config" + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +type Redis struct { + client *redis.Client +} + +func NewRedis(cfg config.Redis) (*Redis, error) { + rdb := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), + Password: cfg.Pass, + DB: 0, + }) + + ctx := context.Background() + if err := rdb.Ping(ctx).Err(); err != nil { + return nil, fmt.Errorf("failed to connect to Redis: %w", err) + } + + return &Redis{client: rdb}, nil +} + +func (r *Redis) Client() *redis.Client { + return r.client +} + +func (r *Redis) Close() error { + return r.client.Close() +} diff --git a/sample.config.yaml b/sample.config.yaml index e69de29..19ae5ae 100644 --- a/sample.config.yaml +++ b/sample.config.yaml @@ -0,0 +1,20 @@ +server: + host: "localhost" + port: 8080 + +db: + user: "your_db_user" + pass: "your_db_password" + host: "localhost" + port: 5432 + db_name: "dezone" + +redis: + host: "localhost" + port: 6379 + pass: "" + +jwt: + token_exp_minutes: 60 + refresh_token_exp_minutes: 10080 # 7 days + token_secret: "your_jwt_secret_key_here" \ No newline at end of file