handlers moved to new system, upload need to be fixed and resolvers must be tested
This commit is contained in:
parent
7ce2b41ce7
commit
87985762bf
@ -26,7 +26,7 @@ func main() {
|
|||||||
|
|
||||||
engine := filesengine.NewFilesEngine(nil, nil)
|
engine := filesengine.NewFilesEngine(nil, nil)
|
||||||
|
|
||||||
go app.New(pg, engine).
|
go app.New(ctx, pg, engine).
|
||||||
Run(ctx)
|
Run(ctx)
|
||||||
|
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
@ -2,14 +2,12 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/app/handlers"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/app/handlers"
|
||||||
"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/processor"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/processor"
|
||||||
resolvedispatcher "git.optclblast.xyz/draincloud/draincloud-core/internal/resolve_dispatcher"
|
resolvedispatcher "git.optclblast.xyz/draincloud/draincloud-core/internal/resolve_dispatcher"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/resolvers/auth"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
@ -23,12 +21,18 @@ type DrainCloud struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
|
ctx context.Context,
|
||||||
database storage.Database,
|
database storage.Database,
|
||||||
filesEngine *filesengine.FilesEngine,
|
filesEngine *filesengine.FilesEngine,
|
||||||
) *DrainCloud {
|
) *DrainCloud {
|
||||||
mux := gin.Default()
|
mux := gin.Default()
|
||||||
|
|
||||||
dispatcher := resolvedispatcher.New()
|
dispatcher := resolvedispatcher.New()
|
||||||
|
dispatcher.RegisterResolver(
|
||||||
|
ctx,
|
||||||
|
auth.AuthResolverV1Name,
|
||||||
|
auth.NewAuthResolver(database),
|
||||||
|
)
|
||||||
|
|
||||||
d := &DrainCloud{
|
d := &DrainCloud{
|
||||||
database: database,
|
database: database,
|
||||||
@ -39,16 +43,19 @@ func New(
|
|||||||
// 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.ginProcessor.Process(
|
authGroup.POST("/register", d.ginProcessor.Process(
|
||||||
handlers.NewRegisterHandler(database),
|
handlers.NewRegisterHandler(database),
|
||||||
))
|
))
|
||||||
authGroup.POST("/logon", d.Login)
|
authGroup.POST("/logon", d.ginProcessor.Process(
|
||||||
|
handlers.NewLogonHandler(database),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
filesGroup := mux.Group("/files")
|
filesGroup := mux.Group("/files")
|
||||||
{
|
{
|
||||||
filesGroup.POST("/upload", d.UploadFile)
|
filesGroup.POST("/upload", d.ginProcessor.Process(
|
||||||
|
handlers.NewUploadFileHandler(filesEngine),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
d.mux = mux
|
d.mux = mux
|
||||||
@ -59,23 +66,3 @@ func New(
|
|||||||
func (d *DrainCloud) Run(ctx context.Context) error {
|
func (d *DrainCloud) Run(ctx context.Context) error {
|
||||||
return d.mux.Run()
|
return d.mux.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeError(ctx *gin.Context, err error) {
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, ErrorAccessDenied):
|
|
||||||
ctx.JSON(http.StatusInternalServerError, domain.ErrorJson{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
case errors.Is(err, ErrorSessionExpired):
|
|
||||||
ctx.JSON(http.StatusInternalServerError, domain.ErrorJson{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
ctx.JSON(http.StatusInternalServerError, domain.ErrorJson{
|
|
||||||
Code: http.StatusInternalServerError,
|
|
||||||
Message: "Internal Error",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
csrfTokenCookie = "__Csrf_token"
|
|
||||||
sessionTokenCookie = "__Session_token"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrorUnauthorized = errors.New("unauthorized")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DrainCloud) authorize(ctx *gin.Context) (*models.Session, error) {
|
|
||||||
session, err := d.getSession(ctx)
|
|
||||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
|
||||||
return nil, ErrorUnauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
if session == nil {
|
|
||||||
return nil, ErrorUnauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateSession(ctx, session); err != nil {
|
|
||||||
// TODO add audit log entry
|
|
||||||
return nil, ErrorUnauthorized
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Debug(ctx, "[authorize] user authorized", slog.String("session_id", session.ID.String()))
|
|
||||||
|
|
||||||
return session, 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
|
|
||||||
}
|
|
@ -1,84 +1,100 @@
|
|||||||
package app
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/domain"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/domain"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/errs"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
"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"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type LogonHandler struct {
|
||||||
ErrorAccessDenied = errors.New("access denied")
|
*handler.BaseHandler
|
||||||
ErrorSessionExpired = errors.New("session expired")
|
authStorage storage.AuthStorage
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DrainCloud) Login(ctx *gin.Context) {
|
|
||||||
logger.Debug(ctx, "[Login] new request")
|
|
||||||
|
|
||||||
req := new(domain.LoginRequest)
|
|
||||||
err := ctx.BindJSON(req)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(ctx, "[Login] failed to bind request", logger.Err(err))
|
|
||||||
ctx.JSON(http.StatusBadRequest, map[string]string{
|
|
||||||
"error": "bad request",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := d.login(ctx, req)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error(ctx, "[Login] failed to login user", logger.Err(err))
|
|
||||||
ctx.JSON(http.StatusInternalServerError, map[string]string{
|
|
||||||
"error": err.Error(),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DrainCloud) login(ctx *gin.Context, req *domain.LoginRequest) (*domain.LoginResponse, error) {
|
func NewLogonHandler(
|
||||||
session, err := d.getSession(ctx)
|
authStorage storage.AuthStorage,
|
||||||
|
) *LogonHandler {
|
||||||
|
h := &LogonHandler{
|
||||||
|
authStorage: authStorage,
|
||||||
|
BaseHandler: handler.New().
|
||||||
|
WithName("logonv1").
|
||||||
|
WithRequiredResolveParams(),
|
||||||
|
}
|
||||||
|
h.WithProcessFunc(h.process)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LogonHandler) process(ctx context.Context, req *common.Request, w handler.Writer) error {
|
||||||
|
logger.Debug(ctx, "[Logon] new request")
|
||||||
|
|
||||||
|
body := new(domain.LogonRequest)
|
||||||
|
err := json.Unmarshal(req.Body, body)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "[Logon] failed to bind request", logger.Err(err))
|
||||||
|
w.Write(ctx, map[string]string{
|
||||||
|
"error": "bad request",
|
||||||
|
}, handler.WithCode(http.StatusBadRequest))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := h.getSession(ctx, req)
|
||||||
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
if err != nil && !errors.Is(err, http.ErrNoCookie) {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if session != nil {
|
if session != nil {
|
||||||
if err := validateSession(ctx, session); err != nil {
|
if err := validateSession(ctx, req, session); err != nil {
|
||||||
// TODO add audit log entry
|
// TODO add audit log entry
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug(ctx, "[login] user is already logged in", slog.String("session_id", session.ID.String()))
|
logger.Debug(ctx, "[login] user is already logged in", slog.String("session_id", session.ID.String()))
|
||||||
return &domain.LoginResponse{
|
w.Write(ctx, &domain.LogonResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
}, nil
|
})
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
logger.Debug(ctx, "[login] session not found. trying to authorize")
|
logger.Debug(ctx, "[login] session not founh. trying to authorize")
|
||||||
|
|
||||||
|
resp, err := h.login(ctx, body, session, w)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "[Logon] failed to login user", logger.Err(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(ctx, resp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *LogonHandler) login(ctx context.Context, req *domain.LogonRequest, session *models.Session, w handler.Writer) (*domain.LogonResponse, error) {
|
||||||
passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
|
passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(ctx, "[login] failed to generate password hash", logger.Err(err))
|
logger.Error(ctx, "[login] failed to generate password hash", logger.Err(err))
|
||||||
return nil, fmt.Errorf("failed to generate password hash: %w", err)
|
return nil, fmt.Errorf("failed to generate password hash: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := d.database.GetUserByLogin(ctx, req.Login)
|
user, err := h.authStorage.GetUserByLogin(ctx, req.Login)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch user by login: %w", err)
|
return nil, fmt.Errorf("failed to fetch user by login: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Equal(passwordHash, user.PasswordHash) {
|
if bytes.Equal(passwordHash, user.PasswordHash) {
|
||||||
logger.Warn(ctx, "[login] failed to login user. passwords hashes not equal")
|
logger.Warn(ctx, "[login] failed to login user. passwords hashes not equal")
|
||||||
return nil, ErrorAccessDenied
|
return nil, errs.ErrorAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionCreatedAt := time.Now()
|
sessionCreatedAt := time.Now()
|
||||||
@ -88,20 +104,20 @@ func (d *DrainCloud) login(ctx *gin.Context, req *domain.LoginRequest) (*domain.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate a session token: %w", err)
|
return nil, fmt.Errorf("failed to generate a session token: %w", err)
|
||||||
}
|
}
|
||||||
ctx.SetCookie(sessionTokenCookie, sessionToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, true)
|
w.SetCookie(sessionTokenCookie, sessionToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, true)
|
||||||
|
|
||||||
csrfToken, err := generateSessionToken(100)
|
csrfToken, err := generateSessionToken(100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate a csrf token: %w", err)
|
return nil, fmt.Errorf("failed to generate a csrf token: %w", err)
|
||||||
}
|
}
|
||||||
ctx.SetCookie(csrfTokenCookie, csrfToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, false)
|
w.SetCookie(csrfTokenCookie, csrfToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, false)
|
||||||
|
|
||||||
sessionID, err := uuid.NewV7()
|
sessionID, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate session id: %w", err)
|
return nil, fmt.Errorf("failed to generate session id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = d.database.AddSession(ctx, &models.Session{
|
if _, err = h.authStorage.AddSession(ctx, &models.Session{
|
||||||
ID: sessionID,
|
ID: sessionID,
|
||||||
SessionToken: sessionToken,
|
SessionToken: sessionToken,
|
||||||
CsrfToken: csrfToken,
|
CsrfToken: csrfToken,
|
||||||
@ -114,17 +130,17 @@ func (d *DrainCloud) login(ctx *gin.Context, req *domain.LoginRequest) (*domain.
|
|||||||
|
|
||||||
// TODO add audit log entry
|
// TODO add audit log entry
|
||||||
|
|
||||||
return &domain.LoginResponse{
|
return &domain.LogonResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DrainCloud) getSession(ctx *gin.Context) (*models.Session, error) {
|
func (h *LogonHandler) getSession(ctx context.Context, req *common.Request) (*models.Session, error) {
|
||||||
token, err := ctx.Cookie(sessionTokenCookie)
|
token, err := common.GetValue[string](req.Metadata, sessionTokenCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch session cookie from request: %w", err)
|
return nil, fmt.Errorf("failed to fetch session cookie from request: %w", err)
|
||||||
}
|
}
|
||||||
csrfToken, err := ctx.Cookie(csrfTokenCookie)
|
csrfToken, err := common.GetValue[string](req.Metadata, csrfTokenCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
|
return nil, fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
|
||||||
}
|
}
|
||||||
@ -133,7 +149,7 @@ func (d *DrainCloud) getSession(ctx *gin.Context) (*models.Session, error) {
|
|||||||
return nil, fmt.Errorf("session token or csrf token is empty")
|
return nil, fmt.Errorf("session token or csrf token is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := d.database.GetSession(ctx, token)
|
session, err := h.authStorage.GetSession(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch session from repo: %w", err)
|
return nil, fmt.Errorf("failed to fetch session from repo: %w", err)
|
||||||
}
|
}
|
||||||
@ -141,22 +157,22 @@ func (d *DrainCloud) getSession(ctx *gin.Context) (*models.Session, error) {
|
|||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSession(ctx *gin.Context, session *models.Session) error {
|
func validateSession(ctx context.Context, req *common.Request, session *models.Session) error {
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return ErrorAccessDenied
|
return errs.ErrorAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
csrfToken, err := ctx.Cookie(csrfTokenCookie)
|
csrfToken, err := common.GetValue[string](req.Metadata, csrfTokenCookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
|
return fmt.Errorf("failed to fetch csrf cookie from request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.CsrfToken != csrfToken {
|
if session.CsrfToken != csrfToken {
|
||||||
return ErrorAccessDenied
|
return errs.ErrorAccessDenied
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.ExpiredAt.Before(time.Now()) {
|
if session.ExpiredAt.Before(time.Now()) {
|
||||||
return ErrorSessionExpired
|
return errs.ErrorSessionExpired
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
91
internal/app/handlers/upload_file.go
Normal file
91
internal/app/handlers/upload_file.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
||||||
|
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/logger"
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/resolvers/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxFileSize = 10 << 30
|
||||||
|
)
|
||||||
|
|
||||||
|
type UploadFileHandler struct {
|
||||||
|
*handler.BaseHandler
|
||||||
|
filesEngine *filesengine.FilesEngine
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUploadFileHandler(
|
||||||
|
filesEngine *filesengine.FilesEngine,
|
||||||
|
) *UploadFileHandler {
|
||||||
|
h := &UploadFileHandler{
|
||||||
|
filesEngine: filesEngine,
|
||||||
|
BaseHandler: handler.New().
|
||||||
|
WithName("uploadfilev1").
|
||||||
|
WithRequiredResolveParams(
|
||||||
|
auth.AuthResolverV1Name,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
h.WithProcessFunc(h.process)
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *UploadFileHandler) process(ctx context.Context, req *common.Request, w handler.Writer) error {
|
||||||
|
if err := req.RawReq.ParseMultipartForm(maxFileSize); err != nil {
|
||||||
|
logger.Error(ctx, "uploadFile handler error", logger.Err(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if err := d.uploadFile(ctx, userID); err != nil {
|
||||||
|
// logger.Error(ctx, "uploadFile handle", logger.Err(err))
|
||||||
|
// writeError(ctx, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (d *UploadFileHandler) uploadFile(ctx context.Context, req *common.Request) error {
|
||||||
|
// title := ctx.PostForm("file")
|
||||||
|
// logger.Info(ctx, "uploadFile", slog.Any("postForm data", spew.Sdump(title)))
|
||||||
|
|
||||||
|
// file, header, err := req.RawReq.FormFile("file")
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// logger.Info(ctx, "uploadFile", slog.Any("header", spew.Sdump(header)))
|
||||||
|
|
||||||
|
// data, err := io.ReadAll(file)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ext := parseExtension(header.Filename)
|
||||||
|
|
||||||
|
// id, err := d.filesEngine.SaveFile(ctx, filesengine.File{
|
||||||
|
// Name: header.Filename,
|
||||||
|
// UserID: userID,
|
||||||
|
// Data: data,
|
||||||
|
// Ext: ext,
|
||||||
|
// Size: int64(len(data)),
|
||||||
|
// Type: "", // че такое type?
|
||||||
|
// })
|
||||||
|
// if err != nil {
|
||||||
|
// return fmt.Errorf("failed to save file: %w", err)
|
||||||
|
// }
|
||||||
|
// logger.Debug(ctx, "new file id", "id", id)
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func parseExtension(filename string) string {
|
||||||
|
// parts := strings.Split(filename, ".")
|
||||||
|
// if len(parts) == 0 {
|
||||||
|
// return ""
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return parts[len(parts)-1]
|
||||||
|
// }
|
@ -1,28 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/reqcontext"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type authorizer interface {
|
|
||||||
authorize(ctx *gin.Context) (*models.Session, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithAuth(handler gin.HandlerFunc, auth authorizer) gin.HandlerFunc {
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
sess, err := auth.authorize(ctx)
|
|
||||||
if err != nil {
|
|
||||||
writeError(ctx, err)
|
|
||||||
ctx.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authCtx := reqcontext.WithSession(ctx.Request.Context(), sess)
|
|
||||||
authCtx = reqcontext.WithUserID(authCtx, sess.UserID)
|
|
||||||
ctx.Request = ctx.Request.WithContext(authCtx)
|
|
||||||
|
|
||||||
handler(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
filesengine "git.optclblast.xyz/draincloud/draincloud-core/internal/files_engine"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/reqcontext"
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxFileSize = 10 << 30
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DrainCloud) UploadFile(ctx *gin.Context) {
|
|
||||||
if err := ctx.Request.ParseMultipartForm(maxFileSize); err != nil {
|
|
||||||
logger.Error(ctx, "uploadFile handler error", logger.Err(err))
|
|
||||||
writeError(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, err := reqcontext.GetUserID(ctx)
|
|
||||||
if err != nil {
|
|
||||||
writeError(ctx, ErrorAccessDenied)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.uploadFile(ctx, userID); err != nil {
|
|
||||||
logger.Error(ctx, "uploadFile handle", logger.Err(err))
|
|
||||||
writeError(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DrainCloud) uploadFile(ctx *gin.Context, userID uuid.UUID) error {
|
|
||||||
title := ctx.PostForm("file")
|
|
||||||
logger.Info(ctx, "uploadFile", slog.Any("postForm data", spew.Sdump(title)))
|
|
||||||
|
|
||||||
file, header, err := ctx.Request.FormFile("file")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logger.Info(ctx, "uploadFile", slog.Any("header", spew.Sdump(header)))
|
|
||||||
|
|
||||||
data, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := parseExtension(header.Filename)
|
|
||||||
|
|
||||||
id, err := d.filesEngine.SaveFile(ctx, filesengine.File{
|
|
||||||
Name: header.Filename,
|
|
||||||
UserID: userID,
|
|
||||||
Data: data,
|
|
||||||
Ext: ext,
|
|
||||||
Size: int64(len(data)),
|
|
||||||
Type: "", // че такое type?
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to save file: %w", err)
|
|
||||||
}
|
|
||||||
logger.Debug(ctx, "new file id", "id", id)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseExtension(filename string) string {
|
|
||||||
parts := strings.Split(filename, ".")
|
|
||||||
if len(parts) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts[len(parts)-1]
|
|
||||||
}
|
|
@ -10,12 +10,12 @@ type RegisterResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LogonRequest struct {
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginResponse struct {
|
type LogonResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,20 @@ import (
|
|||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type WriteOptions struct {
|
||||||
|
Code int
|
||||||
|
}
|
||||||
|
|
||||||
|
type WriteOption func(opts *WriteOptions)
|
||||||
|
|
||||||
|
func WithCode(code int) WriteOption {
|
||||||
|
return func(opts *WriteOptions) {
|
||||||
|
opts.Code = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Writer interface {
|
type Writer interface {
|
||||||
Write(ctx context.Context, resp any)
|
Write(ctx context.Context, resp any, opts ...WriteOption)
|
||||||
SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
|
SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,8 +18,15 @@ func wrapGin(ctx *gin.Context) ginWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w ginWriter) Write(ctx context.Context, resp any) {
|
func (w ginWriter) Write(ctx context.Context, resp any, opts ...handler.WriteOption) {
|
||||||
w.ctx.JSON(http.StatusOK, resp)
|
params := &handler.WriteOptions{
|
||||||
|
Code: http.StatusOK,
|
||||||
|
}
|
||||||
|
for _, o := range opts {
|
||||||
|
o(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.ctx.JSON(params.Code, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w ginWriter) SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) {
|
func (w ginWriter) SetCookie(name string, value string, maxAge int, path string, domain string, secure bool, httpOnly bool) {
|
||||||
|
@ -15,6 +15,10 @@ import (
|
|||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthResolverV1Name = "auth.v1"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
csrfTokenCookie = "__Csrf_token"
|
csrfTokenCookie = "__Csrf_token"
|
||||||
sessionTokenCookie = "__Session_token"
|
sessionTokenCookie = "__Session_token"
|
||||||
@ -24,6 +28,12 @@ type AuthResolver struct {
|
|||||||
authStorage storage.AuthStorage
|
authStorage storage.AuthStorage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAuthResolver(authStorage storage.AuthStorage) *AuthResolver {
|
||||||
|
return &AuthResolver{
|
||||||
|
authStorage: authStorage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (r *AuthResolver) Resolve(ctx context.Context, req *common.Request) error {
|
func (r *AuthResolver) Resolve(ctx context.Context, req *common.Request) error {
|
||||||
return r.authorize(ctx, req)
|
return r.authorize(ctx, req)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user