✨ E2EE Key Exchange
This commit is contained in:
parent
18a4321685
commit
3ba152252e
76
.idea/workspace.xml
generated
76
.idea/workspace.xml
generated
@ -4,10 +4,13 @@
|
||||
<option name="autoReloadType" value="ALL" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":zap: Use map to improve message delivery time">
|
||||
<list default="true" id="3fefb2c4-b6f9-466b-a523-53352e8d6f95" name="更改" comment=":recycle: Improved the notification subscriber API">
|
||||
<change afterPath="$PROJECT_DIR$/pkg/models/unified.go" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/pkg/services/e2ee.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/models/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/models/notifications.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/server/notifications_api.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/notifications_api.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/cmd/main.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/server/notify_ws.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/ws.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/server/startup.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/server/startup.go" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/pkg/services/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/notifications.go" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
@ -26,13 +29,13 @@
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||
<map>
|
||||
<entry key="$PROJECT_DIR$" value="master" />
|
||||
<entry key="$PROJECT_DIR$" value="features/kex" />
|
||||
</map>
|
||||
</option>
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProblemsViewState">
|
||||
<option name="selectedTabId" value="ProjectErrors" />
|
||||
<option name="selectedTabId" value="CurrentFile" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"customColor": "",
|
||||
@ -43,32 +46,32 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"DefaultGoTemplateProperty": "Go File",
|
||||
"Go 构建.Backend.executor": "Run",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.go.formatter.settings.were.checked": "true",
|
||||
"RunOnceActivity.go.migrated.go.modules.settings": "true",
|
||||
"RunOnceActivity.go.modules.automatic.dependencies.download": "true",
|
||||
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"go.import.settings.migrated": "true",
|
||||
"go.sdk.automatically.set": "true",
|
||||
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/server/ui",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"run.code.analysis.last.selected.profile": "pProject Default",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"DefaultGoTemplateProperty": "Go File",
|
||||
"Go 构建.Backend.executor": "Run",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"RunOnceActivity.go.formatter.settings.were.checked": "true",
|
||||
"RunOnceActivity.go.migrated.go.modules.settings": "true",
|
||||
"RunOnceActivity.go.modules.automatic.dependencies.download": "true",
|
||||
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
|
||||
"git-widget-placeholder": "master",
|
||||
"go.import.settings.migrated": "true",
|
||||
"go.sdk.automatically.set": "true",
|
||||
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/server/ui",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"run.code.analysis.last.selected.profile": "pProject Default",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
]
|
||||
}
|
||||
}</component>
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/pkg/server/ui" />
|
||||
@ -122,7 +125,19 @@
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
<State>
|
||||
<option name="FILTERS">
|
||||
<map>
|
||||
<entry key="branch">
|
||||
<value>
|
||||
<list>
|
||||
<option value="master" />
|
||||
</list>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</State>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
@ -152,7 +167,8 @@
|
||||
<MESSAGE value=":bug: Dumb man make dumb mistake again" />
|
||||
<MESSAGE value=":bug: Fix new realm owner missing permissions" />
|
||||
<MESSAGE value=":zap: Use map to improve message delivery time" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value=":zap: Use map to improve message delivery time" />
|
||||
<MESSAGE value=":recycle: Improved the notification subscriber API" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value=":recycle: Improved the notification subscriber API" />
|
||||
</component>
|
||||
<component name="VgoProject">
|
||||
<settings-migrated>true</settings-migrated>
|
||||
|
@ -72,6 +72,7 @@ func main() {
|
||||
quartz.AddFunc("@every 60m", services.DoAutoSignoff)
|
||||
quartz.AddFunc("@every 60m", services.DoAutoAuthCleanup)
|
||||
quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup)
|
||||
quartz.AddFunc("@every 5m", services.KexCleanup)
|
||||
quartz.Start()
|
||||
|
||||
// Messages
|
||||
|
21
pkg/models/unified.go
Normal file
21
pkg/models/unified.go
Normal file
@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
type UnifiedCommand struct {
|
||||
Action string `json:"w"`
|
||||
Message string `json:"m"`
|
||||
Payload any `json:"p"`
|
||||
}
|
||||
|
||||
func UnifiedCommandFromError(err error) UnifiedCommand {
|
||||
return UnifiedCommand{
|
||||
Action: "error",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func (v UnifiedCommand) Marshal() []byte {
|
||||
data, _ := jsoniter.Marshal(v)
|
||||
return data
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/services"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
)
|
||||
|
||||
func listenNotifications(c *websocket.Conn) {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
// Push connection
|
||||
services.ClientRegister(user, c)
|
||||
|
||||
// Event loop
|
||||
var err error
|
||||
for {
|
||||
if _, _, err = c.ReadMessage(); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Pop connection
|
||||
services.ClientUnregister(user, c)
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.solsynth.dev/hydrogen/passport/pkg"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/i18n"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/server/ui"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||
@ -74,8 +74,6 @@ func NewServer() {
|
||||
notify.Post("/subscribe", authMiddleware, addNotifySubscriber)
|
||||
notify.Put("/batch/read", authMiddleware, markNotificationReadBatch)
|
||||
notify.Put("/:notificationId/read", authMiddleware, markNotificationRead)
|
||||
|
||||
notify.Get("/listen", authMiddleware, websocket.New(listenNotifications))
|
||||
}
|
||||
|
||||
me := api.Group("/users/me").Name("Myself Operations")
|
||||
@ -136,6 +134,8 @@ func NewServer() {
|
||||
{
|
||||
developers.Post("/notify", notifyUser)
|
||||
}
|
||||
|
||||
api.Get("/ws", authMiddleware, websocket.New(listenWebsocket))
|
||||
}
|
||||
|
||||
A.Use(favicon.New(favicon.Config{
|
||||
|
77
pkg/server/ws.go
Normal file
77
pkg/server/ws.go
Normal file
@ -0,0 +1,77 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/models"
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/services"
|
||||
"github.com/gofiber/contrib/websocket"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func listenWebsocket(c *websocket.Conn) {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
|
||||
// Push connection
|
||||
services.ClientRegister(user, c)
|
||||
|
||||
// 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"`
|
||||
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, user.ID, req.Deadline)
|
||||
case "kex.provide":
|
||||
var req struct {
|
||||
RequestID string `json:"request_id"`
|
||||
KeypairID string `json:"keypair_id"`
|
||||
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, payload)
|
||||
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)
|
||||
}
|
77
pkg/services/e2ee.go
Normal file
77
pkg/services/e2ee.go
Normal file
@ -0,0 +1,77 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"git.solsynth.dev/hydrogen/passport/pkg/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 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 conn := range wsConn[ownerId] {
|
||||
if conn.WriteMessage(1, models.UnifiedCommand{
|
||||
Action: "kex.request",
|
||||
Payload: fiber.Map{
|
||||
"request_id": requestId,
|
||||
"keypair_id": keypairId,
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -60,7 +60,10 @@ func NewNotification(notification models.Notification) error {
|
||||
func PushNotification(notification models.Notification) error {
|
||||
raw, _ := jsoniter.Marshal(notification)
|
||||
for conn := range wsConn[notification.RecipientID] {
|
||||
_ = conn.WriteMessage(1, raw)
|
||||
_ = conn.WriteMessage(1, models.UnifiedCommand{
|
||||
Action: "notifications.new",
|
||||
Payload: raw,
|
||||
}.Marshal())
|
||||
}
|
||||
|
||||
var subscribers []models.NotificationSubscriber
|
||||
|
Loading…
Reference in New Issue
Block a user