refactoring and fixes. test with /auth/register handler
This commit is contained in:
parent
286a0fe826
commit
0202bd5dbb
@ -5,10 +5,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/app/handlers"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/domain"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/domain"
|
||||||
filesengine "git.optclblast.xyz/draincloud/draincloud-core/internal/files_engine"
|
filesengine "git.optclblast.xyz/draincloud/draincloud-core/internal/files_engine"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/processor"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/processor"
|
||||||
|
resolvedispatcher "git.optclblast.xyz/draincloud/draincloud-core/internal/resolve_dispatcher"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@ -27,16 +28,21 @@ func New(
|
|||||||
) *DrainCloud {
|
) *DrainCloud {
|
||||||
mux := gin.Default()
|
mux := gin.Default()
|
||||||
|
|
||||||
|
dispatcher := resolvedispatcher.New()
|
||||||
|
|
||||||
d := &DrainCloud{
|
d := &DrainCloud{
|
||||||
database: database,
|
database: database,
|
||||||
filesEngine: filesEngine,
|
filesEngine: filesEngine,
|
||||||
|
ginProcessor: processor.NewGinProcessor(database, dispatcher),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Built-in auth component of DrainCloud-Core
|
// Built-in auth component of DrainCloud-Core
|
||||||
authGroup := mux.Group("/auth")
|
authGroup := mux.Group("/auth")
|
||||||
{
|
{
|
||||||
// authGroup.POST("/register", d.Register)
|
// authGroup.POST("/register", d.Register)
|
||||||
authGroup.POST("/register", d.ginProcessor.Process(&handler.Handler{}))
|
authGroup.POST("/register", d.ginProcessor.Process(
|
||||||
|
handlers.NewRegisterHandler(database),
|
||||||
|
))
|
||||||
authGroup.POST("/logon", d.Login)
|
authGroup.POST("/logon", d.Login)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
37
internal/app/handlers/auth.go
Normal file
37
internal/app/handlers/auth.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
csrfTokenCookie = "__Csrf_token"
|
||||||
|
sessionTokenCookie = "__Session_token"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrorUnauthorized = errors.New("unauthorized")
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
120
internal/app/handlers/register.go
Normal file
120
internal/app/handlers/register.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/domain"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterHandler struct {
|
||||||
|
*handler.BaseHandler
|
||||||
|
authStorage storage.AuthStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegisterHandler(
|
||||||
|
authStorage storage.AuthStorage,
|
||||||
|
) *RegisterHandler {
|
||||||
|
h := &RegisterHandler{
|
||||||
|
authStorage: authStorage,
|
||||||
|
BaseHandler: handler.New().
|
||||||
|
WithName("registerv1").
|
||||||
|
WithRequiredResolveParams(),
|
||||||
|
}
|
||||||
|
h.WithProcessFunc(h.process)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RegisterHandler) process(ctx context.Context, req *common.Request, w handler.Writer) error {
|
||||||
|
regReq := new(domain.RegisterRequest)
|
||||||
|
|
||||||
|
if err := json.Unmarshal(req.Body, regReq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.register(ctx, regReq, w)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to register user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ctx, resp)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *RegisterHandler) register(
|
||||||
|
ctx context.Context,
|
||||||
|
req *domain.RegisterRequest,
|
||||||
|
w handler.Writer,
|
||||||
|
) (*domain.RegisterResponse, 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 hash", logger.Err(err))
|
||||||
|
return nil, fmt.Errorf("failed to generate password hash: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate user id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
ID: userID,
|
||||||
|
Username: req.Login,
|
||||||
|
Login: req.Login,
|
||||||
|
PasswordHash: passwordHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = d.authStorage.AddUser(ctx, userID, user.Login, user.Username, user.PasswordHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add new user: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCreatedAt := time.Now()
|
||||||
|
sessionExpiredAt := sessionCreatedAt.Add(time.Hour * 24 * 7)
|
||||||
|
|
||||||
|
sessionToken, err := generateSessionToken(100)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate a session token: %w", err)
|
||||||
|
}
|
||||||
|
w.SetCookie(sessionTokenCookie, sessionToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, true)
|
||||||
|
|
||||||
|
csrfToken, err := generateSessionToken(100)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate a csrf token: %w", err)
|
||||||
|
}
|
||||||
|
w.SetCookie(csrfTokenCookie, csrfToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, false)
|
||||||
|
|
||||||
|
sessionID, err := uuid.NewV7()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate session id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = d.authStorage.AddSession(ctx, &models.Session{
|
||||||
|
ID: sessionID,
|
||||||
|
SessionToken: sessionToken,
|
||||||
|
CsrfToken: csrfToken,
|
||||||
|
UserID: user.ID,
|
||||||
|
CreatedAt: sessionCreatedAt,
|
||||||
|
ExpiredAt: sessionExpiredAt,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to save session: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &domain.RegisterResponse{
|
||||||
|
Ok: true,
|
||||||
|
}, nil
|
||||||
|
}
|
@ -1,10 +1,13 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@ -67,6 +70,12 @@ func NewRequestFromHttp(pool *RequestPool, req *http.Request) *Request {
|
|||||||
out.Metadata[hname] = hval
|
out.Metadata[hname] = hval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(context.TODO(), "failed to read request body", logger.Err(err))
|
||||||
|
}
|
||||||
|
out.Body = body
|
||||||
|
|
||||||
reqID := uuid.NewString()
|
reqID := uuid.NewString()
|
||||||
out.ID = reqID
|
out.ID = reqID
|
||||||
return out
|
return out
|
||||||
|
@ -8,10 +8,48 @@ import (
|
|||||||
|
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(ctx context.Context, resp any)
|
Write(ctx context.Context, resp any)
|
||||||
|
SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Handler struct {
|
type Handler interface {
|
||||||
|
GetName() string
|
||||||
|
GetRequiredResolveParams() []string
|
||||||
|
GetProcessFn() func(ctx context.Context, req *common.Request, w Writer) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseHandler struct {
|
||||||
Name string
|
Name string
|
||||||
RequiredResolveParams []string
|
RequiredResolveParams []string
|
||||||
ProcessFn func(ctx context.Context, req *common.Request, w Writer) error
|
ProcessFn func(ctx context.Context, req *common.Request, w Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func New() *BaseHandler {
|
||||||
|
return new(BaseHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) WithName(name string) *BaseHandler {
|
||||||
|
h.Name = name
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) WithRequiredResolveParams(params ...string) *BaseHandler {
|
||||||
|
h.RequiredResolveParams = params
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) WithProcessFunc(fn func(ctx context.Context, req *common.Request, w Writer) error) *BaseHandler {
|
||||||
|
h.ProcessFn = fn
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) GetName() string {
|
||||||
|
return h.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) GetRequiredResolveParams() []string {
|
||||||
|
return h.RequiredResolveParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) GetProcessFn() func(ctx context.Context, req *common.Request, w Writer) error {
|
||||||
|
return h.ProcessFn
|
||||||
|
}
|
||||||
|
@ -34,9 +34,9 @@ func NewGinProcessor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GinProcessor) Process(handler *handler.Handler) gin.HandlerFunc {
|
func (p *GinProcessor) Process(handler handler.Handler) gin.HandlerFunc {
|
||||||
return func(ctx *gin.Context) {
|
return func(ctx *gin.Context) {
|
||||||
req := p.rp.Get()
|
req := common.NewRequestFromHttp(p.rp, ctx.Request)
|
||||||
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "__request_id", req.ID))
|
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "__request_id", req.ID))
|
||||||
|
|
||||||
// 1. Resolve the resolvers, collect all data required
|
// 1. Resolve the resolvers, collect all data required
|
||||||
@ -50,16 +50,16 @@ func (p *GinProcessor) Process(handler *handler.Handler) gin.HandlerFunc {
|
|||||||
// 3. Call preprocessinf fn's, middlewares etc.
|
// 3. Call preprocessinf fn's, middlewares etc.
|
||||||
// ....
|
// ....
|
||||||
// 4. Call handler.ProcessFn
|
// 4. Call handler.ProcessFn
|
||||||
if err = handler.ProcessFn(ctx, req, wrapGin(ctx)); err != nil {
|
if err = handler.GetProcessFn()(ctx, req, wrapGin(ctx)); err != nil {
|
||||||
p.writeError(ctx, err)
|
p.writeError(ctx, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *GinProcessor) resolve(ctx context.Context, h *handler.Handler, req *common.Request) error {
|
func (p *GinProcessor) resolve(ctx context.Context, h handler.Handler, req *common.Request) error {
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, r := range h.RequiredResolveParams {
|
for _, r := range h.GetRequiredResolveParams() {
|
||||||
resolver, err := p.resolveDispatcher.GetResolver(r)
|
resolver, err := p.resolveDispatcher.GetResolver(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to resolve '%s' param: no resolver provided: %w", r, err)
|
return fmt.Errorf("failed to resolve '%s' param: no resolver provided: %w", r, err)
|
||||||
|
@ -20,3 +20,7 @@ func wrapGin(ctx *gin.Context) ginWriter {
|
|||||||
func (w ginWriter) Write(ctx context.Context, resp any) {
|
func (w ginWriter) Write(ctx context.Context, resp any) {
|
||||||
w.ctx.JSON(http.StatusOK, resp)
|
w.ctx.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w ginWriter) SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) {
|
||||||
|
w.ctx.SetCookie(name, value, maxAge, path, domain, secure, httpOnly)
|
||||||
|
}
|
||||||
|
@ -3,5 +3,5 @@ package processor
|
|||||||
import "git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
import "git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
||||||
|
|
||||||
type Processor[H any] interface {
|
type Processor[H any] interface {
|
||||||
Process(*handler.Handler) H
|
Process(handler.Handler) H
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ func (d *AuthResolver) getSession(ctx context.Context, req *common.Request) (*mo
|
|||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSession(ctx context.Context, req *common.Request, session *models.Session) error {
|
func validateSession(_ context.Context, req *common.Request, session *models.Session) error {
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return errs.ErrorAccessDenied
|
return errs.ErrorAccessDenied
|
||||||
}
|
}
|
||||||
|
7
internal/resolvers/ip/ip.go
Normal file
7
internal/resolvers/ip/ip.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package ip
|
||||||
|
|
||||||
|
type IpResolver struct{}
|
||||||
|
|
||||||
|
func New() *IpResolver {
|
||||||
|
return new(IpResolver)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user