changed auth method
This commit is contained in:
1 parent
1daa409a73
commit
d7a51a0f60
5 files changed
+171
-18
No files matched your search
+2
-6
@@ -13,12 +13,8 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000
|
||||
# URL of your Vaultwarden instance (no trailing slash)
|
||||
VAULTWARDEN_URL=https://vault.yourdomain.com
|
||||
|
||||
# Authentication Method 1: API Keys (RECOMMENDED - doesn't expire)
|
||||
VAULTWARDEN_CLIENT_ID=user.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
VAULTWARDEN_CLIENT_SECRET=your-long-client-secret-here
|
||||
|
||||
# Authentication Method 2: Session Token (LEGACY - expires after some time)
|
||||
# VAULTWARDEN_ACCESS_TOKEN=your-session-token-here
|
||||
# Authentication
|
||||
VAULTWARDEN_ACCESS_TOKEN=your-session-token-here
|
||||
|
||||
# Performance & Caching
|
||||
# How long to cache secrets in memory (reduces API calls)
|
||||
|
||||
+14
-10
@@ -30,18 +30,22 @@ func main() {
|
||||
|
||||
// Initialize Vaultwarden client
|
||||
var vaultClient *vaultwarden.Client
|
||||
if cfg.VaultwardenClientID != "" || cfg.VaultwardenSecret != "" {
|
||||
if cfg.VaultwardenClientID == "" || cfg.VaultwardenSecret == "" {
|
||||
logger.Error.Fatal("Both VaultwardenClientID and VaultwardenSecret must be provided for API key authentication")
|
||||
|
||||
vaultwardenEmail := os.Getenv("VAULTWARDEN_EMAIL")
|
||||
vaultwardenPassword := os.Getenv("VAULTWARDEN_PASSWORD")
|
||||
|
||||
if vaultwardenEmail != "" && vaultwardenPassword != "" {
|
||||
logger.Info.Println("Initializing Bitwarden CLI with credentials")
|
||||
sessionToken, err := vaultwarden.InitializeBitwardenCLI(cfg.VaultwardenURL, vaultwardenEmail, vaultwardenPassword)
|
||||
if err != nil {
|
||||
logger.Error.Fatalf("Failed to initialize Bitwarden CLI: %v", err)
|
||||
}
|
||||
logger.Info.Println("Using API key authentication (recommended)")
|
||||
vaultClient = vaultwarden.NewClientWithAuth(cfg.VaultwardenURL, cfg.VaultwardenClientID, cfg.VaultwardenSecret, cfg.CacheTTL)
|
||||
} else {
|
||||
if cfg.VaultwardenToken == "" {
|
||||
logger.Error.Fatal("VaultwardenToken must be provided for legacy session token authentication")
|
||||
}
|
||||
logger.Warn.Println("Using session token authentication (legacy - token will expire!)")
|
||||
vaultClient = vaultwarden.NewClient(cfg.VaultwardenURL, sessionToken, cfg.CacheTTL)
|
||||
} else if cfg.VaultwardenToken != "" {
|
||||
logger.Warn.Println("Using provided session token (will expire)")
|
||||
vaultClient = vaultwarden.NewClient(cfg.VaultwardenURL, cfg.VaultwardenToken, cfg.CacheTTL)
|
||||
} else {
|
||||
logger.Error.Fatal("No authentication method configured. Set VAULTWARDEN_EMAIL+VAULTWARDEN_PASSWORD or VAULTWARDEN_ACCESS_TOKEN")
|
||||
}
|
||||
|
||||
// Initialize handlers
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -66,11 +68,17 @@ func NewClient(baseURL, token string, cacheTTL time.Duration) *Client {
|
||||
enabled: cacheTTL > 0,
|
||||
}
|
||||
|
||||
// Start cache cleanup goroutine if caching is enabled
|
||||
if cacheTTL > 0 {
|
||||
go cache.startCleanup(cacheTTL)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
if sessionData, err := os.ReadFile("/tmp/bw_session"); err == nil {
|
||||
token = strings.TrimSpace(string(sessionData))
|
||||
logger.Info.Println("Loaded session token from /tmp/bw_session")
|
||||
}
|
||||
}
|
||||
|
||||
return &Client{
|
||||
baseURL: baseURL,
|
||||
token: token,
|
||||
@@ -195,8 +203,10 @@ func (c *Client) fetchSecret(name string) (string, error) {
|
||||
return "", fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
// Search for the secret by name
|
||||
logger.Info.Printf("Found %d ciphers from Vaultwarden API", len(cipherResp.Data))
|
||||
|
||||
for _, cipher := range cipherResp.Data {
|
||||
logger.Info.Printf("Cipher name: '%s' (looking for: '%s')", cipher.Name, name)
|
||||
if cipher.Name == name {
|
||||
return c.extractSecretValue(cipher)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package vaultwarden
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/thijsherman/vaultwarden-api/pkg/logger"
|
||||
)
|
||||
|
||||
func InitializeBitwardenCLI(serverURL, email, password string) (string, error) {
|
||||
logger.Info.Println("Initializing Bitwarden CLI...")
|
||||
|
||||
cmd := exec.Command("bw", "config", "server", serverURL)
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
return "", fmt.Errorf("config server failed: %w - %s", err, output)
|
||||
}
|
||||
|
||||
cmd = exec.Command("bw", "login", email, "--passwordenv", "BW_PASSWORD", "--raw")
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("BW_PASSWORD=%s", password))
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
outputStr := strings.TrimSpace(string(output))
|
||||
if err != nil && !strings.Contains(outputStr, "already logged in") {
|
||||
return "", fmt.Errorf("login failed: %w - %s", err, outputStr)
|
||||
}
|
||||
|
||||
cmd = exec.Command("bw", "unlock", "--passwordenv", "BW_PASSWORD", "--raw")
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("BW_PASSWORD=%s", password))
|
||||
sessionToken, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unlock failed: %w", err)
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(string(sessionToken))
|
||||
if token == "" {
|
||||
return "", fmt.Errorf("received empty session token")
|
||||
}
|
||||
|
||||
logger.Info.Println("Bitwarden CLI initialized successfully")
|
||||
return token, nil
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package vaultwarden
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/thijsherman/vaultwarden-api/pkg/logger"
|
||||
)
|
||||
|
||||
type SessionManager struct {
|
||||
mu sync.RWMutex
|
||||
sessionToken string
|
||||
serverURL string
|
||||
email string
|
||||
password string
|
||||
refreshTicker *time.Ticker
|
||||
stopChan chan bool
|
||||
}
|
||||
|
||||
func NewSessionManager(serverURL, email, password string) *SessionManager {
|
||||
sm := &SessionManager{
|
||||
serverURL: serverURL,
|
||||
email: email,
|
||||
password: password,
|
||||
stopChan: make(chan bool),
|
||||
}
|
||||
|
||||
if err := sm.login(); err != nil {
|
||||
logger.Warn.Printf("Initial login failed: %v - will retry", err)
|
||||
}
|
||||
|
||||
sm.startAutoRefresh()
|
||||
return sm
|
||||
}
|
||||
|
||||
func (sm *SessionManager) GetToken() string {
|
||||
sm.mu.RLock()
|
||||
defer sm.mu.RUnlock()
|
||||
return sm.sessionToken
|
||||
}
|
||||
|
||||
func (sm *SessionManager) login() error {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
cmd := exec.Command("bw", "config", "server", sm.serverURL)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("config server failed: %w", err)
|
||||
}
|
||||
|
||||
if sm.password != "" {
|
||||
cmd = exec.Command("bw", "login", sm.email, "--passwordenv", "BW_PASSWORD")
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("BW_PASSWORD=%s", sm.password))
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil && string(output) != "" && !contains(string(output), "already logged in") {
|
||||
return fmt.Errorf("login failed: %w - %s", err, output)
|
||||
}
|
||||
}
|
||||
|
||||
cmd = exec.Command("bw", "unlock", "--passwordenv", "BW_PASSWORD", "--raw")
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("BW_PASSWORD=%s", sm.password))
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unlock failed: %w", err)
|
||||
}
|
||||
|
||||
sm.sessionToken = string(output)
|
||||
logger.Info.Println("Successfully obtained Bitwarden session token")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *SessionManager) startAutoRefresh() {
|
||||
sm.refreshTicker = time.NewTicker(6 * time.Hour)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-sm.refreshTicker.C:
|
||||
logger.Info.Println("Refreshing Bitwarden session...")
|
||||
if err := sm.login(); err != nil {
|
||||
logger.Error.Printf("Session refresh failed: %v", err)
|
||||
}
|
||||
case <-sm.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (sm *SessionManager) Stop() {
|
||||
sm.refreshTicker.Stop()
|
||||
sm.stopChan <- true
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) && s[:len(substr)] == substr
|
||||
}
|
||||
Reference in new issue
Block a user