🎉 Initial Commit
Some checks failed
release-nightly / build-docker (push) Has been cancelled
Some checks failed
release-nightly / build-docker (push) Has been cancelled
This commit is contained in:
19
pkg/internal/server/api/directory.go
Normal file
19
pkg/internal/server/api/directory.go
Normal file
@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/directory"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func listExistsService(c *fiber.Ctx) error {
|
||||
services := directory.ListServiceInstance()
|
||||
|
||||
return c.JSON(lo.Map(services, func(item *directory.ServiceInstance, index int) map[string]any {
|
||||
return map[string]any{
|
||||
"id": item.ID,
|
||||
"type": item.Type,
|
||||
"label": item.Label,
|
||||
}
|
||||
}))
|
||||
}
|
28
pkg/internal/server/api/index.go
Normal file
28
pkg/internal/server/api/index.go
Normal file
@ -0,0 +1,28 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/server/exts"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func MapAPIs(app *fiber.App) {
|
||||
wellKnown := app.Group("/.well-known").Name("Well Known")
|
||||
{
|
||||
wellKnown.Get("/directory/services", listExistsService)
|
||||
}
|
||||
|
||||
api := app.Group("/api").Name("API")
|
||||
{
|
||||
api.Use(func(c *fiber.Ctx) error {
|
||||
if err := exts.EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.Next()
|
||||
}).Get("/ws", websocket.New(listenWebsocket))
|
||||
|
||||
api.All("/*", func(c *fiber.Ctx) error {
|
||||
return fiber.ErrNotFound
|
||||
})
|
||||
}
|
||||
}
|
29
pkg/internal/server/api/ws.go
Normal file
29
pkg/internal/server/api/ws.go
Normal file
@ -0,0 +1,29 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/models"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/services"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func listenWebsocket(c *websocket.Conn) {
|
||||
user := c.Locals("user").(models.Account)
|
||||
|
||||
// Push connection
|
||||
services.ClientRegister(user, c)
|
||||
log.Debug().Uint("user", user.ID).Msg("New websocket connection established...")
|
||||
|
||||
// Event loop
|
||||
var err error
|
||||
|
||||
for {
|
||||
if _, _, err = c.ReadMessage(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Pop connection
|
||||
services.ClientUnregister(user, c)
|
||||
log.Debug().Uint("user", user.ID).Msg("A websocket connection disconnected...")
|
||||
}
|
118
pkg/internal/server/exts/auth.go
Normal file
118
pkg/internal/server/exts/auth.go
Normal file
@ -0,0 +1,118 @@
|
||||
package exts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/directory"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func AuthMiddleware(c *fiber.Ctx) error {
|
||||
var atk string
|
||||
if cookie := c.Cookies(hyper.CookieAtk); len(cookie) > 0 {
|
||||
atk = cookie
|
||||
}
|
||||
if header := c.Get(fiber.HeaderAuthorization); len(header) > 0 {
|
||||
tk := strings.Replace(header, "Bearer", "", 1)
|
||||
atk = strings.TrimSpace(tk)
|
||||
}
|
||||
if tk := c.Query("tk"); len(tk) > 0 {
|
||||
atk = strings.TrimSpace(tk)
|
||||
}
|
||||
|
||||
c.Locals("p_token", atk)
|
||||
|
||||
rtk := c.Cookies(hyper.CookieRtk)
|
||||
if acc, newAtk, newRtk, err := DoAuthenticate(atk, rtk); err == nil {
|
||||
if newAtk != atk {
|
||||
SetAuthCookies(c, newAtk, newRtk)
|
||||
}
|
||||
c.Locals("user", acc)
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
func DoAuthenticate(atk, rtk string) (acc *proto.UserInfo, accessTk string, refreshTk string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
in := directory.GetServiceInstanceByType(directory.ServiceTypeAuthProvider)
|
||||
if in == nil {
|
||||
return
|
||||
}
|
||||
conn, err := in.GetGrpcConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var reply *proto.AuthReply
|
||||
reply, err = proto.NewAuthClient(conn).Authenticate(ctx, &proto.AuthRequest{
|
||||
AccessToken: atk,
|
||||
RefreshToken: &rtk,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if reply != nil {
|
||||
acc = reply.GetInfo().GetInfo()
|
||||
accessTk = reply.GetInfo().GetNewAccessToken()
|
||||
refreshTk = reply.GetInfo().GetNewRefreshToken()
|
||||
if !reply.IsValid {
|
||||
err = fmt.Errorf("invalid authorization context")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func CheckPermGranted(atk string, key string, val []byte) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
in := directory.GetServiceInstanceByType(directory.ServiceTypeAuthProvider)
|
||||
if in == nil {
|
||||
return fmt.Errorf("no auth provider found")
|
||||
}
|
||||
conn, err := in.GetGrpcConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reply, err := proto.NewAuthClient(conn).EnsurePermGranted(ctx, &proto.CheckPermRequest{
|
||||
Token: atk,
|
||||
Key: key,
|
||||
Value: val,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !reply.GetIsValid() {
|
||||
return fmt.Errorf("missing permission: %s", key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureAuthenticated(c *fiber.Ctx) error {
|
||||
if _, ok := c.Locals("p_user").(*proto.UserInfo); !ok {
|
||||
return fiber.NewError(fiber.StatusUnauthorized)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EnsureGrantedPerm(c *fiber.Ctx, key string, val any) error {
|
||||
if err := EnsureAuthenticated(c); err != nil {
|
||||
return err
|
||||
}
|
||||
encodedVal, _ := jsoniter.Marshal(val)
|
||||
if err := CheckPermGranted(c.Locals("p_token").(string), key, encodedVal); err != nil {
|
||||
return fiber.NewError(fiber.StatusForbidden, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
27
pkg/internal/server/exts/cookies.go
Normal file
27
pkg/internal/server/exts/cookies.go
Normal file
@ -0,0 +1,27 @@
|
||||
package exts
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/spf13/viper"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SetAuthCookies(c *fiber.Ctx, atk, rtk string) {
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: hyper.CookieAtk,
|
||||
Value: atk,
|
||||
Domain: viper.GetString("security.cookie_domain"),
|
||||
SameSite: viper.GetString("security.cookie_samesite"),
|
||||
Expires: time.Now().Add(60 * time.Minute),
|
||||
Path: "/",
|
||||
})
|
||||
c.Cookie(&fiber.Cookie{
|
||||
Name: hyper.CookieRtk,
|
||||
Value: rtk,
|
||||
Domain: viper.GetString("security.cookie_domain"),
|
||||
SameSite: viper.GetString("security.cookie_samesite"),
|
||||
Expires: time.Now().Add(24 * 30 * time.Hour),
|
||||
Path: "/",
|
||||
})
|
||||
}
|
18
pkg/internal/server/exts/request.go
Normal file
18
pkg/internal/server/exts/request.go
Normal file
@ -0,0 +1,18 @@
|
||||
package exts
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
var validation = validator.New(validator.WithRequiredStructEnabled())
|
||||
|
||||
func BindAndValidate(c *fiber.Ctx, out any) error {
|
||||
if err := c.BodyParser(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
} else if err := validation.Struct(out); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
66
pkg/internal/server/server.go
Normal file
66
pkg/internal/server/server.go
Normal file
@ -0,0 +1,66 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/server/api"
|
||||
"git.solsynth.dev/hydrogen/dealer/pkg/internal/server/exts"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/idempotency"
|
||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type HTTPApp struct {
|
||||
app *fiber.App
|
||||
}
|
||||
|
||||
func NewServer() *HTTPApp {
|
||||
app := fiber.New(fiber.Config{
|
||||
DisableStartupMessage: true,
|
||||
EnableIPValidation: true,
|
||||
ServerHeader: "Hydrogen.Dealer",
|
||||
AppName: "Hydrogen.Dealer",
|
||||
ProxyHeader: fiber.HeaderXForwardedFor,
|
||||
JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal,
|
||||
JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal,
|
||||
EnablePrintRoutes: viper.GetBool("debug.print_routes"),
|
||||
})
|
||||
|
||||
app.Use(idempotency.New())
|
||||
app.Use(cors.New(cors.Config{
|
||||
AllowCredentials: true,
|
||||
AllowMethods: strings.Join([]string{
|
||||
fiber.MethodGet,
|
||||
fiber.MethodPost,
|
||||
fiber.MethodHead,
|
||||
fiber.MethodOptions,
|
||||
fiber.MethodPut,
|
||||
fiber.MethodDelete,
|
||||
fiber.MethodPatch,
|
||||
}, ","),
|
||||
AllowOriginsFunc: func(origin string) bool {
|
||||
return true
|
||||
},
|
||||
}))
|
||||
|
||||
app.Use(logger.New(logger.Config{
|
||||
Format: "${status} | ${latency} | ${method} ${path}\n",
|
||||
Output: log.Logger,
|
||||
}))
|
||||
|
||||
app.Use(exts.AuthMiddleware)
|
||||
|
||||
api.MapAPIs(app)
|
||||
|
||||
return &HTTPApp{app}
|
||||
}
|
||||
|
||||
func (v *HTTPApp) Listen() {
|
||||
if err := v.app.Listen(viper.GetString("bind")); err != nil {
|
||||
log.Fatal().Err(err).Msg("An error occurred when starting server...")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user