forked from ryyst/kalzu-value-store
First proto type for issue #3, initial root account.
This commit is contained in:
27
auth/auth.go
27
auth/auth.go
@@ -39,7 +39,7 @@ func NewAuthService(db *badger.DB, logger *logrus.Logger) *AuthService {
|
|||||||
// StoreAPIToken stores an API token in BadgerDB with TTL
|
// StoreAPIToken stores an API token in BadgerDB with TTL
|
||||||
func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes []string, expiresAt int64) error {
|
func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes []string, expiresAt int64) error {
|
||||||
tokenHash := utils.HashToken(tokenString)
|
tokenHash := utils.HashToken(tokenString)
|
||||||
|
|
||||||
apiToken := types.APIToken{
|
apiToken := types.APIToken{
|
||||||
TokenHash: tokenHash,
|
TokenHash: tokenHash,
|
||||||
UserUUID: userUUID,
|
UserUUID: userUUID,
|
||||||
@@ -55,13 +55,13 @@ func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes
|
|||||||
|
|
||||||
return s.db.Update(func(txn *badger.Txn) error {
|
return s.db.Update(func(txn *badger.Txn) error {
|
||||||
entry := badger.NewEntry([]byte(TokenStorageKey(tokenHash)), tokenData)
|
entry := badger.NewEntry([]byte(TokenStorageKey(tokenHash)), tokenData)
|
||||||
|
|
||||||
// Set TTL to the token expiration time
|
// Set TTL to the token expiration time
|
||||||
ttl := time.Until(time.Unix(expiresAt, 0))
|
ttl := time.Until(time.Unix(expiresAt, 0))
|
||||||
if ttl > 0 {
|
if ttl > 0 {
|
||||||
entry = entry.WithTTL(ttl)
|
entry = entry.WithTTL(ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
return txn.SetEntry(entry)
|
return txn.SetEntry(entry)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -69,7 +69,7 @@ func (s *AuthService) StoreAPIToken(tokenString string, userUUID string, scopes
|
|||||||
// GetAPIToken retrieves an API token from BadgerDB by hash
|
// GetAPIToken retrieves an API token from BadgerDB by hash
|
||||||
func (s *AuthService) GetAPIToken(tokenHash string) (*types.APIToken, error) {
|
func (s *AuthService) GetAPIToken(tokenHash string) (*types.APIToken, error) {
|
||||||
var apiToken types.APIToken
|
var apiToken types.APIToken
|
||||||
|
|
||||||
err := s.db.View(func(txn *badger.Txn) error {
|
err := s.db.View(func(txn *badger.Txn) error {
|
||||||
item, err := txn.Get([]byte(TokenStorageKey(tokenHash)))
|
item, err := txn.Get([]byte(TokenStorageKey(tokenHash)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -202,4 +202,21 @@ func GetAuthContext(ctx context.Context) *AuthContext {
|
|||||||
return authCtx
|
return authCtx
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasUsers checks if any users exist in the database
|
||||||
|
func (s *AuthService) HasUsers() (bool, error) {
|
||||||
|
var has bool
|
||||||
|
err := s.db.View(func(txn *badger.Txn) error {
|
||||||
|
opts := badger.DefaultIteratorOptions
|
||||||
|
opts.PrefetchValues = false // Only need keys
|
||||||
|
it := txn.NewIterator(opts)
|
||||||
|
defer it.Close()
|
||||||
|
|
||||||
|
prefix := []byte("user:") // Adjust if UserStorageKey uses a different prefix
|
||||||
|
it.Seek(prefix)
|
||||||
|
has = it.ValidForPrefix(prefix)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return has, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,15 +8,18 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v4"
|
"github.com/dgraph-io/badger/v4"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"kvs/auth"
|
"kvs/auth"
|
||||||
"kvs/cluster"
|
"kvs/cluster"
|
||||||
"kvs/storage"
|
"kvs/storage"
|
||||||
"kvs/types"
|
"kvs/types"
|
||||||
|
"kvs/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server represents the KVS node
|
// Server represents the KVS node
|
||||||
@@ -117,6 +120,89 @@ func NewServer(config *types.Config) (*Server, error) {
|
|||||||
// Initialize authentication service
|
// Initialize authentication service
|
||||||
server.authService = auth.NewAuthService(db, logger)
|
server.authService = auth.NewAuthService(db, logger)
|
||||||
|
|
||||||
|
// New: Initial root account setup for empty DB with no seeds
|
||||||
|
if len(config.SeedNodes) == 0 {
|
||||||
|
hasUsers, err := server.authService.HasUsers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to check for existing users: %v", err)
|
||||||
|
}
|
||||||
|
if !hasUsers {
|
||||||
|
server.logger.Info("Detected empty database with no seed nodes; creating initial root account")
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
// Create admin group
|
||||||
|
adminGroupUUID := uuid.NewString()
|
||||||
|
hashedGroupName := utils.HashGroupName("admin") // Adjust if function name differs
|
||||||
|
adminGroup := types.Group{
|
||||||
|
UUID: adminGroupUUID,
|
||||||
|
Name: hashedGroupName,
|
||||||
|
CreatedAt: now, // Add if field exists; remove otherwise
|
||||||
|
// Members: []string{}, // Add if needed; e.g., add root later
|
||||||
|
}
|
||||||
|
groupData, err := json.Marshal(adminGroup)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal admin group: %v", err)
|
||||||
|
}
|
||||||
|
err = db.Update(func(txn *badger.Txn) error {
|
||||||
|
return txn.Set([]byte(GroupStorageKey(adminGroupUUID)), groupData)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store admin group: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create root user
|
||||||
|
rootUUID := uuid.NewString()
|
||||||
|
hashedNickname := utils.HashUserNickname("root") // Adjust if function name differs
|
||||||
|
rootUser := types.User{
|
||||||
|
UUID: rootUUID,
|
||||||
|
Nickname: hashedNickname,
|
||||||
|
Groups: []string{adminGroupUUID},
|
||||||
|
CreatedAt: now, // Add if field exists; remove otherwise
|
||||||
|
}
|
||||||
|
userData, err := json.Marshal(rootUser)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal root user: %v", err)
|
||||||
|
}
|
||||||
|
err = db.Update(func(txn *badger.Txn) error {
|
||||||
|
return txn.Set([]byte(UserStorageKey(rootUUID)), userData)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store root user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optionally update group members if bidirectional
|
||||||
|
// adminGroup.Members = append(adminGroup.Members, rootUUID)
|
||||||
|
// Update group in DB if needed...
|
||||||
|
|
||||||
|
// Generate and store API token
|
||||||
|
scopes := []string{"admin", "read", "write", "create", "delete"}
|
||||||
|
expirationHours := 8760 // 1 year
|
||||||
|
tokenString, expiresAt, err := auth.GenerateJWT(rootUUID, scopes, expirationHours)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to generate JWT: %v", err)
|
||||||
|
}
|
||||||
|
err = server.authService.StoreAPIToken(tokenString, rootUUID, scopes, expiresAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store API token: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the details securely (only once, to stderr)
|
||||||
|
fmt.Fprintf(os.Stderr, `
|
||||||
|
***************************************************************************
|
||||||
|
WARNING: Initial root user created for new server instance.
|
||||||
|
Save this information securely—it will not be shown again.
|
||||||
|
|
||||||
|
Root User UUID: %s
|
||||||
|
API Token (Bearer): %s
|
||||||
|
Expires At: %s (UTC)
|
||||||
|
|
||||||
|
Use this token for authentication in API requests. Change or revoke it immediately via the API for security.
|
||||||
|
***************************************************************************
|
||||||
|
`, rootUUID, tokenString, time.Unix(expiresAt, 0).UTC())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize Merkle tree using cluster service
|
// Initialize Merkle tree using cluster service
|
||||||
if err := server.syncService.InitializeMerkleTree(); err != nil {
|
if err := server.syncService.InitializeMerkleTree(); err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize Merkle tree: %v", err)
|
return nil, fmt.Errorf("failed to initialize Merkle tree: %v", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user