From 1db53c5fb4792df83011d4403b131100282cfd75 Mon Sep 17 00:00:00 2001 From: Suman Biswas Date: Sun, 19 Jul 2026 14:45:18 +0530 Subject: [PATCH 1/2] go optimization --- server/routes/users.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/server/routes/users.go b/server/routes/users.go index 3764910..7dfece1 100644 --- a/server/routes/users.go +++ b/server/routes/users.go @@ -11,8 +11,7 @@ func UserRoute(router chi.Router) { router.Use(middlewares.AuthGuard) - router.Get("/me", controllers.HandleGetMe) // Resolves to /api/users/me - router.Get("/{id}", controllers.HandleGetUserByID) // Resolves to /api/users/:id - - router.With(searchLimiter.Limit).Get("/search", controllers.HandleUserSearch) // Resolves to /api/users/search + router.Get("/me", controllers.HandleGetMe) + router.With(searchLimiter.Limit).Get("/search", controllers.HandleUserSearch) + router.Get("/{id}", controllers.HandleGetUserByID) } From d1b30ed0c034886294ee20fc7cebe9146d6e68f7 Mon Sep 17 00:00:00 2001 From: Suman Biswas Date: Sun, 19 Jul 2026 15:36:25 +0530 Subject: [PATCH 2/2] e2ee --- server/controllers/e2ee.go | 71 ++++++++++++ server/migrations/010_create_e2ee_keys_up.sql | 20 ++++ server/models/e2ee.go | 24 +++++ server/repository/e2ee.go | 102 ++++++++++++++++++ server/routes/api.go | 1 + server/routes/e2ee.go | 14 +++ server/services/e2ee.go | 39 +++++++ 7 files changed, 271 insertions(+) create mode 100644 server/controllers/e2ee.go create mode 100644 server/migrations/010_create_e2ee_keys_up.sql create mode 100644 server/models/e2ee.go create mode 100644 server/repository/e2ee.go create mode 100644 server/routes/e2ee.go create mode 100644 server/services/e2ee.go diff --git a/server/controllers/e2ee.go b/server/controllers/e2ee.go new file mode 100644 index 0000000..af77806 --- /dev/null +++ b/server/controllers/e2ee.go @@ -0,0 +1,71 @@ +package controllers + +import ( + "encoding/json" + "net/http" + + "github.com/commandlinecoding/elephant/server/middlewares" + "github.com/commandlinecoding/elephant/server/models" + "github.com/commandlinecoding/elephant/server/services" + "github.com/go-chi/chi/v5" +) + +func HandleUploadE2EEKeys(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + uid, _ := r.Context().Value(middlewares.UserIDKey).(string) + + var req models.UploadKeysReq + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + w.WriteHeader(http.StatusBadRequest) + _ = json.NewEncoder(w).Encode(models.JSONResponse{Success: false, Error: "Malformatted JSON configuration payload structure"}) + return + } + + if req.DeviceID == "" { + req.DeviceID = "main" + } + + svc := services.NewE2EEService() + if err := svc.UploadKeys(r.Context(), uid, req); err != nil { + w.WriteHeader(http.StatusInternalServerError) + _ = json.NewEncoder(w).Encode(models.JSONResponse{Success: false, Error: err.Error()}) + return + } + + _, lowKeys, _ := svc.CheckKeysExhaustion(r.Context(), uid, req.DeviceID) + + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(models.JSONResponse{ + Success: true, + Data: map[string]interface{}{ + "message": "Prekey cryptographical parameters saved successfully", + "requires_refill": lowKeys, + }, + }) +} + +func HandleGetPrekeyBundle(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + targetUID := chi.URLParam(r, "userId") + deviceID := r.URL.Query().Get("device_id") + + if targetUID == "" { + w.WriteHeader(http.StatusBadRequest) + _ = json.NewEncoder(w).Encode(models.JSONResponse{Success: false, Error: "Target Identity validation token reference required"}) + return + } + + if deviceID == "" { + deviceID = "main" + } + + svc := services.NewE2EEService() + bundle, err := svc.GetBundle(r.Context(), targetUID, deviceID) + if err != nil { + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(models.JSONResponse{Success: false, Error: err.Error()}) + return + } + + _ = json.NewEncoder(w).Encode(models.JSONResponse{Success: true, Data: bundle}) +} diff --git a/server/migrations/010_create_e2ee_keys_up.sql b/server/migrations/010_create_e2ee_keys_up.sql new file mode 100644 index 0000000..2740ba4 --- /dev/null +++ b/server/migrations/010_create_e2ee_keys_up.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS user_devices ( + user_id UUID REFERENCES users(id) ON DELETE CASCADE, + device_id VARCHAR(50) NOT NULL, + identity_key TEXT NOT NULL, + signed_prekey TEXT NOT NULL, + signed_prekey_signature TEXT NOT NULL, + updated_at TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (user_id, device_id) +); + +CREATE TABLE IF NOT EXISTS one_time_prekeys ( + id SERIAL PRIMARY KEY, + user_id UUID NOT NULL, + device_id VARCHAR(50) NOT NULL, + key_id INT NOT NULL, + key_content TEXT NOT NULL, + FOREIGN KEY (user_id, device_id) REFERENCES user_devices(user_id, device_id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_otp_lookup ON one_time_prekeys(user_id, device_id); \ No newline at end of file diff --git a/server/models/e2ee.go b/server/models/e2ee.go new file mode 100644 index 0000000..d6a89bb --- /dev/null +++ b/server/models/e2ee.go @@ -0,0 +1,24 @@ +package models + +type OneTimeKeyReq struct { + ID int `json:"id"` + Content string `json:"content"` +} + +type UploadKeysReq struct { + DeviceID string `json:"device_id"` + IdentityKey string `json:"identity_key"` + SignedPrekey string `json:"signed_prekey"` + Signature string `json:"signature"` + OneTimePrekeys []OneTimeKeyReq `json:"one_time_prekeys"` +} + +type PrekeyBundle struct { + UserID string `json:"user_id"` + DeviceID string `json:"device_id"` + IdentityKey string `json:"identity_key"` + SignedPrekey string `json:"signed_prekey"` + Signature string `json:"signature"` + OneTimePrekeyID *int `json:"one_time_prekey_id,omitempty"` + OneTimePrekeyBody *string `json:"one_time_prekey_body,omitempty"` +} diff --git a/server/repository/e2ee.go b/server/repository/e2ee.go new file mode 100644 index 0000000..34e1097 --- /dev/null +++ b/server/repository/e2ee.go @@ -0,0 +1,102 @@ +package repository + +import ( + "context" + "errors" + + "github.com/commandlinecoding/elephant/server/config" + "github.com/commandlinecoding/elephant/server/models" + "github.com/jackc/pgx/v5" +) + +type E2EERepository struct{} + +func NewE2EERepository() *E2EERepository { + return &E2EERepository{} +} + +func (r *E2EERepository) SaveDeviceKeys(ctx context.Context, uid string, req models.UploadKeysReq) error { + tx, err := config.DB.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + deviceQuery := ` + INSERT INTO user_devices (user_id, device_id, identity_key, signed_prekey, signed_prekey_signature, updated_at) + VALUES ($1::uuid, $2, $3, $4, $5, NOW()) + ON CONFLICT (user_id, device_id) + DO UPDATE SET identity_key = $3, signed_prekey = $4, signed_prekey_signature = $5, updated_at = NOW(); + ` + _, err = tx.Exec(ctx, deviceQuery, uid, req.DeviceID, req.IdentityKey, req.SignedPrekey, req.Signature) + if err != nil { + return err + } + + if len(req.OneTimePrekeys) > 0 { + otpQuery := `INSERT INTO one_time_prekeys (user_id, device_id, key_id, key_content) VALUES ($1::uuid, $2, $3, $4);` + for _, k := range req.OneTimePrekeys { + _, err = tx.Exec(ctx, otpQuery, uid, req.DeviceID, k.ID, k.Content) + if err != nil { + return err + } + } + } + + return tx.Commit(ctx) +} + +func (r *E2EERepository) FetchBundle(ctx context.Context, targetUID, deviceID string) (*models.PrekeyBundle, error) { + tx, err := config.DB.Begin(ctx) + if err != nil { + return nil, err + } + defer tx.Rollback(ctx) + + baseQuery := ` + SELECT identity_key, signed_prekey, signed_prekey_signature + FROM user_devices + WHERE user_id = $1::uuid AND device_id = $2; + ` + var b models.PrekeyBundle + b.UserID = targetUID + b.DeviceID = deviceID + + err = tx.QueryRow(ctx, baseQuery, targetUID, deviceID).Scan(&b.IdentityKey, &b.SignedPrekey, &b.Signature) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.New("device keys not found for target identity profile") + } + return nil, err + } + + otpQuery := ` + DELETE FROM one_time_prekeys + WHERE id = ( + SELECT id FROM one_time_prekeys + WHERE user_id = $1::uuid AND device_id = $2 + LIMIT 1 + ) + RETURNING key_id, key_content; + ` + var otpID int + var otpBody string + err = tx.QueryRow(ctx, otpQuery, targetUID, deviceID).Scan(&otpID, &otpBody) + if err == nil { + b.OneTimePrekeyID = &otpID + b.OneTimePrekeyBody = &otpBody + } + + if err := tx.Commit(ctx); err != nil { + return nil, err + } + + return &b, nil +} + +func (r *E2EERepository) GetOTPCount(ctx context.Context, uid, deviceID string) (int, error) { + query := `SELECT COUNT(*) FROM one_time_prekeys WHERE user_id = $1::uuid AND device_id = $2;` + var count int + err := config.DB.QueryRow(ctx, query, uid, deviceID).Scan(&count) + return count, err +} diff --git a/server/routes/api.go b/server/routes/api.go index 8734115..c2ddf3a 100644 --- a/server/routes/api.go +++ b/server/routes/api.go @@ -13,4 +13,5 @@ func ApiRoute(router chi.Router) { router.Route("/users", UserRoute) router.Route("/messages", MessageRoute) router.Route("/groups", GroupRoute) + router.Route("/e2ee", E2EERoute) } diff --git a/server/routes/e2ee.go b/server/routes/e2ee.go new file mode 100644 index 0000000..d91dd7f --- /dev/null +++ b/server/routes/e2ee.go @@ -0,0 +1,14 @@ +package routes + +import ( + "github.com/commandlinecoding/elephant/server/controllers" + "github.com/commandlinecoding/elephant/server/middlewares" + "github.com/go-chi/chi/v5" +) + +func E2EERoute(router chi.Router) { + router.Use(middlewares.AuthGuard) + + router.Post("/keys", controllers.HandleUploadE2EEKeys) + router.Get("/bundle/{userId}", controllers.HandleGetPrekeyBundle) +} diff --git a/server/services/e2ee.go b/server/services/e2ee.go new file mode 100644 index 0000000..60a7af8 --- /dev/null +++ b/server/services/e2ee.go @@ -0,0 +1,39 @@ +package services + +import ( + "context" + "errors" + + "github.com/commandlinecoding/elephant/server/models" + "github.com/commandlinecoding/elephant/server/repository" +) + +type E2EEService struct { + repo *repository.E2EERepository +} + +func NewE2EEService() *E2EEService { + return &E2EEService{repo: repository.NewE2EERepository()} +} + +func (s *E2EEService) UploadKeys(ctx context.Context, uid string, req models.UploadKeysReq) error { + if req.DeviceID == "" || req.IdentityKey == "" || req.SignedPrekey == "" || req.Signature == "" { + return errors.New("missing core payload parameters for prekey bundle submission") + } + return s.repo.SaveDeviceKeys(ctx, uid, req) +} + +func (s *E2EEService) GetBundle(ctx context.Context, targetUID, deviceID string) (*models.PrekeyBundle, error) { + if deviceID == "" { + deviceID = "main" + } + return s.repo.FetchBundle(ctx, targetUID, deviceID) +} + +func (s *E2EEService) CheckKeysExhaustion(ctx context.Context, uid string, deviceID string) (int, bool, error) { + count, err := s.repo.GetOTPCount(ctx, uid, deviceID) + if err != nil { + return 0, false, err + } + return count, count < 10, nil +}