♻️ Basiclly moved to Dealer from Consul

This commit is contained in:
2024-07-15 00:01:17 +08:00
parent a60be78ce6
commit 69fb9531cb
40 changed files with 298 additions and 1451 deletions

BIN
pkg/internal/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -2,51 +2,41 @@ package gap
import (
"fmt"
"strconv"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"github.com/rs/zerolog/log"
"strings"
"github.com/hashicorp/consul/api"
"github.com/spf13/viper"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
_ "github.com/mbobakov/grpc-consul-resolver"
)
func Register() error {
cfg := api.DefaultConfig()
cfg.Address = viper.GetString("consul.addr")
client, err := api.NewClient(cfg)
if err != nil {
return err
}
var H *hyper.HyperConn
func RegisterService() error {
grpcBind := strings.SplitN(viper.GetString("grpc_bind"), ":", 2)
httpBind := strings.SplitN(viper.GetString("bind"), ":", 2)
outboundIp, _ := GetOutboundIP()
port, _ := strconv.Atoi(grpcBind[1])
registration := new(api.AgentServiceRegistration)
registration.ID = viper.GetString("id")
registration.Name = "Hydrogen.Passport"
registration.Address = outboundIp.String()
registration.Port = port
registration.Check = &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%s", outboundIp, grpcBind[1]),
Timeout: "5s",
Interval: "1m",
DeregisterCriticalServiceAfter: "3m",
grpcOutbound := fmt.Sprintf("%s:%s", outboundIp, grpcBind[1])
httpOutbound := fmt.Sprintf("%s:%s", outboundIp, httpBind[1])
var err error
H, err = hyper.NewHyperConn(viper.GetString("dealer.addr"), &proto.ServiceInfo{
Id: viper.GetString("id"),
Type: hyper.ServiceTypeAuthProvider,
Label: "Passport",
GrpcAddr: grpcOutbound,
HttpAddr: &httpOutbound,
})
if err == nil {
go func() {
err := H.KeepRegisterService()
if err != nil {
log.Error().Err(err).Msg("An error occurred while registering service...")
}
}()
}
return client.Agent().ServiceRegister(registration)
}
func DiscoverPaperclip() (*grpc.ClientConn, error) {
target := fmt.Sprintf("consul://%s/Hydrogen.Paperclip", viper.GetString("consul.addr"))
return grpc.NewClient(
target,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
return err
}

View File

@ -3,23 +3,22 @@ package grpc
import (
"context"
exproto "git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
jsoniter "github.com/json-iterator/go"
"github.com/samber/lo"
)
func (v *Server) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.AuthReply, error) {
func (v *Server) Authenticate(_ context.Context, in *exproto.AuthRequest) (*exproto.AuthReply, error) {
ctx, perms, atk, rtk, err := services.Authenticate(in.GetAccessToken(), in.GetRefreshToken(), 0)
if err != nil {
return &proto.AuthReply{
return &exproto.AuthReply{
IsValid: false,
}, nil
} else {
user := ctx.Account
rawPerms, _ := jsoniter.Marshal(perms)
userinfo := &proto.Userinfo{
userinfo := &exproto.UserInfo{
Id: uint64(user.ID),
Name: user.Name,
Nick: user.Nick,
@ -34,18 +33,20 @@ func (v *Server) Authenticate(_ context.Context, in *proto.AuthRequest) (*proto.
userinfo.Banner = *user.GetBanner()
}
return &proto.AuthReply{
IsValid: true,
AccessToken: &atk,
RefreshToken: &rtk,
Permissions: rawPerms,
TicketId: lo.ToPtr(uint64(ctx.Ticket.ID)),
Userinfo: userinfo,
return &exproto.AuthReply{
IsValid: true,
Info: &exproto.AuthInfo{
NewAccessToken: &atk,
NewRefreshToken: &rtk,
Permissions: rawPerms,
TicketId: uint64(ctx.Ticket.ID),
Info: userinfo,
},
}, nil
}
}
func (v *Server) CheckPerm(_ context.Context, in *proto.CheckPermRequest) (*proto.CheckPermReply, error) {
func (v *Server) CheckPerm(_ context.Context, in *exproto.CheckPermRequest) (*exproto.CheckPermReply, error) {
claims, err := services.DecodeJwt(in.GetToken())
if err != nil {
return nil, err
@ -64,7 +65,7 @@ func (v *Server) CheckPerm(_ context.Context, in *proto.CheckPermRequest) (*prot
perms := services.FilterPermNodes(heldPerms, ctx.Ticket.Claims)
valid := services.HasPermNode(perms, in.GetKey(), value)
return &proto.CheckPermReply{
return &exproto.CheckPermReply{
IsValid: valid,
}, nil
}

View File

@ -4,6 +4,7 @@ import (
"google.golang.org/grpc/reflection"
"net"
exproto "git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/proto"
"github.com/spf13/viper"
"google.golang.org/grpc"
@ -12,7 +13,7 @@ import (
import health "google.golang.org/grpc/health/grpc_health_v1"
type Server struct {
proto.UnimplementedAuthServer
exproto.UnimplementedAuthServer
proto.UnimplementedNotifyServer
proto.UnimplementedFriendshipsServer
proto.UnimplementedRealmsServer
@ -26,7 +27,7 @@ func NewServer() *Server {
srv: grpc.NewServer(),
}
proto.RegisterAuthServer(server.srv, &Server{})
exproto.RegisterAuthServer(server.srv, &Server{})
proto.RegisterNotifyServer(server.srv, &Server{})
proto.RegisterFriendshipsServer(server.srv, &Server{})
proto.RegisterRealmsServer(server.srv, &Server{})

View File

@ -1,16 +0,0 @@
package i18n
import (
jsoniter "github.com/json-iterator/go"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
var Bundle *i18n.Bundle
func InitInternationalization() {
Bundle = i18n.NewBundle(language.English)
Bundle.RegisterUnmarshalFunc("json", jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal)
Bundle.LoadMessageFileFS(FS, "locale.en.json")
Bundle.LoadMessageFileFS(FS, "locale.zh.json")
}

View File

@ -1,6 +0,0 @@
package i18n
import "embed"
//go:embed locale.*.json
var FS embed.FS

View File

@ -1,23 +0,0 @@
{
"next": "Next",
"email": "Email",
"username": "Username",
"nickname": "Nickname",
"password": "Password",
"unknown": "Unknown",
"apply": "Apply",
"back": "Back",
"approve": "Approve",
"decline": "Decline",
"magicToken": "Magic Token",
"signinTitle": "Sign In",
"signinCaption": "Sign in to Solarpass to explore entire Solar Network. Explore posts, discover communities, talk with your best friends. All these things in the Solar Network!",
"signinRequired": "You need to sign in before do that.",
"signupTitle": "Sign Up",
"signupCaption": "Sign up to create an account on Solarpass, then you can explore the entire Solar Network! Enjoy the next-generation Internet Ecosystem!",
"authorizeTitle": "Authorize",
"authorizeCaption": "One Solarpass, get entire network.",
"mfaTitle": "Multi Factor Authenticate",
"mfaCaption": "We need use one more way to verify it is you.",
"mfaFactorEmail": "OTP through your email"
}

View File

@ -1,23 +0,0 @@
{
"next": "下一步",
"email": "邮件地址",
"username": "用户名",
"nickname": "昵称",
"password": "密码",
"unknown": "未知",
"apply": "应用",
"back": "返回",
"approve": "接受",
"decline": "拒绝",
"magicToken": "魔法令牌",
"signinTitle": "登陆",
"signinCaption": "登陆 Solarpass 以探索整个 Solar Network浏览帖子、探索社区、和你的好朋友聊八卦一切尽在 Solar Network!",
"signinRequired": "你需要在那之前登陆",
"signupTitle": "注册",
"signupCaption": "注册以在 Solarpass 创建一个账号,之后你就可以探索整个 Solar Network享受下一代互联网生态系统",
"authorizeTitle": "授权",
"authorizeCaption": "一个 Solarpass整个网络。",
"mfaTitle": "多因素验证",
"mfaCaption": "我们需要另一个方法来确认你是你。",
"mfaFactorEmail": "电子邮寄一次性验证码"
}

View File

@ -1,15 +0,0 @@
package i18n
import (
"github.com/gofiber/fiber/v2"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
func I18nMiddleware(c *fiber.Ctx) error {
accept := c.Get(fiber.HeaderAcceptLanguage)
localizer := i18n.NewLocalizer(Bundle, accept)
c.Locals("localizer", localizer)
return c.Next()
}

View File

@ -21,26 +21,26 @@ type Account struct {
SuspendedAt *time.Time `json:"suspended_at"`
PermNodes datatypes.JSONMap `json:"perm_nodes"`
Profile AccountProfile `json:"profile"`
Statuses []Status `json:"statuses"`
Badges []Badge `json:"badges"`
Profile AccountProfile `json:"profile,omitempty"`
Statuses []Status `json:"statuses,omitempty"`
Badges []Badge `json:"badges,omitempty"`
Contacts []AccountContact `json:"contacts"`
RealmIdentities []RealmMember `json:"realm_identities"`
Contacts []AccountContact `json:"contacts,omitempty"`
RealmIdentities []RealmMember `json:"realm_identities,omitempty"`
Tickets []AuthTicket `json:"tickets"`
Factors []AuthFactor `json:"factors"`
Tickets []AuthTicket `json:"tickets,omitempty"`
Factors []AuthFactor `json:"factors,omitempty"`
Events []ActionEvent `json:"events"`
Events []ActionEvent `json:"events,omitempty"`
MagicTokens []MagicToken `json:"-"`
ThirdClients []ThirdClient `json:"clients"`
ThirdClients []ThirdClient `json:"clients,omitempty"`
Notifications []Notification `json:"notifications" gorm:"foreignKey:RecipientID"`
NotifySubscribers []NotificationSubscriber `json:"notify_subscribers"`
Notifications []Notification `json:"notifications,omitempty" gorm:"foreignKey:RecipientID"`
NotifySubscribers []NotificationSubscriber `json:"notify_subscribers,omitempty"`
Friendships []AccountFriendship `json:"friendships" gorm:"foreignKey:AccountID"`
RelatedFriendships []AccountFriendship `json:"related_friendships" gorm:"foreignKey:RelatedID"`
Friendships []AccountFriendship `json:"friendships,omitempty" gorm:"foreignKey:AccountID"`
RelatedFriendships []AccountFriendship `json:"related_friendships,omitempty" gorm:"foreignKey:RelatedID"`
}
func (v Account) GetAvatar() *string {

BIN
pkg/internal/server/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -3,6 +3,7 @@ package api
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/paperclip/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
"git.solsynth.dev/hydrogen/passport/pkg/internal/gap"
@ -27,7 +28,7 @@ func setAvatar(c *fiber.Ctx) error {
return err
}
pc, err := gap.DiscoverPaperclip()
pc, err := gap.H.GetServiceGrpcConn(hyper.ServiceTypeFileProvider)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "attachments services was not available")
}
@ -63,7 +64,7 @@ func setBanner(c *fiber.Ctx) error {
return err
}
pc, err := gap.DiscoverPaperclip()
pc, err := gap.H.GetServiceGrpcConn(hyper.ServiceTypeFileProvider)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "attachments services was not available")
}

View File

@ -1,8 +1,6 @@
package api
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
)
@ -99,13 +97,6 @@ func MapAPIs(app *fiber.App) {
developers.Post("/notify", notifyUser)
}
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
})

View File

@ -1,82 +0,0 @@
package api
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/contrib/websocket"
jsoniter "github.com/json-iterator/go"
"github.com/rs/zerolog/log"
"github.com/samber/lo"
)
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 task models.UnifiedCommand
var messageType int
var payload []byte
var packet []byte
var err error
for {
if messageType, packet, err = c.ReadMessage(); err != nil {
break
} else if err := jsoniter.Unmarshal(packet, &task); err != nil {
_ = c.WriteMessage(messageType, models.UnifiedCommand{
Action: "error",
Message: "unable to unmarshal your command, requires json request",
}.Marshal())
continue
} else {
payload, _ = jsoniter.Marshal(task.Payload)
}
var message *models.UnifiedCommand
switch task.Action {
case "kex.request":
var req struct {
RequestID string `json:"request_id"`
KeypairID string `json:"keypair_id"`
Algorithm string `json:"algorithm"`
OwnerID uint `json:"owner_id"`
Deadline int64 `json:"deadline"`
}
_ = jsoniter.Unmarshal(payload, &req)
if len(req.RequestID) <= 0 || len(req.KeypairID) <= 0 || req.OwnerID <= 0 {
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("invalid request")))
}
services.KexRequest(c, req.RequestID, req.KeypairID, req.Algorithm, req.OwnerID, req.Deadline)
case "kex.provide":
var req struct {
RequestID string `json:"request_id"`
KeypairID string `json:"keypair_id"`
Algorithm string `json:"algorithm"`
PublicKey []byte `json:"public_key"`
}
_ = jsoniter.Unmarshal(payload, &req)
if len(req.RequestID) <= 0 || len(req.KeypairID) <= 0 {
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("invalid request")))
}
services.KexProvide(user.ID, req.RequestID, req.KeypairID, packet)
default:
message = lo.ToPtr(models.UnifiedCommandFromError(fmt.Errorf("unknown action")))
}
if message != nil {
if err = c.WriteMessage(messageType, message.Marshal()); err != nil {
break
}
}
}
// Pop connection
services.ClientUnregister(user, c)
log.Debug().Uint("user", user.ID).Msg("A websocket connection disconnected...")
}

View File

@ -2,7 +2,7 @@ package exts
import (
"fmt"
"git.solsynth.dev/hydrogen/passport/pkg/hyper"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"git.solsynth.dev/hydrogen/passport/pkg/internal/services"
"github.com/gofiber/fiber/v2"

View File

@ -1,7 +1,7 @@
package exts
import (
"git.solsynth.dev/hydrogen/passport/pkg/hyper"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"github.com/gofiber/fiber/v2"
"github.com/spf13/viper"
"time"

View File

@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
"git.solsynth.dev/hydrogen/passport/pkg/internal/i18n"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/favicon"
@ -59,7 +58,6 @@ func NewServer() *HTTPApp {
}))
app.Use(exts.AuthMiddleware)
app.Use(i18n.I18nMiddleware)
admin.MapAdminAPIs(app)
api.MapAPIs(app)

View File

@ -1,37 +0,0 @@
package services
import (
"sync"
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/gofiber/contrib/websocket"
)
var (
wsMutex sync.Mutex
wsConn = make(map[uint]map[*websocket.Conn]bool)
)
func ClientRegister(user models.Account, conn *websocket.Conn) {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[*websocket.Conn]bool)
}
wsConn[user.ID][conn] = true
wsMutex.Unlock()
}
func ClientUnregister(user models.Account, conn *websocket.Conn) {
wsMutex.Lock()
if wsConn[user.ID] == nil {
wsConn[user.ID] = make(map[*websocket.Conn]bool)
}
delete(wsConn[user.ID], conn)
wsMutex.Unlock()
if status, err := GetStatus(user.ID); err != nil || !status.IsInvisible {
if len(wsConn[user.ID]) == 0 {
_ = SetAccountLastSeen(user.ID)
}
}
}

View File

@ -1,82 +0,0 @@
package services
import (
"git.solsynth.dev/hydrogen/passport/pkg/internal/models"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
"time"
)
type kexRequest struct {
OwnerID uint
Conn *websocket.Conn
Deadline time.Time
}
var kexRequests = make(map[string]map[string]kexRequest)
func KexRequest(conn *websocket.Conn, requestId, keypairId, algorithm string, ownerId uint, deadline int64) {
if kexRequests[keypairId] == nil {
kexRequests[keypairId] = make(map[string]kexRequest)
}
ddl := time.Now().Add(time.Second * time.Duration(deadline))
request := kexRequest{
OwnerID: ownerId,
Conn: conn,
Deadline: ddl,
}
flag := false
for c := range wsConn[ownerId] {
if c == conn {
continue
}
if c.WriteMessage(1, models.UnifiedCommand{
Action: "kex.request",
Payload: fiber.Map{
"request_id": requestId,
"keypair_id": keypairId,
"algorithm": algorithm,
"owner_id": ownerId,
"deadline": deadline,
},
}.Marshal()) == nil {
flag = true
}
}
if flag {
kexRequests[keypairId][requestId] = request
}
}
func KexProvide(userId uint, requestId, keypairId string, pkt []byte) {
if kexRequests[keypairId] == nil {
return
}
val, ok := kexRequests[keypairId][requestId]
if !ok {
return
} else if val.OwnerID != userId {
return
} else {
_ = val.Conn.WriteMessage(1, pkt)
}
}
func KexCleanup() {
if len(kexRequests) <= 0 {
return
}
for kp, data := range kexRequests {
for idx, req := range data {
if req.Deadline.Unix() <= time.Now().Unix() {
delete(kexRequests[kp], idx)
}
}
}
}

View File

@ -2,7 +2,11 @@ package services
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/internal/gap"
"reflect"
"time"
"firebase.google.com/go/messaging"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
@ -56,15 +60,23 @@ func NewNotification(notification models.Notification) error {
return nil
}
// PushNotification will push the notification what ever it is exists record in the database
// Recommend push another goroutine when you need to push a lot of notification
// And just use block statement when you just push one notification, the time of create a new sub-process is much more than push notification
// PushNotification will push the notification whatever it exists record in the
// database Recommend push another goroutine when you need to push a lot of
// notifications And just use a block statement when you just push one
// notification, the time of create a new subprocess is much more than push
// notification
func PushNotification(notification models.Notification) error {
for conn := range wsConn[notification.RecipientID] {
_ = conn.WriteMessage(1, models.UnifiedCommand{
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err := proto.NewStreamControllerClient(gap.H.GetDealerGrpcConn()).PushStream(ctx, &proto.PushStreamRequest{
UserId: uint64(notification.RecipientID),
Body: models.UnifiedCommand{
Action: "notifications.new",
Payload: notification,
}.Marshal())
}.Marshal(),
})
if err != nil {
return fmt.Errorf("failed to push via websocket: %v", err)
}
// Skip push notification

View File

@ -1,7 +1,10 @@
package services
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/passport/pkg/internal/gap"
"time"
"git.solsynth.dev/hydrogen/passport/pkg/internal/database"
@ -32,7 +35,16 @@ func GetStatus(uid uint) (models.Status, error) {
}
func GetUserOnline(uid uint) bool {
return wsConn[uid] != nil && len(wsConn[uid]) > 0
pc := proto.NewStreamControllerClient(gap.H.GetDealerGrpcConn())
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := pc.CountStreamConnection(ctx, &proto.CountConnectionRequest{
UserId: uint64(uid),
})
if err != nil {
return false
}
return resp.Count > 0
}
func GetStatusDisturbable(uid uint) error {
@ -49,7 +61,7 @@ func GetStatusDisturbable(uid uint) error {
func GetStatusOnline(uid uint) error {
status, err := GetStatus(uid)
isOnline := wsConn[uid] != nil && len(wsConn[uid]) > 0
isOnline := GetUserOnline(uid)
if isOnline && err != nil {
return nil
} else if err == nil && status.IsInvisible {