Compare commits
No commits in common. "7ce2b41ce7bcece5a1661da4ce7a29edc47e24d2" and "640255357ea62ea6c6721ecc4744381ed1b23c39" have entirely different histories.
7ce2b41ce7
...
640255357e
1
go.mod
1
go.mod
@ -14,7 +14,6 @@ require (
|
|||||||
github.com/spf13/viper v1.19.0
|
github.com/spf13/viper v1.19.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
golang.org/x/sync v0.8.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -5,11 +5,8 @@ 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/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"
|
||||||
)
|
)
|
||||||
@ -18,8 +15,6 @@ type DrainCloud struct {
|
|||||||
mux *gin.Engine
|
mux *gin.Engine
|
||||||
database storage.Database
|
database storage.Database
|
||||||
filesEngine *filesengine.FilesEngine
|
filesEngine *filesengine.FilesEngine
|
||||||
|
|
||||||
ginProcessor processor.Processor[gin.HandlerFunc]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@ -28,21 +23,15 @@ 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(
|
|
||||||
handlers.NewRegisterHandler(database),
|
|
||||||
))
|
|
||||||
authGroup.POST("/logon", d.Login)
|
authGroup.POST("/logon", d.Login)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,61 +1,46 @@
|
|||||||
package handlers
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"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/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegisterHandler struct {
|
func (d *DrainCloud) Register(ctx *gin.Context) {
|
||||||
*handler.BaseHandler
|
logger.Debug(ctx, "[register] new request")
|
||||||
authStorage storage.AuthStorage
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegisterHandler(
|
// TODO check if registration is enabled
|
||||||
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 {
|
req := new(domain.RegisterRequest)
|
||||||
regReq := new(domain.RegisterRequest)
|
err := ctx.BindJSON(req)
|
||||||
|
|
||||||
if err := json.Unmarshal(req.Body, regReq); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := h.register(ctx, regReq, w)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to register user: %w", err)
|
logger.Error(ctx, "[register] failed to bind request", logger.Err(err))
|
||||||
|
ctx.JSON(http.StatusBadRequest, map[string]string{
|
||||||
|
"error": "bad request",
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write(ctx, resp)
|
resp, err := d.register(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(ctx, "[register] failed to register user", logger.Err(err))
|
||||||
|
ctx.JSON(http.StatusInternalServerError, map[string]string{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
ctx.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RegisterHandler) register(
|
func (d *DrainCloud) register(ctx *gin.Context, req *domain.RegisterRequest) (*domain.RegisterResponse, error) {
|
||||||
ctx context.Context,
|
|
||||||
req *domain.RegisterRequest,
|
|
||||||
w handler.Writer,
|
|
||||||
) (*domain.RegisterResponse, error) {
|
|
||||||
if err := validateLoginAndPassword(req.Login, req.Password); err != nil {
|
if err := validateLoginAndPassword(req.Login, req.Password); err != nil {
|
||||||
return nil, fmt.Errorf("invalid creds: %w", err)
|
return nil, fmt.Errorf("invalid creds: %w", err)
|
||||||
}
|
}
|
||||||
@ -78,7 +63,7 @@ func (d *RegisterHandler) register(
|
|||||||
PasswordHash: passwordHash,
|
PasswordHash: passwordHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = d.authStorage.AddUser(ctx, userID, user.Login, user.Username, user.PasswordHash)
|
err = d.database.AddUser(ctx, userID, user.Login, user.Username, user.PasswordHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to add new user: %w", err)
|
return nil, fmt.Errorf("failed to add new user: %w", err)
|
||||||
}
|
}
|
||||||
@ -90,20 +75,20 @@ func (d *RegisterHandler) register(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
w.SetCookie(sessionTokenCookie, sessionToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, true)
|
ctx.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)
|
||||||
}
|
}
|
||||||
w.SetCookie(csrfTokenCookie, csrfToken, int(sessionExpiredAt.Sub(sessionCreatedAt).Seconds()), "_path", "_domain", true, false)
|
ctx.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.authStorage.AddSession(ctx, &models.Session{
|
if _, err = d.database.AddSession(ctx, &models.Session{
|
||||||
ID: sessionID,
|
ID: sessionID,
|
||||||
SessionToken: sessionToken,
|
SessionToken: sessionToken,
|
||||||
CsrfToken: csrfToken,
|
CsrfToken: csrfToken,
|
||||||
@ -118,4 +103,3 @@ func (d *RegisterHandler) register(
|
|||||||
Ok: true,
|
Ok: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,10 @@
|
|||||||
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"
|
||||||
)
|
)
|
||||||
@ -16,82 +13,43 @@ type RequestPool struct {
|
|||||||
sp sync.Pool
|
sp sync.Pool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RequestPool) Get() *Request {
|
|
||||||
r, _ := p.sp.Get().(*Request)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *RequestPool) Put(r *Request) {
|
|
||||||
r.ID = ""
|
|
||||||
r.Metadata = &sync.Map{}
|
|
||||||
r.ResolveValues = &sync.Map{}
|
|
||||||
r.Session = nil
|
|
||||||
r.User = nil
|
|
||||||
r.Body = nil
|
|
||||||
p.sp.Put(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRequestPool() *RequestPool {
|
|
||||||
return &RequestPool{
|
|
||||||
sp: sync.Pool{
|
|
||||||
New: func() any {
|
|
||||||
return &Request{
|
|
||||||
ResolveValues: &sync.Map{},
|
|
||||||
Metadata: &sync.Map{},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Request struct {
|
type Request struct {
|
||||||
ID string
|
ID string
|
||||||
Session *models.Session
|
Session *models.Session
|
||||||
User *models.User
|
User *models.User
|
||||||
// ResolveValues - data required to process request.
|
ResolveValues sync.Map
|
||||||
ResolveValues *sync.Map
|
Metadata map[string]any
|
||||||
// Metadata - an additional data, usually added with preprocessing.
|
|
||||||
Metadata *sync.Map
|
|
||||||
// Request body
|
|
||||||
Body []byte
|
Body []byte
|
||||||
RawReq *http.Request
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestFromHttp builds a new *Request struct from raw http Request. No auth data validated.
|
// NewRequest builds a new *Request struct from raw http Request. No auth data validated.
|
||||||
func NewRequestFromHttp(pool *RequestPool, req *http.Request) *Request {
|
func NewRequest(pool *RequestPool, req *http.Request) *Request {
|
||||||
out := pool.sp.Get().(*Request)
|
out := pool.sp.Get().(*Request)
|
||||||
|
|
||||||
cookies := req.Cookies()
|
cookies := req.Cookies()
|
||||||
headers := req.Header
|
headers := req.Header
|
||||||
|
|
||||||
out.Metadata = &sync.Map{}
|
out.Metadata = make(map[string]any, len(cookies))
|
||||||
out.RawReq = req
|
|
||||||
|
|
||||||
for _, cookie := range cookies {
|
for _, cookie := range cookies {
|
||||||
out.Metadata.Store(cookie.Name, cookie.Value)
|
out.Metadata[cookie.Name] = cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
for hname, hval := range headers {
|
for hname, hval := range headers {
|
||||||
out.Metadata.Store(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
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetValue[T any](vals *sync.Map, key string) (T, error) {
|
func GetValue[T any](vals map[string]any, key string) (T, error) {
|
||||||
var out T
|
var out T
|
||||||
if vals == nil {
|
if vals == nil {
|
||||||
return out, fmt.Errorf("nil vals map")
|
return out, fmt.Errorf("nil vals map")
|
||||||
}
|
}
|
||||||
rawVal, ok := vals.Load(key)
|
rawVal, ok := vals[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return out, fmt.Errorf("value not found in resolve values set")
|
return out, fmt.Errorf("value not found in resolve values set")
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package common
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -66,7 +65,7 @@ func TestGetValue_string(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := GetValue[string](_mapToSyncMap(tt.args.vals), tt.args.key)
|
got, err := GetValue[string](tt.args.vals, tt.args.key)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -152,7 +151,7 @@ func TestGetValue_struct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := GetValue[val](_mapToSyncMap(tt.args.vals), tt.args.key)
|
got, err := GetValue[val](tt.args.vals, tt.args.key)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -238,7 +237,7 @@ func TestGetValue_structptr(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := GetValue[*val](_mapToSyncMap(tt.args.vals), tt.args.key)
|
got, err := GetValue[*val](tt.args.vals, tt.args.key)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("GetValue() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -249,13 +248,3 @@ func TestGetValue_structptr(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _mapToSyncMap(m map[string]any) *sync.Map {
|
|
||||||
out := &sync.Map{}
|
|
||||||
|
|
||||||
for k, v := range m {
|
|
||||||
out.Store(k, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
@ -8,59 +8,10 @@ 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 interface {
|
type Handler struct {
|
||||||
GetName() string
|
|
||||||
GetRequiredResolveParams() []string
|
|
||||||
GetProcessFn() func(ctx context.Context, req *common.Request, w Writer) error
|
|
||||||
GetPreprocessFn() 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
|
||||||
PreprocessFn 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) WithPreprocessFunc(fn func(ctx context.Context, req *common.Request, w Writer) error) *BaseHandler {
|
|
||||||
h.PreprocessFn = 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BaseHandler) GetPreprocessFn() func(ctx context.Context, req *common.Request, w Writer) error {
|
|
||||||
return h.PreprocessFn
|
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
|
||||||
"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"
|
|
||||||
resolvedispatcher "git.optclblast.xyz/draincloud/draincloud-core/internal/resolve_dispatcher"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/storage"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GinProcessor struct {
|
|
||||||
rp *common.RequestPool
|
|
||||||
authStorage storage.AuthStorage
|
|
||||||
resolveDispatcher *resolvedispatcher.ResolveDispatcher
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGinProcessor(
|
|
||||||
authStorage storage.AuthStorage,
|
|
||||||
resolveDispatcher *resolvedispatcher.ResolveDispatcher,
|
|
||||||
) *GinProcessor {
|
|
||||||
return &GinProcessor{
|
|
||||||
rp: common.NewRequestPool(),
|
|
||||||
authStorage: authStorage,
|
|
||||||
resolveDispatcher: resolveDispatcher,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GinProcessor) Process(handler handler.Handler) gin.HandlerFunc {
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
req := common.NewRequestFromHttp(p.rp, ctx.Request)
|
|
||||||
ctx.Request = ctx.Request.WithContext(context.WithValue(ctx.Request.Context(), "__request_id", req.ID))
|
|
||||||
|
|
||||||
// 1. Resolve the resolvers, collect all data required
|
|
||||||
// 2. Try process oprional resolvers
|
|
||||||
err := p.resolve(ctx, handler, req)
|
|
||||||
if err != nil {
|
|
||||||
p.writeError(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Call preprocessing fn's, middlewares etc.
|
|
||||||
if err = handler.GetPreprocessFn()(ctx, req, wrapGin(ctx)); err != nil {
|
|
||||||
p.writeError(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Call handler.ProcessFn
|
|
||||||
if err = handler.GetProcessFn()(ctx, req, wrapGin(ctx)); err != nil {
|
|
||||||
p.writeError(ctx, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GinProcessor) resolve(ctx context.Context, h handler.Handler, req *common.Request) error {
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
for _, r := range h.GetRequiredResolveParams() {
|
|
||||||
resolver, err := p.resolveDispatcher.GetResolver(r)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to resolve '%s' param: no resolver provided: %w", r, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveValueName := r
|
|
||||||
eg.Go(func() error {
|
|
||||||
if resolveErr := resolver.Resolve(ctx, req); resolveErr != nil {
|
|
||||||
return fmt.Errorf("failed to resolve '%s' value: %w", resolveValueName, resolveErr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *GinProcessor) writeError(ctx *gin.Context, err error) {
|
|
||||||
logger.Error(ctx, "error process request", logger.Err(err))
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, errs.ErrorAccessDenied):
|
|
||||||
ctx.JSON(http.StatusInternalServerError, domain.ErrorJson{
|
|
||||||
Code: http.StatusForbidden,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
case errors.Is(err, errs.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,26 +0,0 @@
|
|||||||
package processor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ginWriter struct {
|
|
||||||
ctx *gin.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapGin(ctx *gin.Context) ginWriter {
|
|
||||||
return ginWriter{
|
|
||||||
ctx: ctx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w ginWriter) Write(ctx context.Context, resp any) {
|
|
||||||
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)
|
|
||||||
}
|
|
@ -1,7 +1,51 @@
|
|||||||
package processor
|
package processor
|
||||||
|
|
||||||
import "git.optclblast.xyz/draincloud/draincloud-core/internal/handler"
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
type Processor[H any] interface {
|
"git.optclblast.xyz/draincloud/draincloud-core/internal/common"
|
||||||
Process(handler.Handler) H
|
"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/storage"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Processor struct {
|
||||||
|
rp *common.RequestPool
|
||||||
|
authStorage storage.AuthStorage
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Processor) Process(handler *handler.Handler) gin.HandlerFunc {
|
||||||
|
return func(ctx *gin.Context) {
|
||||||
|
//req := common.NewRequest(p.rp, ctx.Request)
|
||||||
|
// if handler.WithAuth {
|
||||||
|
// if err := p.authorize(ctx, req); err != nil {
|
||||||
|
// p.writeError(ctx, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Processor) writeError(ctx *gin.Context, err error) {
|
||||||
|
switch {
|
||||||
|
case errors.Is(err, errs.ErrorAccessDenied):
|
||||||
|
ctx.JSON(http.StatusInternalServerError, domain.ErrorJson{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
case errors.Is(err, errs.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,48 +0,0 @@
|
|||||||
package resolvedispatcher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/logger"
|
|
||||||
"git.optclblast.xyz/draincloud/draincloud-core/internal/resolvers"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ResolveDispatcher struct {
|
|
||||||
m sync.RWMutex
|
|
||||||
router map[string]resolvers.Resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func New() *ResolveDispatcher {
|
|
||||||
return &ResolveDispatcher{
|
|
||||||
router: map[string]resolvers.Resolver{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResolveDispatcher) RegisterResolver(
|
|
||||||
ctx context.Context,
|
|
||||||
resolverName string,
|
|
||||||
resolver resolvers.Resolver,
|
|
||||||
) {
|
|
||||||
d.m.Lock()
|
|
||||||
defer d.m.Unlock()
|
|
||||||
|
|
||||||
if _, ok := d.router[resolverName]; ok {
|
|
||||||
logger.Fatal(ctx, fmt.Sprintf("resolver '%s' is already registered in router", resolverName))
|
|
||||||
}
|
|
||||||
|
|
||||||
d.router[resolverName] = resolver
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ResolveDispatcher) GetResolver(name string) (resolvers.Resolver, error) {
|
|
||||||
d.m.RLock()
|
|
||||||
defer d.m.RUnlock()
|
|
||||||
|
|
||||||
res, ok := d.router[name]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("resolver '%s' not found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
@ -73,7 +73,7 @@ func (d *AuthResolver) getSession(ctx context.Context, req *common.Request) (*mo
|
|||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSession(_ context.Context, req *common.Request, session *models.Session) error {
|
func validateSession(ctx context.Context, req *common.Request, session *models.Session) error {
|
||||||
if session == nil {
|
if session == nil {
|
||||||
return errs.ErrorAccessDenied
|
return errs.ErrorAccessDenied
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user