package services import ( "context" "crypto/md5" "encoding/base64" "fmt" "strings" "time" "github.com/google/uuid" "github.com/mrhid6/keymanager/server/internal/db" "github.com/mrhid6/keymanager/server/internal/models" "go.mongodb.org/mongo-driver/v2/bson" ) func computeFingerprint(pubKey string) string { parts := strings.Fields(pubKey) if len(parts) < 2 { return "" } raw, err := base64.StdEncoding.DecodeString(parts[1]) if err != nil { return "" } sum := md5.Sum(raw) var pairs []string for _, b := range sum { pairs = append(pairs, fmt.Sprintf("%02x", b)) } return "MD5:" + strings.Join(pairs, ":") } func CreateKey(label, publicKey, source, generatedByServerID string) (*models.Key, error) { key := &models.Key{ KeyID: uuid.NewString(), Label: label, PublicKey: publicKey, Fingerprint: computeFingerprint(publicKey), Source: source, GeneratedByServerID: generatedByServerID, CreatedAt: time.Now(), } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := db.Col("keys").InsertOne(ctx, key) if err != nil { return nil, err } return key, nil } func GetKey(keyID string) (*models.Key, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var key models.Key err := db.Col("keys").FindOne(ctx, bson.M{"key_id": keyID}).Decode(&key) if err != nil { return nil, err } return &key, nil } func ListKeys() ([]models.Key, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cursor, err := db.Col("keys").Find(ctx, bson.M{}) if err != nil { return nil, err } defer cursor.Close(ctx) var keys []models.Key if err := cursor.All(ctx, &keys); err != nil { return nil, err } return keys, nil } func DeleteKey(keyID string) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() _, err := db.Col("keys").DeleteOne(ctx, bson.M{"key_id": keyID}) return err } func AssignKey(keyID, serverID string) (*models.Assignment, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // Check if already assigned and active var existing models.Assignment err := db.Col("assignments").FindOne(ctx, bson.M{ "key_id": keyID, "server_id": serverID, "revoked_at": nil, }).Decode(&existing) if err == nil { return &existing, nil } a := &models.Assignment{ KeyID: keyID, ServerID: serverID, AssignedAt: time.Now(), } _, err = db.Col("assignments").InsertOne(ctx, a) if err != nil { return nil, err } return a, nil } func RevokeAssignment(keyID, serverID string) error { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() now := time.Now() _, err := db.Col("assignments").UpdateOne(ctx, bson.M{"key_id": keyID, "server_id": serverID, "revoked_at": nil}, bson.M{"$set": bson.M{"revoked_at": now}}, ) return err } func GetAssignmentsForKey(keyID string) ([]models.Assignment, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cursor, err := db.Col("assignments").Find(ctx, bson.M{"key_id": keyID, "revoked_at": nil}) if err != nil { return nil, err } defer cursor.Close(ctx) var assignments []models.Assignment if err := cursor.All(ctx, &assignments); err != nil { return nil, err } return assignments, nil } type AssignmentWithServer struct { models.Assignment `bson:",inline"` Server *models.Server `json:"server,omitempty"` } func GetAssignmentsWithServers(keyID string) ([]AssignmentWithServer, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cursor, err := db.Col("assignments").Find(ctx, bson.M{"key_id": keyID}) if err != nil { return nil, err } defer cursor.Close(ctx) var assignments []models.Assignment if err := cursor.All(ctx, &assignments); err != nil { return nil, err } result := make([]AssignmentWithServer, 0, len(assignments)) for _, a := range assignments { item := AssignmentWithServer{Assignment: a} var srv models.Server if err := db.Col("servers").FindOne(ctx, bson.M{"server_id": a.ServerID}).Decode(&srv); err == nil { item.Server = &srv } result = append(result, item) } return result, nil } type AssignmentWithKey struct { models.Assignment `bson:",inline"` Key *models.Key `json:"key,omitempty"` } func GetAssignmentsWithKeysForServer(serverID string) ([]AssignmentWithKey, error) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() cursor, err := db.Col("assignments").Find(ctx, bson.M{"server_id": serverID}) if err != nil { return nil, err } defer cursor.Close(ctx) var assignments []models.Assignment if err := cursor.All(ctx, &assignments); err != nil { return nil, err } result := make([]AssignmentWithKey, 0, len(assignments)) for _, a := range assignments { item := AssignmentWithKey{Assignment: a} var key models.Key if err := db.Col("keys").FindOne(ctx, bson.M{"key_id": a.KeyID}).Decode(&key); err == nil { item.Key = &key } result = append(result, item) } return result, nil }