diff --git a/go.mod b/go.mod index ac078e1..d30eb8c 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ go 1.22.3 require ( github.com/MarceloPetrucio/go-scalar-api-reference v0.0.0-20240521013641-ce5d2efe0e06 github.com/gin-gonic/gin v1.10.0 + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/mattn/go-sqlite3 v1.14.16 github.com/pelletier/go-toml/v2 v2.2.2 github.com/rs/zerolog v1.33.0 github.com/swaggo/swag v1.16.3 + golang.org/x/crypto v0.23.0 xorm.io/xorm v1.3.9 ) @@ -46,7 +48,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.23.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index b9774e6..f36e0fe 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= diff --git a/internal/models/error.go b/internal/models/error.go new file mode 100644 index 0000000..1ac1dd8 --- /dev/null +++ b/internal/models/error.go @@ -0,0 +1,6 @@ +package models + +// Error response +type Error struct { + Type string `json:"type"` +} diff --git a/internal/models/token.go b/internal/models/token.go new file mode 100644 index 0000000..016abd7 --- /dev/null +++ b/internal/models/token.go @@ -0,0 +1,6 @@ +package models + +// Token response +type Token struct { + Token string `json:"token"` +} diff --git a/internal/models/v1/account.go b/internal/models/v1/account.go new file mode 100644 index 0000000..ec93eb2 --- /dev/null +++ b/internal/models/v1/account.go @@ -0,0 +1,13 @@ +package v1 + +// Account response +type Account struct { + ID int `json:"id"` + Username string `json:"username"` +} + +// Register body +type Register struct { + Username string `json:"username"` + Password string `json:"password"` +} diff --git a/internal/router/api/v1/account/register.go b/internal/router/api/v1/account/register.go new file mode 100644 index 0000000..5f8c5ec --- /dev/null +++ b/internal/router/api/v1/account/register.go @@ -0,0 +1,85 @@ +package account + +import ( + "fmt" + + "git.supernets.org/perp/gopay/internal/context" + "git.supernets.org/perp/gopay/internal/jwt" + v1 "git.supernets.org/perp/gopay/internal/models/v1" + "golang.org/x/crypto/bcrypt" +) + +// @summary Account registration +// @description Register an account +// @tags account +// @accept json +// @produce json +// @param register body v1.Register true "alice" "supersecretpassword" +// @success 200 {object} models.Token +// @failure 400 {object} models.Error "MissingBody | UsernameTaken" +// @failure 403 {object} models.Error "RegistrationDisabled" +// @failure 500 {object} models.Error "InternalServerError" +// @router /v1/account/register [post] +func Register(ctx *context.Context) { + // Check if registration is disabled + if ctx.Config.Auth.Disabled { + ctx.JSON(403, ctx.Error("RegistrationDisabled")) + return + } + + // Store body + var body *v1.Register + + // Bind JSON + err := ctx.BindJSON(&body) + if err != nil { + fmt.Println(err) + ctx.JSON(400, ctx.Error("MissingBody")) + return + } + + // Select account by username + account, err := ctx.Db.Account.SelectByUsername(body.Username) + if err != nil { + ctx.JSON(500, ctx.Error("InternalServerError")) + return + } + + fmt.Println(account) + + // Account exists + if account.Username != "" { + ctx.JSON(400, ctx.Error("UsernameTaken")) + return + } + + // Hash password + password, err := bcrypt.GenerateFromPassword([]byte(body.Password), ctx.Config.Auth.Cost) + if err != nil { + ctx.JSON(500, ctx.Error("InternalServerError")) + return + } + + // Insert account + err = ctx.Db.Account.Insert(body.Username, string(password)) + if err != nil { + ctx.JSON(500, ctx.Error("InternalServerError")) + return + } + + // Select account by username + account, err = ctx.Db.Account.SelectByUsername(body.Username) + if err != nil { + ctx.JSON(500, ctx.Error("InternalServerError")) + return + } + + // Generate token + token, err := jwt.Encode(account.ID) + if err != nil { + ctx.JSON(500, ctx.Error("InternalServerError")) + return + } + + ctx.JSON(200, ctx.Token(token)) +} diff --git a/internal/router/api/v1/v1.go b/internal/router/api/v1/v1.go index 9464f7d..b2825fb 100644 --- a/internal/router/api/v1/v1.go +++ b/internal/router/api/v1/v1.go @@ -14,7 +14,10 @@ package v1 -import "git.supernets.org/perp/gopay/internal/context" +import ( + "git.supernets.org/perp/gopay/internal/context" + "git.supernets.org/perp/gopay/internal/router/api/v1/account" +) // @title GoPay API v1 // @version 1.0 @@ -29,6 +32,14 @@ import "git.supernets.org/perp/gopay/internal/context" // Register v1 routes func Register(ctx *context.Context) { v1 := ctx.Group("v1") - v1.GET("scalar", ctx.API(Spec)) - v1.GET("docs", ctx.API(Spec)) + + { + v1.GET("scalar", ctx.API(Spec)) + v1.GET("docs", ctx.API(Spec)) + } + + { + a := v1.Group("account") + a.POST("register", ctx.API(account.Register)) + } }