✨ 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" />
|
<option name="autoReloadType" value="ALL" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<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$/.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/cmd/main.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/cmd/main.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/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" />
|
<change beforePath="$PROJECT_DIR$/pkg/services/notifications.go" beforeDir="false" afterPath="$PROJECT_DIR$/pkg/services/notifications.go" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@ -26,13 +29,13 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="master" />
|
<entry key="$PROJECT_DIR$" value="features/kex" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProblemsViewState">
|
<component name="ProblemsViewState">
|
||||||
<option name="selectedTabId" value="ProjectErrors" />
|
<option name="selectedTabId" value="CurrentFile" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo">{
|
||||||
"customColor": "",
|
"customColor": "",
|
||||||
@ -43,32 +46,32 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent">{
|
<component name="PropertiesComponent"><![CDATA[{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"DefaultGoTemplateProperty": "Go File",
|
"DefaultGoTemplateProperty": "Go File",
|
||||||
"Go 构建.Backend.executor": "Run",
|
"Go 构建.Backend.executor": "Run",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.go.formatter.settings.were.checked": "true",
|
"RunOnceActivity.go.formatter.settings.were.checked": "true",
|
||||||
"RunOnceActivity.go.migrated.go.modules.settings": "true",
|
"RunOnceActivity.go.migrated.go.modules.settings": "true",
|
||||||
"RunOnceActivity.go.modules.automatic.dependencies.download": "true",
|
"RunOnceActivity.go.modules.automatic.dependencies.download": "true",
|
||||||
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
|
"RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true",
|
||||||
"git-widget-placeholder": "master",
|
"git-widget-placeholder": "master",
|
||||||
"go.import.settings.migrated": "true",
|
"go.import.settings.migrated": "true",
|
||||||
"go.sdk.automatically.set": "true",
|
"go.sdk.automatically.set": "true",
|
||||||
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/server/ui",
|
"last_opened_file_path": "/Users/littlesheep/Documents/Projects/Hydrogen/Passport/pkg/server/ui",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"run.code.analysis.last.selected.profile": "pProject Default",
|
"run.code.analysis.last.selected.profile": "pProject Default",
|
||||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
"DatabaseDriversLRU": [
|
"DatabaseDriversLRU": [
|
||||||
"postgresql"
|
"postgresql"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}</component>
|
}]]></component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<key name="CopyFile.RECENT_KEYS">
|
||||||
<recent name="$PROJECT_DIR$/pkg/server/ui" />
|
<recent name="$PROJECT_DIR$/pkg/server/ui" />
|
||||||
@ -122,7 +125,19 @@
|
|||||||
<map>
|
<map>
|
||||||
<entry key="MAIN">
|
<entry key="MAIN">
|
||||||
<value>
|
<value>
|
||||||
<State />
|
<State>
|
||||||
|
<option name="FILTERS">
|
||||||
|
<map>
|
||||||
|
<entry key="branch">
|
||||||
|
<value>
|
||||||
|
<list>
|
||||||
|
<option value="master" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</State>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
</map>
|
</map>
|
||||||
@ -152,7 +167,8 @@
|
|||||||
<MESSAGE value=":bug: Dumb man make dumb mistake again" />
|
<MESSAGE value=":bug: Dumb man make dumb mistake again" />
|
||||||
<MESSAGE value=":bug: Fix new realm owner missing permissions" />
|
<MESSAGE value=":bug: Fix new realm owner missing permissions" />
|
||||||
<MESSAGE value=":zap: Use map to improve message delivery time" />
|
<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>
|
||||||
<component name="VgoProject">
|
<component name="VgoProject">
|
||||||
<settings-migrated>true</settings-migrated>
|
<settings-migrated>true</settings-migrated>
|
||||||
|
@ -72,6 +72,7 @@ func main() {
|
|||||||
quartz.AddFunc("@every 60m", services.DoAutoSignoff)
|
quartz.AddFunc("@every 60m", services.DoAutoSignoff)
|
||||||
quartz.AddFunc("@every 60m", services.DoAutoAuthCleanup)
|
quartz.AddFunc("@every 60m", services.DoAutoAuthCleanup)
|
||||||
quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup)
|
quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup)
|
||||||
|
quartz.AddFunc("@every 5m", services.KexCleanup)
|
||||||
quartz.Start()
|
quartz.Start()
|
||||||
|
|
||||||
// Messages
|
// 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
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gofiber/contrib/websocket"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg"
|
"git.solsynth.dev/hydrogen/passport/pkg"
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/i18n"
|
"git.solsynth.dev/hydrogen/passport/pkg/i18n"
|
||||||
"git.solsynth.dev/hydrogen/passport/pkg/server/ui"
|
"git.solsynth.dev/hydrogen/passport/pkg/server/ui"
|
||||||
"github.com/gofiber/contrib/websocket"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||||
@ -74,8 +74,6 @@ func NewServer() {
|
|||||||
notify.Post("/subscribe", authMiddleware, addNotifySubscriber)
|
notify.Post("/subscribe", authMiddleware, addNotifySubscriber)
|
||||||
notify.Put("/batch/read", authMiddleware, markNotificationReadBatch)
|
notify.Put("/batch/read", authMiddleware, markNotificationReadBatch)
|
||||||
notify.Put("/:notificationId/read", authMiddleware, markNotificationRead)
|
notify.Put("/:notificationId/read", authMiddleware, markNotificationRead)
|
||||||
|
|
||||||
notify.Get("/listen", authMiddleware, websocket.New(listenNotifications))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
me := api.Group("/users/me").Name("Myself Operations")
|
me := api.Group("/users/me").Name("Myself Operations")
|
||||||
@ -136,6 +134,8 @@ func NewServer() {
|
|||||||
{
|
{
|
||||||
developers.Post("/notify", notifyUser)
|
developers.Post("/notify", notifyUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api.Get("/ws", authMiddleware, websocket.New(listenWebsocket))
|
||||||
}
|
}
|
||||||
|
|
||||||
A.Use(favicon.New(favicon.Config{
|
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 {
|
func PushNotification(notification models.Notification) error {
|
||||||
raw, _ := jsoniter.Marshal(notification)
|
raw, _ := jsoniter.Marshal(notification)
|
||||||
for conn := range wsConn[notification.RecipientID] {
|
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
|
var subscribers []models.NotificationSubscriber
|
||||||
|
Loading…
Reference in New Issue
Block a user