Compare commits
No commits in common. "feat/DEZON-80/user" and "main" have entirely different histories.
feat/DEZON
...
main
51
.air.toml
51
.air.toml
@ -1,51 +0,0 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main cmd/api/main.go "
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
config.yaml
|
||||
bin
|
||||
tmp
|
||||
0
cmd/api/.gitkeep
Normal file
0
cmd/api/.gitkeep
Normal file
@ -1,23 +0,0 @@
|
||||
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)
|
||||
}
|
||||
0
cmd/worker/.gitkeep
Normal file
0
cmd/worker/.gitkeep
Normal file
0
config/.gitkeep
Normal file
0
config/.gitkeep
Normal file
@ -1,49 +0,0 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
Server Server `mapstructure:"server"`
|
||||
JWT JWT `mapstructure:"jwt"`
|
||||
DB DB `mapstructure:"db"`
|
||||
Redis Redis `mapstructure:"redis"`
|
||||
KYCProvider KYC `mapstructure:"kyc"`
|
||||
OTP OTP `mapstructure:"otp"`
|
||||
Kavenegar SMSProvider `mapstructure:"kavenegar"`
|
||||
}
|
||||
type Server struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
User string `mapstructure:"user"`
|
||||
Pass string `mapstructure:"pass"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
DBName string `mapstructure:"db_name"`
|
||||
}
|
||||
|
||||
type Redis struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Pass string `mapstructure:"pass"`
|
||||
}
|
||||
|
||||
type JWT struct {
|
||||
TokenExpMinutes uint `mapstructure:"token_exp_minutes"`
|
||||
RefreshTokenExpMinutes uint `mapstructure:"refresh_token_exp_minutes"`
|
||||
TokenSecret string `mapstructure:"token_secret"`
|
||||
}
|
||||
|
||||
type KYC struct {
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
URL string `mapstructure:"url"`
|
||||
}
|
||||
|
||||
type OTP struct {
|
||||
CodeExpMinutes uint `mapstructure:"code_exp_minutes"`
|
||||
}
|
||||
|
||||
type SMSProvider struct {
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
Template string `mapstructure:"template"`
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func ReadGeneric[T any](cfgPath string) (T, error) {
|
||||
var cfg T
|
||||
fullAbsPath, err := absPath(cfgPath)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
// configPath := filepath.Dir(fullAbsPath)
|
||||
// viper.AddConfigPath(configPath)
|
||||
// configType := strings.TrimPrefix(filepath.Ext(fullAbsPath), ".")
|
||||
// viper.SetConfigType(configType)
|
||||
// configFile := strings.TrimSuffix(filepath.Base(fullAbsPath), filepath.Ext(fullAbsPath))
|
||||
// viper.SetConfigName(configFile)
|
||||
|
||||
viper.SetConfigFile(fullAbsPath)
|
||||
|
||||
viper.AutomaticEnv()
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
return cfg, viper.Unmarshal(&cfg)
|
||||
}
|
||||
|
||||
func ReadStandard(cfgPath string) (Config, error) {
|
||||
return ReadGeneric[Config](cfgPath)
|
||||
}
|
||||
|
||||
func absPath(cfgPath string) (string, error) {
|
||||
if !filepath.IsAbs(cfgPath) {
|
||||
return filepath.Abs(cfgPath)
|
||||
}
|
||||
return cfgPath, nil
|
||||
}
|
||||
|
||||
func MustReadStandard(configPath string) Config {
|
||||
cfg, err := ReadStandard(configPath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
367
docs/docs.go
367
docs/docs.go
@ -1,367 +0,0 @@
|
||||
// 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}}",
|
||||
"contact": {},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/kyc": {
|
||||
"post": {
|
||||
"description": "Verify user KYC with national ID and birth date",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify user KYC",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "KYC Verify Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.KYCVerifyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/otp": {
|
||||
"post": {
|
||||
"description": "Verify the provided OTP code for the phone number",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify OTP code",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OTP Verify Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OTPVerifyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OTPVerifyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"dto.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.KYCVerifyRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"birthDate",
|
||||
"nationalId"
|
||||
],
|
||||
"properties": {
|
||||
"birthDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"nationalId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPProviderReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"receptor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPProviderResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPVerifyRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"code",
|
||||
"phone"
|
||||
],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPVerifyResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
@ -1,338 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/kyc": {
|
||||
"post": {
|
||||
"description": "Verify user KYC with national ID and birth date",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify user KYC",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "KYC Verify Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.KYCVerifyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/otp": {
|
||||
"post": {
|
||||
"description": "Verify the provided OTP code for the phone number",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Verify OTP code",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "OTP Verify Request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OTPVerifyRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/dto.OTPVerifyResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"dto.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.KYCVerifyRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"birthDate",
|
||||
"nationalId"
|
||||
],
|
||||
"properties": {
|
||||
"birthDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"nationalId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPProviderReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"receptor": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPProviderResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPVerifyRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"code",
|
||||
"phone"
|
||||
],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.OTPVerifyResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,219 +0,0 @@
|
||||
definitions:
|
||||
dto.APIResponse:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
type: object
|
||||
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
|
||||
dto.KYCVerifyRequest:
|
||||
properties:
|
||||
birthDate:
|
||||
type: string
|
||||
nationalId:
|
||||
type: string
|
||||
required:
|
||||
- birthDate
|
||||
- nationalId
|
||||
type: object
|
||||
dto.OTPProviderReq:
|
||||
properties:
|
||||
receptor:
|
||||
type: string
|
||||
type: object
|
||||
dto.OTPProviderResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
dto.OTPVerifyRequest:
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
required:
|
||||
- code
|
||||
- phone
|
||||
type: object
|
||||
dto.OTPVerifyResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
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
|
||||
/auth/kyc:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Verify user KYC with national ID and birth date
|
||||
parameters:
|
||||
- description: KYC Verify Request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.KYCVerifyRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Verify user KYC
|
||||
tags:
|
||||
- auth
|
||||
/auth/otp:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Verify the provided OTP code for the phone number
|
||||
parameters:
|
||||
- description: OTP Verify Request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OTPVerifyRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/dto.OTPVerifyResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Verify OTP code
|
||||
tags:
|
||||
- auth
|
||||
swagger: "2.0"
|
||||
73
go.mod
73
go.mod
@ -1,81 +1,20 @@
|
||||
module backend
|
||||
module boiler-plate
|
||||
|
||||
go 1.25.1
|
||||
go 1.25.0
|
||||
|
||||
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/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d
|
||||
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.5
|
||||
gorm.io/gorm v1.30.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/StackExchange/wmi 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/deckarep/golang-set/v2 v2.6.0 // 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.9.0 // indirect
|
||||
github.com/go-ole/go-ole v1.3.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/gorilla/websocket v1.4.2 // 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
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
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.4 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // 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/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // 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.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
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
)
|
||||
|
||||
310
go.sum
310
go.sum
@ -1,138 +1,6 @@
|
||||
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
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=
|
||||
github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
|
||||
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
|
||||
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/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
|
||||
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
|
||||
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
|
||||
github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
|
||||
github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
|
||||
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
|
||||
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
|
||||
github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0=
|
||||
github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI=
|
||||
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=
|
||||
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||
github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
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/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0=
|
||||
github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
|
||||
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=
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E=
|
||||
github.com/ethereum/go-ethereum v1.16.3 h1:nDoBSrmsrPbrDIVLTkDQCy1U9KdHN+F2PzvMbDoS42Q=
|
||||
github.com/ethereum/go-ethereum v1.16.3/go.mod h1:Lrsc6bt9Gm9RyvhfFK53vboCia8kpF9nv+2Ukntnl+8=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY=
|
||||
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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
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-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=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk=
|
||||
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0=
|
||||
github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc=
|
||||
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
|
||||
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
|
||||
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4=
|
||||
github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
|
||||
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
|
||||
github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA=
|
||||
github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k=
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
@ -141,186 +9,28 @@ github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
|
||||
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
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/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d h1:5yPyBSS28Nojbr7pAkiXADGj6VpTXx73o6SsprKbSoo=
|
||||
github.com/kavenegar/kavenegar-go v0.0.0-20240205151018-77039f51467d/go.mod h1:CRhvvr4KNAyrg+ewrutOf+/QoHs7lztSoLjp+GqhYlA=
|
||||
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=
|
||||
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
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/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
|
||||
github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
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/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
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/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
|
||||
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
|
||||
github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8=
|
||||
github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
|
||||
github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
|
||||
github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms=
|
||||
github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0=
|
||||
github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ=
|
||||
github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c=
|
||||
github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g=
|
||||
github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM=
|
||||
github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
|
||||
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
|
||||
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
|
||||
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
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/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
|
||||
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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.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.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/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
|
||||
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=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
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=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
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/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
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=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
|
||||
gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gorm.io/gorm v1.30.2 h1:f7bevlVoVe4Byu3pmbWPVHnPsLoWaMjEb7/clyr9Ivs=
|
||||
gorm.io/gorm v1.30.2/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
|
||||
0
internal/api/.gitkeep
Normal file
0
internal/api/.gitkeep
Normal file
0
internal/api/dto/.gitkeep
Normal file
0
internal/api/dto/.gitkeep
Normal file
@ -1,58 +0,0 @@
|
||||
package dto
|
||||
|
||||
type ChallengeRequest struct {
|
||||
PubKey string `json:"pubKey" validate:"required,eth_pubkey"`
|
||||
}
|
||||
|
||||
type ChallengeResponse struct {
|
||||
Message string `json:"message"`
|
||||
TimeStamp string `json:"timeStamp"`
|
||||
ExpiresAt string `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type AuthenticateRequest struct {
|
||||
PubKey string `json:"pubKey" validate:"required,eth_pubkey"`
|
||||
Signature string `json:"signature" validate:"required,eth_signature"`
|
||||
}
|
||||
|
||||
type AuthenticateResponse struct {
|
||||
AuthorizationToken string `json:"authorizationToken"`
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
type RefreshTokenRequest struct {
|
||||
RefreshToken string `json:"refreshToken" validate:"required,jwt"`
|
||||
}
|
||||
|
||||
type OTPProviderReq struct {
|
||||
Receptor string `json:"receptor"`
|
||||
}
|
||||
|
||||
type OTPProviderResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type OTPVerifyRequest struct {
|
||||
Phone string `json:"phone" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
}
|
||||
|
||||
type OTPVerifyResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type KYCVerifyRequest struct {
|
||||
NationalID string `json:"nationalId" validate:"required"`
|
||||
BirthDate string `json:"birthDate" validate:"required"`
|
||||
}
|
||||
|
||||
type KYCVerifyResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data,omitempty"`
|
||||
}
|
||||
0
internal/api/handlers/.gitkeep
Normal file
0
internal/api/handlers/.gitkeep
Normal file
@ -1,193 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"backend/internal/api/dto"
|
||||
"backend/internal/api/http/middlewares"
|
||||
"backend/internal/usecase"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService usecase.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler(authService *usecase.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: *authService,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
challenge, err := h.authService.GenerateChallenge(c.Context(), req.PubKey)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(
|
||||
dto.ChallengeResponse{
|
||||
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 {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
userToken, err := h.authService.Authenticate(
|
||||
c.Context(),
|
||||
req.PubKey,
|
||||
req.Signature,
|
||||
//TODO: add chainID to cfg
|
||||
1,
|
||||
c.IP(),
|
||||
c.Get("User-Agent"),
|
||||
)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(dto.AuthenticateResponse{
|
||||
AuthorizationToken: userToken.AuthorizationToken,
|
||||
RefreshToken: userToken.RefreshToken,
|
||||
ExpiresAt: userToken.ExpiresAt,
|
||||
})
|
||||
}
|
||||
|
||||
// SendOTP sends OTP code to the provided phone number
|
||||
// @Summary Send OTP code
|
||||
// @Description Send OTP code to the provided phone number
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.OTPProviderReq true "OTP Request"
|
||||
// @Success 200 {object} dto.OTPProviderResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/otp [post]
|
||||
func (h *AuthHandler) SendOTP(c *fiber.Ctx) error {
|
||||
var req dto.OTPProviderReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
_, err := h.authService.SendOTPCode(c.Context(), req.Receptor)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(dto.OTPProviderResponse{
|
||||
Message: "OTP code sent successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyOTP verifies the OTP code
|
||||
// @Summary Verify OTP code
|
||||
// @Description Verify the provided OTP code for the phone number
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.OTPVerifyRequest true "OTP Verify Request"
|
||||
// @Success 200 {object} dto.OTPVerifyResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 401 {object} map[string]string
|
||||
// @Router /auth/otp [post]
|
||||
func (h *AuthHandler) VerifyOTP(c *fiber.Ctx) error {
|
||||
var req dto.OTPVerifyRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
|
||||
err := h.authService.VerifyOTP(c.Context(), req.Phone, req.Code)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(dto.OTPVerifyResponse{
|
||||
Message: "OTP verified successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// VerifyKYC verifies user KYC information
|
||||
// @Summary Verify user KYC
|
||||
// @Description Verify user KYC with national ID and birth date
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.KYCVerifyRequest true "KYC Verify Request"
|
||||
// @Success 200 {object} dto.APIResponse
|
||||
// @Failure 400 {object} map[string]string
|
||||
// @Failure 500 {object} map[string]string
|
||||
// @Router /auth/kyc [post]
|
||||
func (h *AuthHandler) VerifyKYC(c *fiber.Ctx) error {
|
||||
var req dto.KYCVerifyRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request body",
|
||||
})
|
||||
}
|
||||
claims := middlewares.GetUserClaims(c)
|
||||
err := h.authService.VerifyKYC(c.Context(), claims.UserID, req.NationalID, req.BirthDate)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(dto.APIResponse{
|
||||
Success: true,
|
||||
Message: "KYC verified successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) HelloWorld(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
||||
"message": "Hello, World!",
|
||||
})
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"backend/pkg/jwt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
const userClaimsKey = "User-Claims"
|
||||
|
||||
func JWTAuthMiddleware(secret []byte) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "missing or invalid Authorization header",
|
||||
})
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
claims, err := jwt.ParseToken(tokenString, secret)
|
||||
if err != nil || claims == nil {
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||
"error": "invalid or expired token",
|
||||
})
|
||||
}
|
||||
|
||||
c.Locals(userClaimsKey, claims)
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserClaims(c *fiber.Ctx) *jwt.UserClaims {
|
||||
claims, _ := c.Locals(userClaimsKey).(*jwt.UserClaims)
|
||||
return claims
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/docs"
|
||||
httpHandlers "backend/internal/api/http/handlers"
|
||||
"backend/internal/api/http/middlewares"
|
||||
"backend/internal/app"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/gofiber/adaptor/v2"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
)
|
||||
|
||||
func Run(cfg config.Server, app *app.AppContainer) {
|
||||
fiberApp := fiber.New()
|
||||
|
||||
fiberApp.Use(cors.New())
|
||||
|
||||
// Serve static files (HTML, CSS, JS)
|
||||
fiberApp.Static("/", "./static")
|
||||
|
||||
api := fiberApp.Group("/api")
|
||||
// register routes here
|
||||
registerPublicRoutes(api, app)
|
||||
|
||||
docs.SwaggerInfo.Host = fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
|
||||
docs.SwaggerInfo.BasePath = "/api"
|
||||
docs.SwaggerInfo.Schemes = []string{"http", "https"}
|
||||
|
||||
api.Get("/swagger/*", adaptor.HTTPHandler(httpSwagger.Handler()))
|
||||
|
||||
api.Get("/hello", middlewares.JWTAuthMiddleware([]byte("Secret")), func(c *fiber.Ctx) error {
|
||||
return c.SendString("Hello, World!")
|
||||
})
|
||||
|
||||
log.Printf("Server starting on %s:%d", cfg.Host, cfg.Port)
|
||||
log.Fatal(fiberApp.Listen(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)))
|
||||
}
|
||||
|
||||
func registerPublicRoutes(router fiber.Router, app *app.AppContainer) {
|
||||
authgroup := router.Group("/auth")
|
||||
|
||||
authService := app.AuthService()
|
||||
authHandler := httpHandlers.NewAuthHandler(&authService)
|
||||
|
||||
// Register auth routes
|
||||
authgroup.Post("/challenge", authHandler.GenerateChallenge)
|
||||
authgroup.Post("/authenticate", authHandler.Authenticate)
|
||||
authgroup.Post("/otp", authHandler.SendOTP)
|
||||
authgroup.Post("/verify", authHandler.VerifyOTP)
|
||||
// add JWT middleware for KYC
|
||||
authgroup.Post("/kyc", middlewares.JWTAuthMiddleware([]byte("Secret")), authHandler.VerifyKYC)
|
||||
}
|
||||
0
internal/api/middlewares/.gitkeep
Normal file
0
internal/api/middlewares/.gitkeep
Normal file
@ -1,89 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/internal/repository/cache"
|
||||
"backend/internal/repository/storage"
|
||||
"backend/internal/repository/storage/types"
|
||||
"backend/internal/usecase"
|
||||
cachePackage "backend/pkg/cache"
|
||||
"backend/pkg/postgres"
|
||||
"backend/pkg/sms"
|
||||
"backend/pkg/zohal"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
dbConn.AutoMigrate(&types.User{})
|
||||
|
||||
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)
|
||||
zohal := zohal.NewZohal(cfg.KYCProvider)
|
||||
otpRepo := cache.NewOTPRepository(redis)
|
||||
smsProvider := sms.NewKavenegar(cfg.Kavenegar)
|
||||
|
||||
authService := usecase.NewAuthService(
|
||||
userRepo,
|
||||
sessionRepo,
|
||||
challengeRepo,
|
||||
30,
|
||||
otpRepo,
|
||||
zohal,
|
||||
smsProvider,
|
||||
cfg,
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
0
internal/domain/.gitkeep
Normal file
0
internal/domain/.gitkeep
Normal file
0
internal/domain/entity/.gitkeep
Normal file
0
internal/domain/entity/.gitkeep
Normal file
0
internal/domain/event/.gitkeep
Normal file
0
internal/domain/event/.gitkeep
Normal file
@ -1,34 +0,0 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"backend/pkg/util"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type OTPRepo interface {
|
||||
Create(ctx context.Context, phone string, otp *OTP) error
|
||||
Get(ctx context.Context, phone string) (*OTP, error)
|
||||
Delete(ctx context.Context, phone string) error
|
||||
}
|
||||
|
||||
type OTP struct {
|
||||
ID uuid.UUID
|
||||
Code string
|
||||
Phone string
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func NewOTP(phone string) *OTP {
|
||||
return &OTP{
|
||||
ID: uuid.New(),
|
||||
Code: util.GenOTPCode(),
|
||||
Phone: phone,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *OTP) IsExpired() bool {
|
||||
return time.Now().After(o.ExpiresAt)
|
||||
}
|
||||
0
internal/domain/service/.gitkeep
Normal file
0
internal/domain/service/.gitkeep
Normal file
@ -1,38 +0,0 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type SessionRepo interface {
|
||||
Create(ctx context.Context, session *UserSession) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*UserSession, error)
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
GetUserSessions(ctx context.Context, userID uuid.UUID) ([]UserSession, error)
|
||||
}
|
||||
|
||||
// TODO: add IP and UserAgent
|
||||
type UserSession struct {
|
||||
ID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
User User
|
||||
WalletID string
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
func NewSession(userID uuid.UUID, walletID string, expiresAt time.Time) *UserSession {
|
||||
return &UserSession{
|
||||
ID: uuid.New(),
|
||||
UserID: userID,
|
||||
WalletID: walletID,
|
||||
ExpiresAt: expiresAt,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}
|
||||
}
|
||||
@ -1,162 +0,0 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"net/mail"
|
||||
|
||||
validate "backend/pkg/validate/common"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrEmailInvalid = errors.New("email is invalid")
|
||||
)
|
||||
|
||||
type UserRepo interface {
|
||||
Create(ctx context.Context, user *User) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*User, error)
|
||||
GetByPhone(ctx context.Context, phone string) (*User, error)
|
||||
GetByPubKey(ctx context.Context, pubKey string) (*User, error)
|
||||
GetOrCreateUserByPubKey(ctx context.Context, pubKey string) (*User, error)
|
||||
Update(ctx context.Context, user *User) error
|
||||
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 KYCLevel int8
|
||||
|
||||
const (
|
||||
KYCLevel0 KYCLevel = iota
|
||||
KYCLevel1
|
||||
KYCLevel2
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
PubKey string
|
||||
Name string
|
||||
KYCLevel KYCLevel
|
||||
LastName string
|
||||
PhoneNumber string
|
||||
Email *string
|
||||
NationalID string
|
||||
BirthDate *time.Time
|
||||
LastLogin *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
func NewUser(pubKey, name, lastName, phoneNumber, email, nationalID string) (*User, error) {
|
||||
|
||||
_, err := validate.IsValidPhone(phoneNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = validate.IsValidNationalID(nationalID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = validate.IsValidPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &User{
|
||||
ID: uuid.New(),
|
||||
PubKey: pubKey,
|
||||
Name: name,
|
||||
LastName: lastName,
|
||||
PhoneNumber: phoneNumber,
|
||||
NationalID: nationalID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
UpdatedAt: time.Now().UTC(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *User) UpdateLastLogin() {
|
||||
now := time.Now().UTC()
|
||||
u.LastLogin = &now
|
||||
u.UpdatedAt = now
|
||||
}
|
||||
|
||||
func (u *User) UpdateInfo(email string) {
|
||||
addr, err := mail.ParseAddress(email)
|
||||
if err == nil {
|
||||
u.Email = &addr.Address
|
||||
}
|
||||
u.UpdatedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
func (u *User) UpdateKYCLevel(level KYCLevel) {
|
||||
u.KYCLevel = level
|
||||
u.UpdatedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
type Challenge struct {
|
||||
Message string
|
||||
TimeStamp time.Time
|
||||
ExpiresAt time.Time
|
||||
}
|
||||
|
||||
func (c *Challenge) IsExpired() bool {
|
||||
return time.Now().After(c.ExpiresAt)
|
||||
}
|
||||
|
||||
func (c *Challenge) Verify(address string, signedMsg string) (bool, error) {
|
||||
if c.IsExpired() {
|
||||
return false, errors.New("challenge has expired")
|
||||
}
|
||||
|
||||
signature, err := hexutil.Decode(signedMsg)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("decode signature: %w", err)
|
||||
}
|
||||
|
||||
challengeBytes := []byte(c.Message)
|
||||
|
||||
return c.VerifySignedBytes(address, challengeBytes, signature)
|
||||
}
|
||||
|
||||
func (c *Challenge) VerifySignedBytes(expectedAddress string, msg []byte, sig []byte) (bool, error) {
|
||||
msgHash := accounts.TextHash(msg)
|
||||
|
||||
if len(sig) != 65 {
|
||||
return false, fmt.Errorf("invalid signature length: expected 65 bytes, got %d", len(sig))
|
||||
}
|
||||
|
||||
if sig[crypto.RecoveryIDOffset] == 27 || sig[crypto.RecoveryIDOffset] == 28 {
|
||||
sig[crypto.RecoveryIDOffset] -= 27
|
||||
}
|
||||
|
||||
recovered, err := crypto.SigToPub(msgHash, sig)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to recover public key: %w", err)
|
||||
}
|
||||
recoveredAddr := crypto.PubkeyToAddress(*recovered)
|
||||
|
||||
if !common.IsHexAddress(expectedAddress) {
|
||||
return false, fmt.Errorf("invalid Ethereum address format: %s", expectedAddress)
|
||||
}
|
||||
expectedAddr := common.HexToAddress(expectedAddress)
|
||||
|
||||
return strings.EqualFold(expectedAddr.Hex(), recoveredAddr.Hex()), nil
|
||||
}
|
||||
0
internal/repository/.gitkeep
Normal file
0
internal/repository/.gitkeep
Normal file
0
internal/repository/cache/.gitkeep
vendored
Normal file
0
internal/repository/cache/.gitkeep
vendored
Normal file
80
internal/repository/cache/challenge_repo.go
vendored
80
internal/repository/cache/challenge_repo.go
vendored
@ -1,80 +0,0 @@
|
||||
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)
|
||||
}
|
||||
79
internal/repository/cache/otp_repo.go
vendored
79
internal/repository/cache/otp_repo.go
vendored
@ -1,79 +0,0 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"backend/internal/domain"
|
||||
"backend/pkg/cache"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
type OTPRepository struct {
|
||||
redis *cache.Redis
|
||||
}
|
||||
|
||||
func NewOTPRepository(redis *cache.Redis) domain.OTPRepo {
|
||||
return &OTPRepository{
|
||||
redis: redis,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *OTPRepository) Create(ctx context.Context, phone string, otp *domain.OTP) error {
|
||||
key := r.getOTPKey(phone)
|
||||
|
||||
otpData, err := json.Marshal(otp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal otp: %w", err)
|
||||
}
|
||||
|
||||
ttl := time.Until(otp.ExpiresAt)
|
||||
if ttl <= 0 {
|
||||
return fmt.Errorf("OTP expired already")
|
||||
}
|
||||
|
||||
if err := r.redis.Client().Set(ctx, key, otpData, ttl).Err(); err != nil {
|
||||
return fmt.Errorf("failed to store otp in redis: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *OTPRepository) Delete(ctx context.Context, phone string) error {
|
||||
key := r.getOTPKey(phone)
|
||||
|
||||
if err := r.redis.Client().Del(ctx, key).Err(); err != nil {
|
||||
return fmt.Errorf("failed to del otp from redis")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *OTPRepository) Get(ctx context.Context, phone string) (*domain.OTP, error) {
|
||||
key := r.getOTPKey(phone)
|
||||
|
||||
result, err := r.redis.Client().Get(ctx, key).Result()
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
return nil, fmt.Errorf("otp code not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var otp domain.OTP
|
||||
if err := json.Unmarshal([]byte(result), &otp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if otp.IsExpired() {
|
||||
r.Delete(ctx, phone)
|
||||
return nil, fmt.Errorf("otp is expired")
|
||||
}
|
||||
return &otp, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *OTPRepository) getOTPKey(phone string) string {
|
||||
return fmt.Sprintf("challenge:%s", phone)
|
||||
}
|
||||
135
internal/repository/cache/session_repo.go
vendored
135
internal/repository/cache/session_repo.go
vendored
@ -1,135 +0,0 @@
|
||||
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())
|
||||
}
|
||||
0
internal/repository/external/.gitkeep
vendored
Normal file
0
internal/repository/external/.gitkeep
vendored
Normal file
0
internal/repository/storage/.gitkeep
Normal file
0
internal/repository/storage/.gitkeep
Normal file
0
internal/repository/storage/mapper/.gitkeep
Normal file
0
internal/repository/storage/mapper/.gitkeep
Normal file
0
internal/repository/storage/types/.gitkeep
Normal file
0
internal/repository/storage/types/.gitkeep
Normal file
@ -1,22 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeletedAt *time.Time
|
||||
}
|
||||
|
||||
func (b *Base) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
if b.ID == uuid.Nil {
|
||||
b.ID = uuid.New()
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"backend/internal/domain"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Base
|
||||
PubKey string
|
||||
Name string
|
||||
LastName string
|
||||
KYCLevel int
|
||||
//TODO: add NFT Embedded struct
|
||||
Phone string
|
||||
NationalID string
|
||||
LastLogin *time.Time
|
||||
Email *string
|
||||
BirthDate *time.Time
|
||||
}
|
||||
|
||||
func CastUserToStorage(u domain.User) *User {
|
||||
return &User{
|
||||
Base: Base{
|
||||
ID: u.ID,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
DeletedAt: u.DeletedAt,
|
||||
},
|
||||
PubKey: u.PubKey,
|
||||
Name: u.Name,
|
||||
LastName: u.LastName,
|
||||
KYCLevel: int(u.KYCLevel),
|
||||
Phone: u.PhoneNumber,
|
||||
NationalID: u.NationalID,
|
||||
Email: u.Email,
|
||||
BirthDate: u.BirthDate,
|
||||
LastLogin: u.LastLogin,
|
||||
}
|
||||
}
|
||||
|
||||
func CastUserToDomain(u User) *domain.User {
|
||||
return &domain.User{
|
||||
ID: u.ID,
|
||||
PubKey: u.PubKey,
|
||||
Name: u.Name,
|
||||
LastName: u.LastName,
|
||||
KYCLevel: domain.KYCLevel(u.KYCLevel),
|
||||
PhoneNumber: u.Phone,
|
||||
NationalID: u.NationalID,
|
||||
Email: u.Email,
|
||||
BirthDate: u.BirthDate,
|
||||
LastLogin: u.LastLogin,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
DeletedAt: u.DeletedAt,
|
||||
}
|
||||
}
|
||||
@ -1,81 +0,0 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"backend/internal/domain"
|
||||
"backend/internal/repository/storage/types"
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) domain.UserRepo {
|
||||
return &UserRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *UserRepository) Create(ctx context.Context, user *domain.User) error {
|
||||
model := types.CastUserToStorage(*user)
|
||||
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
*user = *types.CastUserToDomain(*model)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.User, error) {
|
||||
var user types.User
|
||||
if err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.CastUserToDomain(user), nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByPhone(ctx context.Context, phone string) (*domain.User, error) {
|
||||
var user types.User
|
||||
if err := r.db.WithContext(ctx).First(&user, "phone = ?", phone).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.CastUserToDomain(user), nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) Update(ctx context.Context, user *domain.User) error {
|
||||
userModel := types.CastUserToStorage(*user)
|
||||
return r.db.WithContext(ctx).Save(&userModel).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&types.User{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetByPubKey(ctx context.Context, pubKey string) (*domain.User, error) {
|
||||
var user types.User
|
||||
if err := r.db.WithContext(ctx).First(&user, "pub_key = ?", pubKey).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.CastUserToDomain(user), nil
|
||||
}
|
||||
|
||||
func (r *UserRepository) GetOrCreateUserByPubKey(ctx context.Context, pubKey string) (*domain.User, error) {
|
||||
var user types.User
|
||||
err := r.db.WithContext(ctx).First(&user, "pub_key = ?", pubKey).Error
|
||||
if err == nil {
|
||||
return types.CastUserToDomain(user), nil
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user = types.User{
|
||||
PubKey: pubKey,
|
||||
}
|
||||
if err := r.db.WithContext(ctx).Create(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return types.CastUserToDomain(user), nil
|
||||
}
|
||||
0
internal/usecase/.gitkeep
Normal file
0
internal/usecase/.gitkeep
Normal file
@ -1,237 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/internal/domain"
|
||||
"backend/pkg/jwt"
|
||||
"backend/pkg/sms"
|
||||
"backend/pkg/validate/common"
|
||||
"backend/pkg/zohal"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
jwt2 "github.com/golang-jwt/jwt/v5"
|
||||
"github.com/kavenegar/kavenegar-go"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type AuthService interface {
|
||||
GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error)
|
||||
Authenticate(ctx context.Context, pubKey string, signature string, chainID uint, ipAddress, userAgent string) (*UserToken, error)
|
||||
SendOTPCode(ctx context.Context, phone string) (string, error)
|
||||
VerifyOTP(ctx context.Context, phone, code string) error
|
||||
VerifyKYC(ctx context.Context, userID, nationalID, birthDate string) error
|
||||
}
|
||||
|
||||
type authService struct {
|
||||
userRepo domain.UserRepo
|
||||
sessionRepo domain.SessionRepo
|
||||
challengeRepo domain.ChallengeRepo
|
||||
otpRepo domain.OTPRepo
|
||||
zohal *zohal.Zohal
|
||||
smsProvider *sms.Kavenegar
|
||||
challengeExp uint
|
||||
cfg config.Config
|
||||
}
|
||||
|
||||
type UserToken struct {
|
||||
AuthorizationToken string
|
||||
RefreshToken string
|
||||
ExpiresAt int64
|
||||
}
|
||||
|
||||
func NewAuthService(
|
||||
userRepo domain.UserRepo,
|
||||
sessionRepo domain.SessionRepo,
|
||||
challengeRepo domain.ChallengeRepo,
|
||||
challengeExp uint,
|
||||
otpRepo domain.OTPRepo,
|
||||
zohal *zohal.Zohal,
|
||||
smsProvider *sms.Kavenegar,
|
||||
cfg config.Config,
|
||||
) AuthService {
|
||||
return &authService{
|
||||
userRepo: userRepo,
|
||||
sessionRepo: sessionRepo,
|
||||
challengeRepo: challengeRepo,
|
||||
otpRepo: otpRepo,
|
||||
challengeExp: challengeExp,
|
||||
zohal: zohal,
|
||||
smsProvider: smsProvider,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *authService) GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) {
|
||||
_, err := common.IsValidPublicKey(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challenge := &domain.Challenge{
|
||||
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, 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)
|
||||
}
|
||||
|
||||
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.GetOrCreateUserByPubKey(ctx, pubKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get or create user: %w", err)
|
||||
}
|
||||
|
||||
user.UpdateLastLogin()
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("failed to update user: %w", err)
|
||||
}
|
||||
|
||||
expiresAt := time.Now().Add(time.Duration(s.cfg.JWT.TokenExpMinutes) * time.Minute)
|
||||
session := domain.NewSession(user.ID, user.PubKey, expiresAt)
|
||||
|
||||
err = s.sessionRepo.Create(ctx, session)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
|
||||
claims := &jwt.UserClaims{
|
||||
UserID: user.ID.String(),
|
||||
KYCLevel: int(user.KYCLevel),
|
||||
Sections: []string{},
|
||||
}
|
||||
claims.ExpiresAt = jwt2.NewNumericDate(expiresAt)
|
||||
claims.IssuedAt = jwt2.NewNumericDate(time.Now())
|
||||
|
||||
authToken, err := jwt.CreateToken([]byte(s.cfg.JWT.TokenSecret), claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refreshExpiresAt := time.Now().Add(time.Duration(s.cfg.JWT.RefreshTokenExpMinutes) * time.Minute)
|
||||
refreshClaims := &jwt.UserClaims{
|
||||
UserID: user.ID.String(),
|
||||
KYCLevel: int(user.KYCLevel),
|
||||
Sections: []string{"refresh"},
|
||||
}
|
||||
|
||||
refreshClaims.ExpiresAt = jwt2.NewNumericDate(refreshExpiresAt)
|
||||
refreshClaims.IssuedAt = jwt2.NewNumericDate(time.Now())
|
||||
|
||||
refreshToken, err := jwt.CreateToken([]byte(s.cfg.JWT.TokenSecret), refreshClaims)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create refresh token: %w", err)
|
||||
}
|
||||
|
||||
return &UserToken{
|
||||
AuthorizationToken: authToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt.Unix(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *authService) VerifyKYC(ctx context.Context, userID, nationalID, birthDate string) error {
|
||||
uID, err := uuid.Parse(userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid user ID format: %w", err)
|
||||
}
|
||||
|
||||
user, err := s.userRepo.GetByID(ctx, uID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user: %w", err)
|
||||
}
|
||||
|
||||
shahkarResp, err := s.zohal.Shahkar(ctx, user.PhoneNumber, user.NationalID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("shahkar verification failed: %w", err)
|
||||
}
|
||||
|
||||
identityResp, err := s.zohal.GetPerson(ctx, nationalID, birthDate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("identity verification failed: %w", err)
|
||||
}
|
||||
|
||||
if shahkarResp.StatusCode == 200 && identityResp.StatusCode == 200 {
|
||||
user.UpdateKYCLevel(domain.KYCLevel1)
|
||||
err = s.userRepo.Update(ctx, user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update user KYC level: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("KYC verification failed - shahkar status: %d, identity status: %d",
|
||||
shahkarResp.StatusCode, identityResp.StatusCode)
|
||||
}
|
||||
|
||||
func (s *authService) SendOTPCode(ctx context.Context, phone string) (string, error) {
|
||||
otp := domain.NewOTP(phone)
|
||||
if err := s.otpRepo.Create(ctx, phone, otp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
otpMsg := &sms.OTPMsg{
|
||||
Receptor: otp.Phone,
|
||||
Token: otp.Code,
|
||||
Template: s.cfg.Kavenegar.Template,
|
||||
// TODO: make sure when use VerfiyLookup Params
|
||||
Params: kavenegar.VerifyLookupParam{},
|
||||
}
|
||||
err := s.smsProvider.OTP(otpMsg)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return otp.Code, err
|
||||
}
|
||||
|
||||
func (s *authService) VerifyOTP(ctx context.Context, phone, code string) error {
|
||||
otp, err := s.otpRepo.Get(ctx, phone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if otp == nil {
|
||||
return errors.New("otp code not found or expired")
|
||||
}
|
||||
|
||||
if code != otp.Code {
|
||||
return errors.New("otp code is not valid")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
pkg/cache/redis.go
vendored
36
pkg/cache/redis.go
vendored
@ -1,36 +0,0 @@
|
||||
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()
|
||||
}
|
||||
0
pkg/errors/.gitkeep
Normal file
0
pkg/errors/.gitkeep
Normal file
@ -1,7 +0,0 @@
|
||||
package errors
|
||||
|
||||
var (
|
||||
ErrPhoneInvalid = NewAppError(400, "Invalid phone number", ErrorTypeValidation, nil)
|
||||
ErrNationalIDInvalid = NewAppError(400, "Invalid national ID", ErrorTypeValidation, nil)
|
||||
ErrWalletInvalid = NewAppError(400, "Invalid wallet address", ErrorTypeValidation, nil)
|
||||
)
|
||||
@ -1,51 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ErrorType string
|
||||
|
||||
const (
|
||||
ErrorTypeValidation ErrorType = "VALIDATION_ERROR"
|
||||
|
||||
ErrorTypeAuth ErrorType = "AUTH_ERROR"
|
||||
)
|
||||
|
||||
type AppError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Type ErrorType `json:"type"`
|
||||
Cause error `json:"_"`
|
||||
}
|
||||
|
||||
func NewAppError(code int, msg string, typ ErrorType, cause error) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
Type: typ,
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *AppError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
func (e *AppError) Is(target error) bool {
|
||||
if t, ok := target.(*AppError); ok {
|
||||
return e.Type == t.Type
|
||||
}
|
||||
return errors.Is(e.Cause, target)
|
||||
}
|
||||
|
||||
func (e *AppError) As(target interface{}) bool {
|
||||
if t, ok := target.(**AppError); ok {
|
||||
*t = e
|
||||
return true
|
||||
}
|
||||
return errors.As(e.Cause, target)
|
||||
}
|
||||
@ -1,253 +0,0 @@
|
||||
package ethereum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type EthereumClient interface {
|
||||
GetEthClient() *ethclient.Client
|
||||
GetRPCClient() *rpc.Client
|
||||
Close()
|
||||
IsConnected(ctx context.Context) error
|
||||
|
||||
NewBoundContract(address common.Address, abi string) (*bind.BoundContract, error)
|
||||
// Creates options for sending transactions to the blockchain.
|
||||
CreateTransactOpts(ctx context.Context, privateKey string, value *big.Int) (*bind.TransactOpts, error)
|
||||
// Creates options for reading from the blockchain.
|
||||
CreateCallOpts(ctx context.Context, blockNumber *big.Int) *bind.CallOpts
|
||||
// Waits for a transaction to be mined and returns the receipt.
|
||||
WaitForTransaction(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||
}
|
||||
|
||||
type Geth struct {
|
||||
ethClient *ethclient.Client
|
||||
rpcClient *rpc.Client
|
||||
url string
|
||||
}
|
||||
|
||||
type GethConfig struct {
|
||||
URL string
|
||||
RequestTimeout time.Duration
|
||||
}
|
||||
|
||||
func DefaultGethConfig() *GethConfig {
|
||||
return &GethConfig{
|
||||
RequestTimeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGeth(clientURL string) (*Geth, error) {
|
||||
if clientURL == "" {
|
||||
return nil, fmt.Errorf("client URL cannot be empty")
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.Dial(clientURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to RPC client at %s: %w", clientURL, err)
|
||||
}
|
||||
|
||||
ethClient := ethclient.NewClient(rpcClient)
|
||||
|
||||
geth := &Geth{
|
||||
ethClient: ethClient,
|
||||
rpcClient: rpcClient,
|
||||
url: clientURL,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := geth.IsConnected(ctx); err != nil {
|
||||
geth.Close()
|
||||
return nil, fmt.Errorf("connection test failed: %w", err)
|
||||
}
|
||||
|
||||
return geth, nil
|
||||
}
|
||||
|
||||
func NewGethWithConfig(config *GethConfig) (*Geth, error) {
|
||||
if config == nil {
|
||||
return nil, fmt.Errorf("config cannot be nil")
|
||||
}
|
||||
|
||||
if config.URL == "" {
|
||||
return nil, fmt.Errorf("client URL cannot be empty")
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.Dial(config.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to RPC client at %s: %w", config.URL, err)
|
||||
}
|
||||
|
||||
ethClient := ethclient.NewClient(rpcClient)
|
||||
|
||||
geth := &Geth{
|
||||
ethClient: ethClient,
|
||||
rpcClient: rpcClient,
|
||||
url: config.URL,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), config.RequestTimeout)
|
||||
defer cancel()
|
||||
|
||||
if err := geth.IsConnected(ctx); err != nil {
|
||||
geth.Close()
|
||||
return nil, fmt.Errorf("connection test failed: %w", err)
|
||||
}
|
||||
|
||||
return geth, nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetEthClient() *ethclient.Client {
|
||||
return g.ethClient
|
||||
}
|
||||
|
||||
func (g *Geth) GetRPCClient() *rpc.Client {
|
||||
return g.rpcClient
|
||||
}
|
||||
|
||||
func (g *Geth) Close() {
|
||||
if g.rpcClient != nil {
|
||||
g.rpcClient.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Geth) IsConnected(ctx context.Context) error {
|
||||
if g.ethClient == nil {
|
||||
return fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
_, err := g.ethClient.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get network ID: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetNetworkID(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
networkID, err := g.ethClient.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get network ID: %w", err)
|
||||
}
|
||||
|
||||
return networkID.Uint64(), nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetChainID(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
chainID, err := g.ethClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get chain ID: %w", err)
|
||||
}
|
||||
|
||||
return chainID.Uint64(), nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetBlockNumber(ctx context.Context) (uint64, error) {
|
||||
if g.ethClient == nil {
|
||||
return 0, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
blockNumber, err := g.ethClient.BlockNumber(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get block number: %w", err)
|
||||
}
|
||||
|
||||
return blockNumber, nil
|
||||
}
|
||||
|
||||
func (g *Geth) GetURL() string {
|
||||
return g.url
|
||||
}
|
||||
|
||||
func (g *Geth) NewBoundContract(address common.Address, abiJSON string) (*bind.BoundContract, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
parsedABI, err := parseABI(abiJSON)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse ABI: %w", err)
|
||||
}
|
||||
|
||||
return bind.NewBoundContract(address, parsedABI, g.ethClient, g.ethClient, g.ethClient), nil
|
||||
}
|
||||
|
||||
func (g *Geth) CreateTransactOpts(ctx context.Context, privateKeyHex string, value *big.Int) (*bind.TransactOpts, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
privateKey, err := parsePrivateKey(privateKeyHex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
chainID, err := g.ethClient.ChainID(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get chain ID: %w", err)
|
||||
}
|
||||
|
||||
opts, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create transactor: %w", err)
|
||||
}
|
||||
|
||||
opts.Context = ctx
|
||||
if value != nil {
|
||||
opts.Value = value
|
||||
}
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
func (g *Geth) CreateCallOpts(ctx context.Context, blockNumber *big.Int) *bind.CallOpts {
|
||||
return &bind.CallOpts{
|
||||
Context: ctx,
|
||||
BlockNumber: blockNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Geth) WaitForTransaction(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
if g.ethClient == nil {
|
||||
return nil, fmt.Errorf("ethereum client is not initialized")
|
||||
}
|
||||
|
||||
receipt, err := bind.WaitMined(ctx, g.ethClient, &types.Transaction{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wait for transaction: %w", err)
|
||||
}
|
||||
|
||||
return receipt, nil
|
||||
}
|
||||
|
||||
func parseABI(abiJSON string) (abi.ABI, error) {
|
||||
return abi.JSON(strings.NewReader(abiJSON))
|
||||
}
|
||||
|
||||
func parsePrivateKey(privateKeyHex string) (*ecdsa.PrivateKey, error) {
|
||||
privateKeyHex = strings.TrimPrefix(privateKeyHex, "0x")
|
||||
|
||||
return crypto.HexToECDSA(privateKeyHex)
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import jwt2 "github.com/golang-jwt/jwt/v5"
|
||||
|
||||
type UserClaims struct {
|
||||
jwt2.RegisteredClaims
|
||||
UserID string
|
||||
KYCLevel int
|
||||
Sections []string
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
jwt2 "github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
const UserClaimKey = "User-Claims"
|
||||
|
||||
func CreateToken(secret []byte, claims *UserClaims) (string, error) {
|
||||
return jwt2.NewWithClaims(jwt2.SigningMethodHS512, claims).SignedString(secret)
|
||||
}
|
||||
|
||||
func ParseToken(tokenString string, secret []byte) (*UserClaims, error) {
|
||||
token, err := jwt2.ParseWithClaims(tokenString, &UserClaims{}, func(t *jwt2.Token) (interface{}, error) {
|
||||
return secret, nil
|
||||
})
|
||||
|
||||
var claim *UserClaims
|
||||
if token.Claims != nil {
|
||||
cc, ok := token.Claims.(*UserClaims)
|
||||
if ok {
|
||||
claim = cc
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return claim, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return claim, errors.New("token is not valid")
|
||||
}
|
||||
|
||||
return claim, nil
|
||||
}
|
||||
0
pkg/logger/.gitkeep
Normal file
0
pkg/logger/.gitkeep
Normal file
@ -1,57 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type LogLevel string
|
||||
|
||||
type contextKey string
|
||||
|
||||
const TraceIDKey contextKey = "trace_id"
|
||||
|
||||
type Logger struct {
|
||||
*slog.Logger
|
||||
}
|
||||
|
||||
func NewLogger(level LogLevel) *Logger {
|
||||
return &Logger{
|
||||
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: slog.LevelInfo, // default log level
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateTraceID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func WithTraceID(ctx context.Context) context.Context {
|
||||
if ctx.Value(TraceIDKey) == nil {
|
||||
return context.WithValue(ctx, TraceIDKey, GenerateTraceID())
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
func GetTraceID(ctx context.Context) string {
|
||||
if traceID, ok := ctx.Value(TraceIDKey).(string); ok {
|
||||
return traceID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (l *Logger) Info(ctx context.Context, msg string, args ...any) {
|
||||
traceID := GetTraceID(ctx)
|
||||
if traceID != "" {
|
||||
args = append(args, slog.String(string(TraceIDKey), traceID))
|
||||
}
|
||||
l.Logger.Info(msg, args...)
|
||||
}
|
||||
|
||||
func (l *Logger) ErrorWithoutContext(msg string, args ...any) {
|
||||
l.Logger.Error(msg, args...)
|
||||
}
|
||||
0
pkg/rabbit/.gitkeep
Normal file
0
pkg/rabbit/.gitkeep
Normal file
@ -1,32 +0,0 @@
|
||||
package sms
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
|
||||
"github.com/kavenegar/kavenegar-go"
|
||||
)
|
||||
|
||||
type Kavenegar struct {
|
||||
*kavenegar.Kavenegar
|
||||
}
|
||||
|
||||
type OTPMsg struct {
|
||||
Receptor string
|
||||
Token string
|
||||
Template string
|
||||
Params kavenegar.VerifyLookupParam
|
||||
}
|
||||
|
||||
func NewKavenegar(cfg config.SMSProvider) *Kavenegar {
|
||||
instance := kavenegar.New(cfg.APIKey)
|
||||
return &Kavenegar{
|
||||
Kavenegar: instance,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *Kavenegar) OTP(otpMsg *OTPMsg) error {
|
||||
if _, err := k.Verify.Lookup(otpMsg.Receptor, otpMsg.Template, otpMsg.Token, &otpMsg.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type HttpClient struct {
|
||||
client *http.Client
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func NewHttpClient() *HttpClient {
|
||||
return &HttpClient{
|
||||
http.DefaultClient,
|
||||
sync.Once{},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HttpClient) getClient() *http.Client {
|
||||
h.once.Do(func() {
|
||||
h.client = &http.Client{}
|
||||
})
|
||||
return h.client
|
||||
}
|
||||
|
||||
func (h *HttpClient) HttpRequest(ctx context.Context, method, url string, body interface{}, headers map[string]string) (*http.Response, error) {
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for headerTitle, headerValue := range headers {
|
||||
req.Header.Set(headerTitle, headerValue)
|
||||
}
|
||||
|
||||
resp, err := h.getClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
return &http.Response{
|
||||
Status: "200 ok",
|
||||
StatusCode: 200,
|
||||
Body: resp.Body,
|
||||
}, nil
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenOTPCode() string {
|
||||
newRand := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
minNum := 100000
|
||||
maxNum := 999999
|
||||
return strconv.Itoa(newRand.Intn(maxNum-minNum+1) + minNum)
|
||||
}
|
||||
0
pkg/utils/.gitkeep
Normal file
0
pkg/utils/.gitkeep
Normal file
@ -1,64 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"backend/pkg/errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
func IsValidPhone(value string) (bool, error) {
|
||||
re := regexp.MustCompile(`^(?:0|\+98|0098)(\d{10})$`)
|
||||
result := re.FindStringSubmatch(value)
|
||||
if len(result) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.ErrPhoneInvalid
|
||||
}
|
||||
|
||||
func IsValidNationalID(value string) (bool, error) {
|
||||
valueCount := len(value)
|
||||
if valueCount < 8 {
|
||||
return false, errors.ErrNationalIDInvalid
|
||||
}
|
||||
|
||||
if valueCount >= 8 && valueCount < 10 {
|
||||
value = strings.Repeat("0", 10-valueCount) + value
|
||||
}
|
||||
|
||||
var valueSlices []uint8
|
||||
for _, s := range value {
|
||||
i, err := strconv.ParseInt(string(s), 10, 8)
|
||||
if err != nil {
|
||||
return false, errors.ErrNationalIDInvalid
|
||||
}
|
||||
valueSlices = append(valueSlices, uint8(i))
|
||||
}
|
||||
|
||||
s := calculateNationalIDNumbers(&valueSlices)
|
||||
|
||||
s %= 11
|
||||
l := valueSlices[len(value)-1]
|
||||
if (s < 2 && s != int(l)) || (s >= 2 && int(l) != 11-s) {
|
||||
return false, errors.ErrNationalIDInvalid
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func calculateNationalIDNumbers(valueSlices *[]uint8) (sum int) {
|
||||
var i uint8
|
||||
for i = 10; i >= 2; i-- {
|
||||
sum += int(i * (*valueSlices)[10-i])
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func IsValidPublicKey(value string) (bool, error) {
|
||||
if !common.IsHexAddress(value) {
|
||||
return false, errors.ErrWalletInvalid
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
package zohal
|
||||
|
||||
type respMatched struct {
|
||||
Matched bool `json:"matched"`
|
||||
}
|
||||
type personIdentity struct {
|
||||
Matched bool `json:"matched"`
|
||||
LastName string `json:"last_name"`
|
||||
FirstName string `json:"first_name"`
|
||||
FatherName string `json:"father_name"`
|
||||
NationalCode string `json:"national_code"`
|
||||
IsDead bool `json:"is_dead"`
|
||||
Alive bool `json:"alive"`
|
||||
}
|
||||
type respBody[T any] struct {
|
||||
Data T `json:"data"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
type zohalResp[T any] struct {
|
||||
StatusCode int `json:"status_code"`
|
||||
Body respBody[T] `json:"response_body"`
|
||||
}
|
||||
|
||||
type (
|
||||
// https://service.zohal.io/api/v0/services/inquiry/shahkar
|
||||
ZohalShahkarReq struct {
|
||||
Phone string `json:"mobile"`
|
||||
NationalID string `json:"national_code"`
|
||||
}
|
||||
ZohalShahkarResp struct {
|
||||
zohalResp[respMatched]
|
||||
}
|
||||
// https://service.zohal.io/api/v0/services/inquiry/national_identity_inquiry
|
||||
ZohalIdentityReq struct {
|
||||
NationalID string `json:"national_code"`
|
||||
BirthDate string `json:"birth_date"`
|
||||
}
|
||||
ZohalIdentityResp struct {
|
||||
zohalResp[personIdentity]
|
||||
}
|
||||
// https://service.zohal.io/api/v0/services/inquiry/check_iban_with_national_code
|
||||
ZohalIbanReq struct {
|
||||
NationalID string `json:"national_code"`
|
||||
IBAN string `json:"IBAN"`
|
||||
BirthDate string `json:"birth_date"`
|
||||
}
|
||||
ZohalIbanResp struct {
|
||||
zohalResp[respMatched]
|
||||
}
|
||||
)
|
||||
@ -1,105 +0,0 @@
|
||||
package zohal
|
||||
|
||||
import (
|
||||
"backend/config"
|
||||
"backend/pkg/util"
|
||||
"backend/pkg/validate/common"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Zohal struct {
|
||||
cfg config.KYC
|
||||
}
|
||||
|
||||
func NewZohal(cfg config.KYC) *Zohal {
|
||||
return &Zohal{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// check Phone and NationalID
|
||||
func (z *Zohal) Shahkar(ctx context.Context, phone, nationalID string) (ZohalShahkarResp, error) {
|
||||
var req ZohalShahkarReq
|
||||
var resp ZohalShahkarResp
|
||||
ok, err := common.IsValidPhone(phone)
|
||||
if !ok {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
ok, err = common.IsValidNationalID(nationalID)
|
||||
if !ok {
|
||||
return resp, err
|
||||
}
|
||||
header := make(map[string]string)
|
||||
header["Content-Type"] = "application/json"
|
||||
header["Accept"] = "application/json"
|
||||
header["Authorization"] = fmt.Sprintf("Bearer %s", z.cfg.APIKey)
|
||||
|
||||
req.Phone = phone
|
||||
req.NationalID = nationalID
|
||||
|
||||
u, err := url.Parse(z.cfg.URL)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
u = u.JoinPath("inquiry", "shahkar")
|
||||
|
||||
client := util.NewHttpClient()
|
||||
clientResp, err := client.HttpRequest(ctx, http.MethodPost, u.String(), req, header)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(clientResp.Body)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bodyBytes, &resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (z *Zohal) GetPerson(ctx context.Context, nationalID, birthDate string) (ZohalIdentityResp, error) {
|
||||
var req ZohalIdentityReq
|
||||
var resp ZohalIdentityResp
|
||||
|
||||
header := make(map[string]string)
|
||||
header["Content-Type"] = "application/json"
|
||||
header["Accept"] = "application/json"
|
||||
header["Authorization"] = fmt.Sprintf("Bearer %s", z.cfg.APIKey)
|
||||
|
||||
req.BirthDate = birthDate
|
||||
req.NationalID = nationalID
|
||||
|
||||
u, err := url.Parse(z.cfg.URL)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
u = u.JoinPath("inquiry", "shahkar")
|
||||
|
||||
client := util.NewHttpClient()
|
||||
clientResp, err := client.HttpRequest(ctx, http.MethodPost, u.String(), req, header)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(clientResp.Body)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bodyBytes, &resp)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
return resp, err
|
||||
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
server:
|
||||
host: "localhost"
|
||||
port: 8080
|
||||
|
||||
db:
|
||||
user: "postgres"
|
||||
pass: "postgres"
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
db_name: "dezone"
|
||||
|
||||
redis:
|
||||
host: "localhost"
|
||||
port: 6379
|
||||
pass: ""
|
||||
|
||||
jwt:
|
||||
token_exp_minutes: 60
|
||||
refresh_token_exp_minutes: 10080
|
||||
token_secret: "Secret"
|
||||
|
||||
RPC:
|
||||
url: "exmaple.com"
|
||||
|
||||
otp:
|
||||
code_exp_minutes: 2
|
||||
@ -1,245 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Web3 Login process with MetaMask</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/ethers@5.6.4/dist/ethers.umd.min.js"
|
||||
type="application/javascript"
|
||||
></script>
|
||||
<script>
|
||||
function web3_check_metamask() {
|
||||
if (!window.ethereum) {
|
||||
console.error(
|
||||
"It seems that the MetaMask extension is not detected. Please install MetaMask first.",
|
||||
);
|
||||
alert(
|
||||
"It seems that the MetaMask extension is not detected. Please install MetaMask first.",
|
||||
);
|
||||
return false;
|
||||
} else {
|
||||
alert("✅ MetaMask extension has been detected!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function web3_metamask_hash(pubKey) {
|
||||
const z = await (
|
||||
await fetch("http://127.0.0.1:8080/api/auth/challenge", {
|
||||
body: `{ "pubKey": "${pubKey}"\n}`,
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
})
|
||||
).json();
|
||||
return z.message;
|
||||
}
|
||||
|
||||
async function login_api(pubKey, sig) {
|
||||
res = await (await fetch("http://127.0.0.1:8080/api/auth/authenticate", {
|
||||
body: JSON.stringify({ pubKey: pubKey, signature: sig }),
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
})).json();
|
||||
updateStatus(JSON.stringify(res), "text-green-600");
|
||||
response = await (await fetch("http://127.0.0.1:8080/api/hello", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${res.authorizationToken}`,
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "GET",
|
||||
})).json();
|
||||
console.log(response);
|
||||
}
|
||||
|
||||
async function web3_metamask_login() {
|
||||
// Check first if the user has the MetaMask installed
|
||||
if (web3_check_metamask()) {
|
||||
console.log("Initate Login Process");
|
||||
updateStatus("Initiating login process...", "text-blue-600");
|
||||
|
||||
// Get the Ethereum provider
|
||||
const provider = new ethers.providers.Web3Provider(window.ethereum);
|
||||
// Get Ethereum accounts
|
||||
await provider.send("eth_requestAccounts", []);
|
||||
console.log("Connected!!");
|
||||
updateStatus("Connected to MetaMask!", "text-green-600");
|
||||
|
||||
// Get the User Ethereum address
|
||||
const address = await provider.getSigner().getAddress();
|
||||
console.log(address);
|
||||
document.getElementById("address").textContent = address;
|
||||
|
||||
// Get custom message from input or use hashed string
|
||||
const customMessage = document
|
||||
.getElementById("messageInput")
|
||||
.value.trim();
|
||||
const messageToSign = customMessage || await web3_metamask_hash(address);
|
||||
console.log("Message to sign: " + messageToSign);
|
||||
|
||||
// Request the user to sign it
|
||||
updateStatus(
|
||||
"Please sign the message in MetaMask...",
|
||||
"text-orange-600",
|
||||
);
|
||||
const signature = await provider
|
||||
.getSigner()
|
||||
.signMessage(messageToSign);
|
||||
// Got the signature
|
||||
console.log("The signature: " + signature);
|
||||
document.getElementById("signature").textContent = signature;
|
||||
updateStatus("Message signed successfully!", "text-green-600");
|
||||
|
||||
// TODO
|
||||
// you can then send the signature to the webserver for further processing and verification
|
||||
console.log({address, signature})
|
||||
await login_api(address, signature);
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus(message, className) {
|
||||
const statusEl = document.getElementById("status");
|
||||
statusEl.textContent = message;
|
||||
statusEl.className = `text-sm font-medium ${className}`;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
|
||||
<div class="container mx-auto px-4 py-8 max-w-2xl">
|
||||
<div class="bg-white rounded-xl shadow-lg p-8">
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-2">
|
||||
Web3 MetaMask Login
|
||||
</h1>
|
||||
<p class="text-gray-600">
|
||||
Connect your wallet and sign messages securely
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- MetaMask Detection Section -->
|
||||
<div class="bg-gray-50 rounded-lg p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-700 mb-3">
|
||||
1. MetaMask Detection
|
||||
</h2>
|
||||
<button
|
||||
onclick="web3_check_metamask();"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200 ease-in-out transform hover:scale-105"
|
||||
>
|
||||
Detect MetaMask
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Message Input Section -->
|
||||
<div class="bg-gray-50 rounded-lg p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-700 mb-3">
|
||||
2. Custom Message (Optional)
|
||||
</h2>
|
||||
<div class="mb-4">
|
||||
<label
|
||||
for="messageInput"
|
||||
class="block text-sm font-medium text-gray-600 mb-2"
|
||||
>
|
||||
Enter a custom message to sign (leave empty for random hash):
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="messageInput"
|
||||
placeholder="Enter your custom message here..."
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition duration-200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Login Section -->
|
||||
<div class="bg-gray-50 rounded-lg p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-700 mb-3">
|
||||
3. Wallet Login
|
||||
</h2>
|
||||
<button
|
||||
onclick="web3_metamask_login();"
|
||||
class="bg-gradient-to-r from-orange-500 to-red-600 hover:from-orange-600 hover:to-red-700 text-white font-medium py-3 px-8 rounded-lg transition duration-200 ease-in-out transform hover:scale-105 shadow-md"
|
||||
>
|
||||
🦊 Login with MetaMask
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Status Section -->
|
||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-700 mb-3">Status</h3>
|
||||
<div id="status" class="text-sm text-gray-500">
|
||||
Ready to connect...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
<div class="bg-white border border-gray-200 rounded-lg p-6">
|
||||
<h3 class="text-lg font-semibold text-gray-700 mb-4">
|
||||
Connection Details
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-600 mb-1"
|
||||
>Wallet Address:</label
|
||||
>
|
||||
<div
|
||||
id="address"
|
||||
class="bg-gray-50 p-3 rounded border text-sm font-mono break-all text-gray-700"
|
||||
>
|
||||
Not connected
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-600 mb-1"
|
||||
>Message Signature:</label
|
||||
>
|
||||
<div
|
||||
id="signature"
|
||||
class="bg-gray-50 p-3 rounded border text-sm font-mono break-all text-gray-700 max-h-32 overflow-y-auto"
|
||||
>
|
||||
No signature yet
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Section -->
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<svg
|
||||
class="h-5 w-5 text-blue-400"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 20 20"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h4 class="text-sm font-medium text-blue-800">How it works</h4>
|
||||
<p class="text-sm text-blue-700 mt-1">
|
||||
First detect MetaMask, then optionally enter a custom message,
|
||||
and finally connect your wallet to sign the message securely.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user