Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
175
native/desktop/maplefile/internal/config/userdata.go
Normal file
175
native/desktop/maplefile/internal/config/userdata.go
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
// internal/config/userdata.go
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetUserDataDir returns the appropriate directory for storing application data
|
||||
// following platform-specific conventions:
|
||||
// - Windows: %LOCALAPPDATA%\{appName}
|
||||
// - macOS: ~/Library/Application Support/{appName}
|
||||
// - Linux: ~/.local/share/{appName} (or $XDG_DATA_HOME/{appName})
|
||||
func GetUserDataDir(appName string) (string, error) {
|
||||
var baseDir string
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
// Use LOCALAPPDATA for application data on Windows
|
||||
baseDir = os.Getenv("LOCALAPPDATA")
|
||||
if baseDir == "" {
|
||||
// Fallback to APPDATA if LOCALAPPDATA is not set
|
||||
baseDir = os.Getenv("APPDATA")
|
||||
if baseDir == "" {
|
||||
// Last resort: use UserConfigDir
|
||||
baseDir, err = os.UserConfigDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "darwin":
|
||||
// Use ~/Library/Application Support on macOS
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
baseDir = filepath.Join(home, "Library", "Application Support")
|
||||
|
||||
default:
|
||||
// Linux and other Unix-like systems
|
||||
// Follow XDG Base Directory Specification
|
||||
if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
|
||||
baseDir = xdgData
|
||||
} else {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
baseDir = filepath.Join(home, ".local", "share")
|
||||
}
|
||||
}
|
||||
|
||||
// Combine with app name
|
||||
appDataDir := filepath.Join(baseDir, appName)
|
||||
|
||||
// Create the directory if it doesn't exist with restrictive permissions
|
||||
if err := os.MkdirAll(appDataDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return appDataDir, nil
|
||||
}
|
||||
|
||||
// GetUserSpecificDataDir returns the data directory for a specific user.
|
||||
// User data is isolated by hashing the email to create a unique directory name.
|
||||
// This ensures:
|
||||
// 1. Different users have completely separate storage
|
||||
// 2. Email addresses are not exposed in directory names
|
||||
// 3. The same user always gets the same directory
|
||||
//
|
||||
// Directory structure:
|
||||
//
|
||||
// {appDataDir}/users/{emailHash}/
|
||||
// ├── local_files/ # File and collection metadata (LevelDB)
|
||||
// ├── sync_state/ # Sync state (LevelDB)
|
||||
// ├── cache/ # Application cache (LevelDB)
|
||||
// └── files/ # Downloaded decrypted files
|
||||
// └── {collectionId}/
|
||||
// └── {filename}
|
||||
func GetUserSpecificDataDir(appName, userEmail string) (string, error) {
|
||||
if userEmail == "" {
|
||||
return "", nil // No user logged in, return empty
|
||||
}
|
||||
|
||||
appDataDir, err := GetUserDataDir(appName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Hash the email to create a privacy-preserving directory name
|
||||
emailHash := hashEmail(userEmail)
|
||||
|
||||
// Create user-specific directory
|
||||
userDir := filepath.Join(appDataDir, "users", emailHash)
|
||||
|
||||
// Create the directory with restrictive permissions (owner only)
|
||||
if err := os.MkdirAll(userDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return userDir, nil
|
||||
}
|
||||
|
||||
// GetUserFilesDir returns the directory where decrypted files are stored for a user.
|
||||
// Files are organized by collection: {userDir}/files/{collectionId}/{filename}
|
||||
func GetUserFilesDir(appName, userEmail string) (string, error) {
|
||||
userDir, err := GetUserSpecificDataDir(appName, userEmail)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if userDir == "" {
|
||||
return "", nil // No user logged in
|
||||
}
|
||||
|
||||
filesDir := filepath.Join(userDir, "files")
|
||||
|
||||
// Create with restrictive permissions
|
||||
if err := os.MkdirAll(filesDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filesDir, nil
|
||||
}
|
||||
|
||||
// hashEmail creates a SHA256 hash of the email address (lowercase, trimmed).
|
||||
// Returns a shortened hash (first 16 characters) for more readable directory names
|
||||
// while still maintaining uniqueness.
|
||||
func hashEmail(email string) string {
|
||||
// Normalize email: lowercase and trim whitespace
|
||||
normalizedEmail := strings.ToLower(strings.TrimSpace(email))
|
||||
|
||||
// Create SHA256 hash
|
||||
hash := sha256.Sum256([]byte(normalizedEmail))
|
||||
|
||||
// Return first 16 characters of hex representation (64 bits of entropy is sufficient)
|
||||
return hex.EncodeToString(hash[:])[:16]
|
||||
}
|
||||
|
||||
// GetEmailHashForPath returns the hash that would be used for a user's directory.
|
||||
// This can be used to check if a user's data exists without revealing the email.
|
||||
func GetEmailHashForPath(userEmail string) string {
|
||||
if userEmail == "" {
|
||||
return ""
|
||||
}
|
||||
return hashEmail(userEmail)
|
||||
}
|
||||
|
||||
// GetUserSearchIndexDir returns the directory where the Bleve search index is stored.
|
||||
// Returns: {userDir}/search/index.bleve
|
||||
func GetUserSearchIndexDir(appName, userEmail string) (string, error) {
|
||||
userDir, err := GetUserSpecificDataDir(appName, userEmail)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if userDir == "" {
|
||||
return "", nil // No user logged in
|
||||
}
|
||||
|
||||
searchIndexPath := filepath.Join(userDir, "search", "index.bleve")
|
||||
|
||||
// Create parent directory with restrictive permissions
|
||||
searchDir := filepath.Join(userDir, "search")
|
||||
if err := os.MkdirAll(searchDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return searchIndexPath, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue