From 92e85351a68a53521171364236c377a614560071 Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 21 Mar 2026 12:08:11 +0000 Subject: [PATCH] refactor: migrate core import to dappco.re/go/core Co-Authored-By: Virgil --- go.mod | 3 +- go.sum | 6 ++- workspace/service.go | 92 +++++++++++++++++++++++++-------------- workspace/service_test.go | 27 +++++------- 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/go.mod b/go.mod index dacd54d..3f87ee0 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module forge.lthn.ai/core/go-io go 1.26.0 require ( + dappco.re/go/core v0.4.7 forge.lthn.ai/Snider/Borg v0.3.1 - forge.lthn.ai/core/go v0.3.3 forge.lthn.ai/core/go-crypt v0.1.6 forge.lthn.ai/core/go-log v0.0.4 github.com/aws/aws-sdk-go-v2 v1.41.4 @@ -15,6 +15,7 @@ require ( ) require ( + forge.lthn.ai/core/go v0.3.0 // indirect github.com/ProtonMail/go-crypto v1.4.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect diff --git a/go.sum b/go.sum index e598e6c..d25c96d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ +dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA= +dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= forge.lthn.ai/Snider/Borg v0.3.1 h1:gfC1ZTpLoZai07oOWJiVeQ8+qJYK8A795tgVGJHbVL8= forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg= -forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0= -forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ= +forge.lthn.ai/core/go v0.3.0 h1:mOG97ApMprwx9Ked62FdWVwXTGSF6JO6m0DrVpoH2Q4= +forge.lthn.ai/core/go v0.3.0/go.mod h1:gE6c8h+PJ2287qNhVUJ5SOe1kopEwHEquvinstpuyJc= forge.lthn.ai/core/go-crypt v0.1.6 h1:jB7L/28S1NR+91u3GcOYuKfBLzPhhBUY1fRe6WkGVns= forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo= forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= diff --git a/workspace/service.go b/workspace/service.go index d54d1a4..5325d2b 100644 --- a/workspace/service.go +++ b/workspace/service.go @@ -7,14 +7,29 @@ import ( "path/filepath" "sync" + core "dappco.re/go/core" coreerr "forge.lthn.ai/core/go-log" - core "forge.lthn.ai/core/go/pkg/core" + "forge.lthn.ai/core/go-io" ) -// Service implements the core.Workspace interface. +// Workspace provides management for encrypted user workspaces. +type Workspace interface { + CreateWorkspace(identifier, password string) (string, error) + SwitchWorkspace(name string) error + WorkspaceFileGet(filename string) (string, error) + WorkspaceFileSet(filename, content string) error +} + +// cryptProvider is the interface for PGP key generation. +type cryptProvider interface { + CreateKeyPair(name, passphrase string) (string, error) +} + +// Service implements the Workspace interface. type Service struct { core *core.Core + crypt cryptProvider activeWorkspace string rootPath string medium io.Medium @@ -22,7 +37,8 @@ type Service struct { } // New creates a new Workspace service instance. -func New(c *core.Core) (any, error) { +// An optional cryptProvider can be passed to supply PGP key generation. +func New(c *core.Core, crypt ...cryptProvider) (any, error) { home, err := os.UserHomeDir() if err != nil { return nil, coreerr.E("workspace.New", "failed to determine home directory", err) @@ -35,6 +51,10 @@ func New(c *core.Core) (any, error) { medium: io.Local, } + if len(crypt) > 0 && crypt[0] != nil { + s.crypt = crypt[0] + } + if err := s.medium.EnsureDir(rootPath); err != nil { return nil, coreerr.E("workspace.New", "failed to ensure root directory", err) } @@ -43,13 +63,16 @@ func New(c *core.Core) (any, error) { } // CreateWorkspace creates a new encrypted workspace. -// Identifier is hashed (SHA-256 as proxy for LTHN) to create the directory name. +// Identifier is hashed (SHA-256) to create the directory name. // A PGP keypair is generated using the password. func (s *Service) CreateWorkspace(identifier, password string) (string, error) { s.mu.Lock() defer s.mu.Unlock() - // 1. Identification (LTHN hash proxy) + if s.crypt == nil { + return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil) + } + hash := sha256.Sum256([]byte(identifier)) wsID := hex.EncodeToString(hash[:]) wsPath := filepath.Join(s.rootPath, wsID) @@ -58,26 +81,18 @@ func (s *Service) CreateWorkspace(identifier, password string) (string, error) { return "", coreerr.E("workspace.CreateWorkspace", "workspace already exists", nil) } - // 2. Directory structure - dirs := []string{"config", "log", "data", "files", "keys"} - for _, d := range dirs { + for _, d := range []string{"config", "log", "data", "files", "keys"} { if err := s.medium.EnsureDir(filepath.Join(wsPath, d)); err != nil { return "", coreerr.E("workspace.CreateWorkspace", "failed to create directory: "+d, err) } } - // 3. PGP Keypair generation - crypt := s.core.Crypt() - if crypt == nil { - return "", coreerr.E("workspace.CreateWorkspace", "crypt service not available", nil) - } - privKey, err := crypt.CreateKeyPair(identifier, password) + privKey, err := s.crypt.CreateKeyPair(identifier, password) if err != nil { return "", coreerr.E("workspace.CreateWorkspace", "failed to generate keys", err) } - // Save private key - if err := s.medium.Write(filepath.Join(wsPath, "keys", "private.key"), privKey); err != nil { + if err := s.medium.WriteMode(filepath.Join(wsPath, "keys", "private.key"), privKey, 0600); err != nil { return "", coreerr.E("workspace.CreateWorkspace", "failed to save private key", err) } @@ -98,36 +113,41 @@ func (s *Service) SwitchWorkspace(name string) error { return nil } +// activeFilePath returns the full path to a file in the active workspace, +// or an error if no workspace is active. +func (s *Service) activeFilePath(op, filename string) (string, error) { + if s.activeWorkspace == "" { + return "", coreerr.E(op, "no active workspace", nil) + } + return filepath.Join(s.rootPath, s.activeWorkspace, "files", filename), nil +} + // WorkspaceFileGet retrieves the content of a file from the active workspace. -// In a full implementation, this would involve decryption using the workspace key. func (s *Service) WorkspaceFileGet(filename string) (string, error) { s.mu.RLock() defer s.mu.RUnlock() - if s.activeWorkspace == "" { - return "", coreerr.E("workspace.WorkspaceFileGet", "no active workspace", nil) + path, err := s.activeFilePath("workspace.WorkspaceFileGet", filename) + if err != nil { + return "", err } - - path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename) return s.medium.Read(path) } // WorkspaceFileSet saves content to a file in the active workspace. -// In a full implementation, this would involve encryption using the workspace key. func (s *Service) WorkspaceFileSet(filename, content string) error { s.mu.Lock() defer s.mu.Unlock() - if s.activeWorkspace == "" { - return coreerr.E("workspace.WorkspaceFileSet", "no active workspace", nil) + path, err := s.activeFilePath("workspace.WorkspaceFileSet", filename) + if err != nil { + return err } - - path := filepath.Join(s.rootPath, s.activeWorkspace, "files", filename) return s.medium.Write(path, content) } // HandleIPCEvents handles workspace-related IPC messages. -func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error { +func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) core.Result { switch m := msg.(type) { case map[string]any: action, _ := m["action"].(string) @@ -135,15 +155,21 @@ func (s *Service) HandleIPCEvents(c *core.Core, msg core.Message) error { case "workspace.create": id, _ := m["identifier"].(string) pass, _ := m["password"].(string) - _, err := s.CreateWorkspace(id, pass) - return err + wsID, err := s.CreateWorkspace(id, pass) + if err != nil { + return core.Result{} + } + return core.Result{Value: wsID, OK: true} case "workspace.switch": name, _ := m["name"].(string) - return s.SwitchWorkspace(name) + if err := s.SwitchWorkspace(name); err != nil { + return core.Result{} + } + return core.Result{OK: true} } } - return nil + return core.Result{OK: true} } -// Ensure Service implements core.Workspace. -var _ core.Workspace = (*Service)(nil) +// Ensure Service implements Workspace. +var _ Workspace = (*Service)(nil) diff --git a/workspace/service_test.go b/workspace/service_test.go index 852d526..1cab667 100644 --- a/workspace/service_test.go +++ b/workspace/service_test.go @@ -1,32 +1,25 @@ package workspace import ( - "os" "path/filepath" "testing" + core "dappco.re/go/core" "forge.lthn.ai/core/go-crypt/crypt/openpgp" - core "forge.lthn.ai/core/go/pkg/core" "github.com/stretchr/testify/assert" ) func TestWorkspace(t *testing.T) { - // Setup core with crypt service - c, _ := core.New( - core.WithName("crypt", openpgp.New), - ) - - tempHome, _ := os.MkdirTemp("", "core-test-home") - defer os.RemoveAll(tempHome) - - // Mock os.UserHomeDir by setting HOME env - oldHome := os.Getenv("HOME") - os.Setenv("HOME", tempHome) - defer os.Setenv("HOME", oldHome) - - s_any, err := New(c) + c := core.New() + pgpSvc, err := openpgp.New(nil) assert.NoError(t, err) - s := s_any.(*Service) + + tempHome := t.TempDir() + t.Setenv("HOME", tempHome) + + svc, err := New(c, pgpSvc.(cryptProvider)) + assert.NoError(t, err) + s := svc.(*Service) // Test CreateWorkspace id, err := s.CreateWorkspace("test-user", "pass123") -- 2.45.3