package app import ( "context" "crypto/rand" "encoding/base64" "fmt" "net/http" "time" "git.optclblast.xyz/draincloud/draincloud-core/internal/domain" "git.optclblast.xyz/draincloud/draincloud-core/internal/logger" "git.optclblast.xyz/draincloud/draincloud-core/internal/storage" "github.com/gin-gonic/gin" "golang.org/x/crypto/bcrypt" ) type DrainCloud struct { mux *gin.Engine datadase storage.Database } func New( datadase storage.Database, ) *DrainCloud { mux := gin.Default() d := &DrainCloud{ datadase: datadase, } // Built-in auth component of DrainCloud-Core authGroup := mux.Group("/auth") { authGroup.POST("/register", d.Register) authGroup.POST("/login", d.Login) authGroup.POST("/logout", d.Logout) } d.mux = mux return d } func (d *DrainCloud) Run(ctx context.Context) error { return d.mux.Run() } func (d *DrainCloud) Register(ctx *gin.Context) { logger.Debug(ctx, "[register] new request") req := new(domain.RegisterRequest) err := ctx.BindJSON(req) if err != nil { logger.Error(ctx, "[register] failed to bind request", logger.Err(err)) ctx.JSON(http.StatusBadRequest, map[string]string{ "error": "bad request", }) return } _, err = d.register(ctx, req) if err != nil { logger.Error(ctx, "[register] failed to register user", logger.Err(err)) ctx.JSON(http.StatusBadRequest, map[string]string{ "error": err.Error(), }) return } ctx.JSON(http.StatusOK, map[string]bool{ "ok": true, }) } type registerResult struct { // cookies []http.Cookie } func (d *DrainCloud) register(ctx *gin.Context, req *domain.RegisterRequest) (*registerResult, error) { if err := validateLoginAndPassword(req.Login, req.Password); err != nil { return nil, fmt.Errorf("invalid creds: %w", err) } passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10) if err != nil { logger.Error(ctx, "[register] failed to generate password", logger.Err(err)) return nil, fmt.Errorf("failed to generate password: %w", err) } _, err = d.datadase.AddUser(ctx, req.Login, req.Login, passwordHash) if err != nil { return nil, fmt.Errorf("failed to add new user: %w", err) } sessionToken, err := generateSessionToken(100) if err != nil { return nil, fmt.Errorf("failed to generate a session token: %w", err) } ctx.SetCookie("__Session_token", sessionToken, int((time.Hour * 24).Seconds()), "_path", "_domain", true, true) csrfToken, err := generateSessionToken(100) if err != nil { return nil, fmt.Errorf("failed to generate a csrf token: %w", err) } ctx.SetCookie("__Csrf_token", csrfToken, int((time.Hour * 24).Seconds()), "_path", "_domain", true, false) // TODO save session into database return ®isterResult{}, nil } func validateLoginAndPassword(login, password string) error { if len(login) < 4 { return fmt.Errorf("login must be longer than 8 chars") } if len(password) < 6 { return fmt.Errorf("password must be longer than 8 chars") } return nil } func generateSessionToken(length int) (string, error) { bytes := make([]byte, length) if _, err := rand.Read(bytes); err != nil { return "", fmt.Errorf("failed to generate token: %w", err) } return base64.URLEncoding.EncodeToString(bytes), nil } func (d *DrainCloud) Login(ctx *gin.Context) { } func (d *DrainCloud) Logout(ctx *gin.Context) { }