From c4912af8fb2f89a721840b7f46274b23a72ed8a0 Mon Sep 17 00:00:00 2001 From: Vitaliy Pavlov Date: Fri, 10 May 2024 13:17:18 +0700 Subject: [PATCH] Initial commit --- .gitignore | 1 + app/constants/errors.go | 7 ++ app/constants/misc.go | 5 + app/controllers/auth.go | 15 +++ app/router/router.go | 17 ++++ app/server.go | 51 ++++++++++ auth/README.md | 11 ++ auth/auth.go | 77 ++++++++++++++ auth/middlewares/session.go | 40 ++++++++ auth/tokens.go | 76 ++++++++++++++ auth/types.go | 11 ++ database/database.go | 80 +++++++++++++++ database/entities/auth_token.go | 8 ++ database/entities/group.go | 16 +++ database/entities/permission.go | 6 ++ database/entities/server.go | 30 ++++++ database/entities/session.go | 29 ++++++ database/entities/user.go | 34 +++++++ docs/docs.go | 93 +++++++++++++++++ docs/swagger.json | 67 ++++++++++++ docs/swagger.yaml | 44 ++++++++ environment/environment.go | 19 ++++ go.mod | 67 ++++++++++++ go.sum | 174 ++++++++++++++++++++++++++++++++ main.go | 23 +++++ users/sql.go | 22 ++++ users/users.go | 9 ++ utils/crypto.go | 13 +++ utils/jwt.go | 28 +++++ validators/password.go | 28 +++++ validators/validator.go | 68 +++++++++++++ 31 files changed, 1169 insertions(+) create mode 100644 .gitignore create mode 100644 app/constants/errors.go create mode 100644 app/constants/misc.go create mode 100644 app/controllers/auth.go create mode 100644 app/router/router.go create mode 100644 app/server.go create mode 100644 auth/README.md create mode 100644 auth/auth.go create mode 100644 auth/middlewares/session.go create mode 100644 auth/tokens.go create mode 100644 auth/types.go create mode 100644 database/database.go create mode 100644 database/entities/auth_token.go create mode 100644 database/entities/group.go create mode 100644 database/entities/permission.go create mode 100644 database/entities/server.go create mode 100644 database/entities/session.go create mode 100644 database/entities/user.go create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml create mode 100644 environment/environment.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 users/sql.go create mode 100644 users/users.go create mode 100644 utils/crypto.go create mode 100644 utils/jwt.go create mode 100644 validators/password.go create mode 100644 validators/validator.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2eea525 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/app/constants/errors.go b/app/constants/errors.go new file mode 100644 index 0000000..05fc778 --- /dev/null +++ b/app/constants/errors.go @@ -0,0 +1,7 @@ +package constants + +const ( + AUTH_FAILED = "User with specified email and password not found" + NOT_FOUND = "Wrong request method or path" + UNAUTHORIZED = "Unauthorized" +) diff --git a/app/constants/misc.go b/app/constants/misc.go new file mode 100644 index 0000000..4f47cf5 --- /dev/null +++ b/app/constants/misc.go @@ -0,0 +1,5 @@ +package constants + +const ( + JWT_APP_ISS = "stc" +) diff --git a/app/controllers/auth.go b/app/controllers/auth.go new file mode 100644 index 0000000..0197f74 --- /dev/null +++ b/app/controllers/auth.go @@ -0,0 +1,15 @@ +package controllers + +import ( + "system-trace/core/auth" + + "github.com/gofiber/fiber/v2" +) + +func InitAuth(app *fiber.App, version *fiber.Router) { + v := *version + g := v.Group("/auth") + { + g.Post("/login", auth.ReqTokens) + } +} diff --git a/app/router/router.go b/app/router/router.go new file mode 100644 index 0000000..ca30799 --- /dev/null +++ b/app/router/router.go @@ -0,0 +1,17 @@ +package router + +import ( + "system-trace/core/app/controllers" + _ "system-trace/core/docs" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/swagger" +) + +func Init(app *fiber.App) { + app.Get("/swagger/*", swagger.HandlerDefault) + v1 := app.Group("/v1") + { + controllers.InitAuth(app, &v1) + } +} diff --git a/app/server.go b/app/server.go new file mode 100644 index 0000000..2ec4442 --- /dev/null +++ b/app/server.go @@ -0,0 +1,51 @@ +package app + +import ( + "os" + "system-trace/core/app/constants" + "system-trace/core/app/router" + "system-trace/core/auth/middlewares" + "system-trace/core/environment" + + "github.com/goccy/go-json" + + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" + "github.com/gofiber/fiber/v2/middleware/recover" +) + +var Instance *fiber.App + +func Serve() { + Instance = initServer() + Instance.Use(recover.New()) + Instance.Use(cors.New()) + Instance.Use(middlewares.ValidateSession) + router.Init(Instance) + Instance.Use(func(c *fiber.Ctx) error { + return c. + Status(fiber.StatusNotFound). + JSON(fiber.Map{ + "error": constants.NOT_FOUND, + }) + }) + Instance.Listen(":" + os.Getenv("APP_PORT")) +} + +func initServer() *fiber.App { + debug := environment.IsDebug() + + return fiber.New(fiber.Config{ + JSONEncoder: json.Marshal, + JSONDecoder: json.Unmarshal, + CaseSensitive: true, + EnablePrintRoutes: debug, + Prefork: true, + AppName: "System Trace API", + ErrorHandler: func(c *fiber.Ctx, err error) error { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + }, + }) +} diff --git a/auth/README.md b/auth/README.md new file mode 100644 index 0000000..2f16350 --- /dev/null +++ b/auth/README.md @@ -0,0 +1,11 @@ +## Password requirements: +- Minimum 1 uppercase character +- Minimum 1 lowercase character +- Minimum 1 digit +- Minimum 1 special character +- Minimal length 8 characters + +### Auth request return Set-Cookie headers to set server-cookie on client +### AccessToken lifetime is 1 hour +### RefreshToken lifetime is 24 hours +### When AccessToken expires, RefreshToken requests a new one. Old pair becomes revoked and creates new one \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..7cd5fe9 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,77 @@ +package auth + +import ( + "errors" + "system-trace/core/app/constants" + "system-trace/core/users" + "system-trace/core/validators" + "time" + + "github.com/gofiber/fiber/v2" +) + +// ReqTokens godoc +// @Summary Request pair of tokens +// @Description Returns pair of access and refresh tokens +// @Tags auth +// @Produce json +// @Param request body AuthBody true "Request body" +// @Header 200 {string} Token "accessToken=..." +// @Header 200 {string} Set-Cookie "refreshToken=..." +// @Success 200 +// @Router /auth/login [post] +func ReqTokens(c *fiber.Ctx) error { + ab := new(AuthBody) + if err := c.BodyParser(ab); err != nil { + return err + } + if err := validators.Validate(c, ab); err != nil { + return err + } + + u, err := users.FindByEmailAndPassword(ab.Email, ab.Password) + if err != nil { + e := err.Error() + if e == "sql: no rows in result set" { + e = constants.AUTH_FAILED + } + return c. + Status(fiber.StatusBadRequest). + JSON(fiber.Map{ + "error": e, + }) + } + if u != nil { + p, err := genPair(u) + if err != nil { + return c. + Status(fiber.StatusBadRequest). + JSON(fiber.Map{ + "error": err.Error(), + }) + } + setCookie(c, p) + return c.SendStatus(fiber.StatusOK) + } + + return errors.New(constants.AUTH_FAILED) +} + +func setCookie(c *fiber.Ctx, p *PairTokens) { + // Access token + atc := new(fiber.Cookie) + atc.Name = "accessToken" + atc.Value = p.AccessToken + atc.Expires = time.Now().Add(time.Duration(AccessTokenLifetime) * time.Hour) + atc.Secure = true + atc.HTTPOnly = true + c.Cookie(atc) + // Refresh token + rtc := new(fiber.Cookie) + rtc.Name = "refreshToken" + rtc.Value = p.RefreshToken + rtc.Expires = time.Now().Add(time.Duration(RefreshTokenLifetime) * time.Hour) + rtc.Secure = true + rtc.HTTPOnly = true + c.Cookie(rtc) +} diff --git a/auth/middlewares/session.go b/auth/middlewares/session.go new file mode 100644 index 0000000..7379796 --- /dev/null +++ b/auth/middlewares/session.go @@ -0,0 +1,40 @@ +package middlewares + +import ( + "fmt" + "net/http" + "strings" + "system-trace/core/app/constants" + "system-trace/core/utils" + + "github.com/gofiber/fiber/v2" +) + +func ValidateSession(c *fiber.Ctx) error { + header := c.GetReqHeaders()[http.CanonicalHeaderKey("Authorization")] + if len(header) <= 0 || len(header[0]) <= 0 || !validateToken(c, header[0]) { + return c.Status(http.StatusForbidden).JSON(fiber.Map{ + "error": constants.UNAUTHORIZED, + }) + } + + return c.Next() +} + +func validateToken(c *fiber.Ctx, hash string) bool { + splitted := strings.Split(hash, " ") + if len(splitted) <= 1 { + return false + } + claims, err := utils.ValidateJWT(splitted[1]) + fmt.Println(claims, err) + // id, ok := claims["ID"].(string) + // TODO validate date and check refresh token + if err != nil || claims["iss"] != constants.JWT_APP_ISS { + return false + } + + // c.Locals("userId", id) + + return true +} diff --git a/auth/tokens.go b/auth/tokens.go new file mode 100644 index 0000000..23d4b9f --- /dev/null +++ b/auth/tokens.go @@ -0,0 +1,76 @@ +package auth + +import ( + "context" + "fmt" + "system-trace/core/app/constants" + "system-trace/core/database" + "system-trace/core/database/entities" + "system-trace/core/utils" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +const ( + AccessTokenLifetime int8 = 3 + RefreshTokenLifetime int8 = 24 +) + +func genPair(u *entities.User) (*PairTokens, error) { + at, rt, err := genTokens(u) + + err = insertPair(u.ID, at, rt) + if err != nil { + return nil, err + } + + p := PairTokens{ + AccessToken: at, + RefreshToken: rt, + } + + return &p, nil +} + +func genTokens(u *entities.User) (string, string, error) { + at, err := genToken(fmt.Sprintf("%d", u.ID), AccessTokenLifetime) + if err != nil { + return "", "", err + } + + rt, err := genToken(at, RefreshTokenLifetime) + if err != nil { + return "", "", err + } + + return at, rt, nil +} + +func genToken(sub string, hours int8) (string, error) { + fmt.Println(sub, hours) + c := jwt.MapClaims{ + "iss": constants.JWT_APP_ISS, + "sub": sub, + "iat": time.Now().Unix(), + "exp": time.Now().Add(time.Duration(hours) * time.Hour).Unix(), + } + a, err := utils.SignJWT(c) + + return a, err +} + +func insertPair(id int32, at, rt string) error { + aut := entities.AuthToken{ + UserID: id, + AccessToken: at, + RefreshToken: rt, + } + + ctx := context.Background() + _, err := database.PG.NewInsert(). + Model(&aut). + Exec(ctx) + + return err +} diff --git a/auth/types.go b/auth/types.go new file mode 100644 index 0000000..92cf6fc --- /dev/null +++ b/auth/types.go @@ -0,0 +1,11 @@ +package auth + +type PairTokens struct { + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` +} + +type AuthBody struct { + Email string `json:"email" validate:"required,email" example:"john@proton.mail"` + Password string `json:"password" validate:"required,password" example:"Aasdfg1!"` +} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..587973f --- /dev/null +++ b/database/database.go @@ -0,0 +1,80 @@ +package database + +import ( + "context" + "crypto/tls" + "database/sql" + "fmt" + "os" + "system-trace/core/database/entities" + "system-trace/core/environment" + "time" + + "github.com/gofiber/fiber/v2" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect/pgdialect" + "github.com/uptrace/bun/driver/pgdriver" + "github.com/uptrace/bun/extra/bundebug" +) + +var PG *bun.DB + +func Connect() { + pgconn := pgdriver.NewConnector( + pgdriver.WithNetwork("tcp"), + pgdriver.WithAddr(fmt.Sprintf("%s:%s", os.Getenv("DB_HOST"), os.Getenv("DB_PORT"))), + pgdriver.WithTLSConfig(&tls.Config{InsecureSkipVerify: true}), + pgdriver.WithInsecure(true), + pgdriver.WithUser(os.Getenv("DB_USER")), + pgdriver.WithPassword(os.Getenv("DB_PASS")), + pgdriver.WithDatabase(os.Getenv("DB_NAME")), + pgdriver.WithApplicationName("st-controller"), + pgdriver.WithTimeout(5*time.Second), + pgdriver.WithDialTimeout(5*time.Second), + pgdriver.WithReadTimeout(5*time.Second), + pgdriver.WithWriteTimeout(5*time.Second), + ) + + sqldb := sql.OpenDB(pgconn) + db := bun.NewDB(sqldb, pgdialect.New()) + + if environment.IsDebug() { + db.AddQueryHook(bundebug.NewQueryHook( + bundebug.WithVerbose(true), + bundebug.FromEnv("BUNDEBUG"), + )) + } + + if !fiber.IsChild() { + err := createSchema(db) + if err != nil { + panic(err) + } + } + + PG = db +} + +func createSchema(db *bun.DB) error { + models := []interface{}{ + (*entities.Group)(nil), + (*entities.GroupPermission)(nil), + (*entities.User)(nil), + (*entities.Session)(nil), + (*entities.Server)(nil), + (*entities.AuthToken)(nil), + } + ctx := context.Background() + + for _, model := range models { + _, err := db. + NewCreateTable(). + Model(model). + IfNotExists(). + Exec(ctx) + if err != nil { + return err + } + } + return nil +} diff --git a/database/entities/auth_token.go b/database/entities/auth_token.go new file mode 100644 index 0000000..b34df64 --- /dev/null +++ b/database/entities/auth_token.go @@ -0,0 +1,8 @@ +package entities + +type AuthToken struct { + UserID int32 + AccessToken string `bun:"type:varchar"` + RefreshToken string `bun:"type:varchar"` + IsRevoked bool `bun:",default:false"` +} diff --git a/database/entities/group.go b/database/entities/group.go new file mode 100644 index 0000000..ef392f9 --- /dev/null +++ b/database/entities/group.go @@ -0,0 +1,16 @@ +package entities + +import "time" + +type Group struct { + ID int32 `bun:",pk,autoincrement"` + IssuerID int32 `bun:",notnull"` + Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"` + Name string `bun:",notnull,unique"` + GroupID int32 `bun:",notnull"` + Users []*User `bun:"rel:has-many,join:id=group_id"` + Permissions []*GroupPermission `bun:"rel:has-many,join:id=group_id"` + CreatedAt time.Time `bun:",notnull,default:current_timestamp"` + UpdatedAt time.Time + DeletedAt time.Time `bun:",soft_delete,nullzero"` +} diff --git a/database/entities/permission.go b/database/entities/permission.go new file mode 100644 index 0000000..ea1353f --- /dev/null +++ b/database/entities/permission.go @@ -0,0 +1,6 @@ +package entities + +type GroupPermission struct { + GroupID int32 `bun:",notnull" json:"-"` + Value int8 `bun:",notnull"` +} diff --git a/database/entities/server.go b/database/entities/server.go new file mode 100644 index 0000000..b04d774 --- /dev/null +++ b/database/entities/server.go @@ -0,0 +1,30 @@ +package entities + +import ( + "context" + "time" + + "github.com/uptrace/bun" +) + +type Server struct { + ID int64 `bun:",pk,autoincrement"` + Hostname string `bun:",notnull"` + IP string `bun:",notnull"` + IsOnline bool `bun:",notnull,default:true"` + LastOnline time.Time + CreatedAt time.Time `bun:",notnull,default:current_timestamp"` + UpdatedAt time.Time +} + +var _ bun.BeforeAppendModelHook = (*Server)(nil) + +func (s *Server) BeforeAppendModel(ctx context.Context, query bun.Query) error { + switch query.(type) { + case *bun.InsertQuery: + s.LastOnline = time.Now() + case *bun.UpdateQuery: + s.UpdatedAt = time.Now() + } + return nil +} diff --git a/database/entities/session.go b/database/entities/session.go new file mode 100644 index 0000000..882f105 --- /dev/null +++ b/database/entities/session.go @@ -0,0 +1,29 @@ +package entities + +import ( + "context" + "time" + + "github.com/uptrace/bun" +) + +type Session struct { + ID int64 `bun:",pk,autoincrement"` + IssuerID int32 `bun:",notnull"` + Issuer *User `bun:"rel:belongs-to,join:issuer_id=id"` + Configuration map[string]interface{} `bun:"type:jsonb"` + CreatedAt time.Time `bun:",notnull,default:current_timestamp"` + FinishedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time `bun:",soft_delete,nullzero"` +} + +var _ bun.BeforeAppendModelHook = (*Session)(nil) + +func (s *Session) BeforeAppendModel(ctx context.Context, query bun.Query) error { + switch query.(type) { + case *bun.UpdateQuery: + s.UpdatedAt = time.Now() + } + return nil +} diff --git a/database/entities/user.go b/database/entities/user.go new file mode 100644 index 0000000..e80068f --- /dev/null +++ b/database/entities/user.go @@ -0,0 +1,34 @@ +package entities + +import ( + "context" + "time" + + "github.com/uptrace/bun" +) + +type User struct { + ID int32 `bun:",pk,autoincrement"` + Email string `bun:",notnull,unique"` + PasswordHash string `bun:",notnull,type:varchar(64)"` + PasswordLength int8 `bun:",notnull"` + RealName string `bun:",notnull"` + GroupID int32 `bun:",notnull"` + Group *Group `bun:"rel:belongs-to,join:group_id=id"` + IsRequiredToSetPassword bool `bun:",notnull,default:true"` + IsActive bool `bun:",notnull,default:true"` + LastLogin time.Time + CreatedAt time.Time `bun:",notnull,default:current_timestamp"` + UpdatedAt time.Time + DeletedAt time.Time `bun:",soft_delete,nullzero"` +} + +var _ bun.BeforeAppendModelHook = (*User)(nil) + +func (u *User) BeforeAppendModel(ctx context.Context, query bun.Query) error { + switch query.(type) { + case *bun.UpdateQuery: + u.UpdatedAt = time.Now() + } + return nil +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..eddbcca --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,93 @@ +// 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": { + "name": "https://peresvet.it" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/auth/login": { + "post": { + "description": "Returns pair of access and refresh tokens", + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Request pair of tokens", + "parameters": [ + { + "description": "Request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/auth.AuthBody" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "definitions": { + "auth.AuthBody": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "example": "john@proton.mail" + }, + "password": { + "type": "string", + "example": "Aasdfgh1!" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "/v1", + Schemes: []string{}, + Title: "System Trace API", + Description: "", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..9b4357a --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,67 @@ +{ + "swagger": "2.0", + "info": { + "title": "System Trace API", + "contact": { + "name": "https://peresvet.it" + }, + "version": "1.0" + }, + "basePath": "/v1", + "paths": { + "/auth/login": { + "post": { + "description": "Returns pair of access and refresh tokens", + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Request pair of tokens", + "parameters": [ + { + "description": "Request body", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/auth.AuthBody" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + } + }, + "definitions": { + "auth.AuthBody": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "example": "john@proton.mail" + }, + "password": { + "type": "string", + "example": "Aasdfgh1!" + } + } + } + }, + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..2fed0cc --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,44 @@ +basePath: /v1 +definitions: + auth.AuthBody: + properties: + email: + example: john@proton.mail + type: string + password: + example: Aasdfgh1! + type: string + required: + - email + - password + type: object +info: + contact: + name: https://peresvet.it + title: System Trace API + version: "1.0" +paths: + /auth/login: + post: + description: Returns pair of access and refresh tokens + parameters: + - description: Request body + in: body + name: request + required: true + schema: + $ref: '#/definitions/auth.AuthBody' + produces: + - application/json + responses: + "200": + description: OK + summary: Request pair of tokens + tags: + - auth +securityDefinitions: + Bearer: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/environment/environment.go b/environment/environment.go new file mode 100644 index 0000000..2d92c29 --- /dev/null +++ b/environment/environment.go @@ -0,0 +1,19 @@ +package environment + +import ( + "log" + "os" + + "github.com/joho/godotenv" +) + +func Load() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } +} + +func IsDebug() bool { + return os.Getenv("DEBUG") == "true" +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..804c069 --- /dev/null +++ b/go.mod @@ -0,0 +1,67 @@ +module system-trace/core + +go 1.22.3 + +require ( + github.com/go-playground/validator v9.31.0+incompatible + github.com/go-playground/validator/v10 v10.20.0 + github.com/goccy/go-json v0.10.2 + github.com/gofiber/fiber v1.14.6 + github.com/gofiber/fiber/v2 v2.52.4 + github.com/gofiber/swagger v1.0.0 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/joho/godotenv v1.5.1 + github.com/swaggo/swag v1.16.3 + github.com/uptrace/bun v1.2.1 + github.com/uptrace/bun/dialect/pgdialect v1.2.1 + github.com/uptrace/bun/driver/pgdriver v1.2.1 + github.com/uptrace/bun/extra/bundebug v1.2.1 + github.com/wasilibs/go-re2 v1.5.2 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/andybalholm/brotli v1.1.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/gofiber/utils v0.0.10 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/schema v1.1.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/klauspost/compress v1.17.8 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magefile/mage v1.14.0 // 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.15 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/swaggo/files/v2 v2.0.0 // indirect + github.com/tetratelabs/wazero v1.7.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.52.0 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/crypto v0.23.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect + golang.org/x/tools v0.7.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + mellium.im/sasl v0.3.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..beb4295 --- /dev/null +++ b/go.sum @@ -0,0 +1,174 @@ +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +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.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +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-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp4Mit+3FDh548oRqwVgNsHA= +github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofiber/fiber v1.14.6 h1:QRUPvPmr8ijQuGo1MgupHBn8E+wW0IKqiOvIZPtV70o= +github.com/gofiber/fiber v1.14.6/go.mod h1:Yw2ekF1YDPreO9V6TMYjynu94xRxZBdaa8X5HhHsjCM= +github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= +github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/swagger v1.0.0 h1:BzUzDS9ZT6fDUa692kxmfOjc1DZiloLiPK/W5z1H1tc= +github.com/gofiber/swagger v1.0.0/go.mod h1:QrYNF1Yrc7ggGK6ATsJ6yfH/8Zi5bu9lA7wB8TmCecg= +github.com/gofiber/utils v0.0.10 h1:3Mr7X7JdCUo7CWf/i5sajSaDmArEDtti8bM1JUVso2U= +github.com/gofiber/utils v0.0.10/go.mod h1:9J5aHFUIjq0XfknT4+hdSMG6/jzfaAgCu4HEbWDeBlo= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +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/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= +github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +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.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= +github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= +github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.2.1 h1:2ENAcfeCfaY5+2e7z5pXrzFKy3vS8VXvkCag6N2Yzfk= +github.com/uptrace/bun v1.2.1/go.mod h1:cNg+pWBUMmJ8rHnETgf65CEvn3aIKErrwOD6IA8e+Ec= +github.com/uptrace/bun/dialect/pgdialect v1.2.1 h1:ceP99r03u+s8ylaDE/RzgcajwGiC76Jz3nS2ZgyPQ4M= +github.com/uptrace/bun/dialect/pgdialect v1.2.1/go.mod h1:mv6B12cisvSc6bwKm9q9wcrr26awkZK8QXM+nso9n2U= +github.com/uptrace/bun/driver/pgdriver v1.2.1 h1:Cp6c1tKzbTIyL8o0cGT6cOhTsmQZdsUNhgcV51dsmLU= +github.com/uptrace/bun/driver/pgdriver v1.2.1/go.mod h1:jEd3WGx74hWLat3/IkesOoWNjrFNUDADK3nkyOFOOJM= +github.com/uptrace/bun/extra/bundebug v1.2.1 h1:85MYpX3QESYI02YerKxUi1CD9mHuLrc2BXs1eOCtQus= +github.com/uptrace/bun/extra/bundebug v1.2.1/go.mod h1:sfGKIi0HSGxsTC/sgIHGwpnYduHHYhdMeOIwurgSY+Y= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +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.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= +github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0= +github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +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/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/wasilibs/go-re2 v1.5.2 h1:fDO2TJrRzRrv3jD0gzOvmZ2UM4Yt9YXOEdLrlNc/Ies= +github.com/wasilibs/go-re2 v1.5.2/go.mod h1:UqqxQ1O99boQUm1r61H/IYGiGQOS/P88K7hU5nLNkEg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +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/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= +mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= +mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/main.go b/main.go new file mode 100644 index 0000000..db132f8 --- /dev/null +++ b/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "system-trace/core/app" + "system-trace/core/database" + "system-trace/core/environment" + "system-trace/core/validators" +) + +// @title System Trace API +// @version 1.0 +// @contact.name https://peresvet.it +// @BasePath /v1 +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +// @externalDocs.description OpenAPI +func main() { + environment.Load() + validators.RegisterValidators() + database.Connect() + app.Serve() +} diff --git a/users/sql.go b/users/sql.go new file mode 100644 index 0000000..fc34e56 --- /dev/null +++ b/users/sql.go @@ -0,0 +1,22 @@ +package users + +import ( + "context" + "system-trace/core/database" + "system-trace/core/database/entities" + "system-trace/core/utils" +) + +func FindByEmailAndPassword(email, password string) (*entities.User, error) { + passwordHash := utils.SHA256(password) + ctx := context.Background() + u := new(entities.User) + err := database.PG.NewSelect(). + Model(u). + Where("email = ?", email). + Where("password_hash = ?", passwordHash). + Column("id"). + Scan(ctx) + + return u, err +} diff --git a/users/users.go b/users/users.go new file mode 100644 index 0000000..2b4904b --- /dev/null +++ b/users/users.go @@ -0,0 +1,9 @@ +package users + +import ( + "github.com/gofiber/fiber/v2" +) + +func Get(c *fiber.Ctx) error { + return nil +} diff --git a/utils/crypto.go b/utils/crypto.go new file mode 100644 index 0000000..fd8142f --- /dev/null +++ b/utils/crypto.go @@ -0,0 +1,13 @@ +package utils + +import ( + "crypto/sha256" + "fmt" +) + +func SHA256(s string) string { + h := sha256.New() + h.Write([]byte(s)) + bs := h.Sum(nil) + return fmt.Sprintf("%x", bs) +} diff --git a/utils/jwt.go b/utils/jwt.go new file mode 100644 index 0000000..9ec12b4 --- /dev/null +++ b/utils/jwt.go @@ -0,0 +1,28 @@ +package utils + +import ( + "fmt" + + "github.com/golang-jwt/jwt/v5" +) + +func SignJWT(c jwt.Claims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) + ts, err := token.SignedString([]byte("123")) + + return ts, err +} + +func ValidateJWT(h string) (jwt.MapClaims, error) { + token, err := jwt.Parse(h, func(token *jwt.Token) (interface{}, error) { + return []byte("123"), nil + }) + if err != nil { + fmt.Println(err) + return nil, err + } + + claims := token.Claims.(jwt.MapClaims) + + return claims, err +} diff --git a/validators/password.go b/validators/password.go new file mode 100644 index 0000000..8fb58e8 --- /dev/null +++ b/validators/password.go @@ -0,0 +1,28 @@ +package validators + +import ( + regexp "github.com/wasilibs/go-re2" + + "github.com/go-playground/validator/v10" +) + +func regPassword(v *XValidator) { + v.validator.RegisterValidation("password", func(fl validator.FieldLevel) bool { + tests := []string{ + `[a-z]`, + `[A-Z]`, + `[\d]`, + `[@$!%*?&]`, + `[A-Za-z\d@$!%*?&]`, + `.{8,}`, + } + password := fl.Field().String() + for _, test := range tests { + t, _ := regexp.MatchString(test, password) + if !t { + return false + } + } + return true + }) +} diff --git a/validators/validator.go b/validators/validator.go new file mode 100644 index 0000000..ee2d0a8 --- /dev/null +++ b/validators/validator.go @@ -0,0 +1,68 @@ +package validators + +import ( + "errors" + "fmt" + "strings" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" +) + +type ( + ErrorResponse struct { + Error bool + FailedField string + Tag string + } + XValidator struct { + validator *validator.Validate + } +) + +var Validator *XValidator +var validate = validator.New() + +func RegisterValidators() { + Validator = &XValidator{ + validator: validate, + } + regPassword(Validator) +} + +func Validate(c *fiber.Ctx, data interface{}) error { + if errs := Validator.validateFields(data); len(errs) > 0 && errs[0].Error { + errMsgs := make([]string, 0) + + for _, err := range errs { + errMsgs = append(errMsgs, fmt.Sprintf( + "field '%s' needs to implement '%s' rule", + err.FailedField, + err.Tag, + )) + } + + return errors.New(strings.Join(errMsgs, " || ")) + } + + return nil +} + +func (v XValidator) validateFields(data interface{}) []ErrorResponse { + validationErrors := []ErrorResponse{} + + errs := validate.Struct(data) + if errs != nil { + for _, err := range errs.(validator.ValidationErrors) { + elem := ErrorResponse{ + FailedField: err.Field(), + Tag: err.ActualTag(), + Error: true, + } + + validationErrors = append(validationErrors, elem) + } + } + + return validationErrors +}