~bigbes/sr-ht-dolt

ref: 4520d5cf9385a3a7c18037e0c3dba95ad1874b35 sr-ht-dolt/db/keys.go -rw-r--r-- 4.4 KiB
4520d5cf — Eugene Blikh feat(web): pluggable alternative-view registry for repositories 30 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package db

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	"github.com/lib/pq"

	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)

// DoltKey is a registered Ed25519 credential (dolt creds / dolt login). PubKey
// is the raw 32-byte public key; Comment and LastUsed are nullable in the
// schema and default to "" / nil.
type DoltKey struct {
	ID       int
	UserID   int
	KID      string
	PubKey   []byte
	Comment  string
	Created  time.Time
	LastUsed *time.Time
}

// KeyAuth is everything the Bearer-JWT verifier needs to authenticate a dolt
// keypair request: the stored public key to verify the JWS signature, plus the
// owning user's identity to build a core.Caller. Suspended is derived by the
// caller from UserType == core.UserTypeSuspended.
type KeyAuth struct {
	KeyID    int
	UserID   int
	Username string
	UserType core.UserType
	PubKey   []byte
}

// InsertKey registers a dolt key for a user. kid is base32(SHA-512/224(pubkey))
// in dolt's alphabet; pubkey is the raw 32-byte Ed25519 public key. A duplicate
// kid (dolt_key.kid UNIQUE) is mapped to ErrKeyExists. Returns the created row.
func (s *Store) InsertKey(ctx context.Context, userID int, kid string, pubkey []byte, comment string) (*DoltKey, error) {
	now := time.Now().UTC()
	const q = `
INSERT INTO dolt_key (created, user_id, kid, pubkey, comment)
VALUES ($1, $2, $3, $4, $5)
RETURNING id`
	var cmt any
	if comment != "" {
		cmt = comment
	}
	var id int
	err := s.q.QueryRowContext(ctx, q, now, userID, kid, pubkey, cmt).Scan(&id)
	if err != nil {
		var pqErr *pq.Error
		if errors.As(err, &pqErr) && pqErr.Code == "23505" {
			return nil, ErrKeyExists
		}
		return nil, fmt.Errorf("insert dolt key: %w", err)
	}
	return &DoltKey{
		ID:      id,
		UserID:  userID,
		KID:     kid,
		PubKey:  pubkey,
		Comment: comment,
		Created: now,
	}, nil
}

// KeyByKID resolves a key by its kid and returns the public key together with
// the owning user's identity, for authentication. Returns ErrNotFound if no key
// with that kid is registered.
func (s *Store) KeyByKID(ctx context.Context, kid string) (*KeyAuth, error) {
	const q = `
SELECT k.id, k.pubkey, u.id, COALESCE(u.username, ''), u.user_type
FROM dolt_key k
JOIN "user" u ON u.id = k.user_id
WHERE k.kid = $1`
	var (
		ka       KeyAuth
		userType string
	)
	err := s.q.QueryRowContext(ctx, q, kid).Scan(
		&ka.KeyID, &ka.PubKey, &ka.UserID, &ka.Username, &userType)
	if errors.Is(err, sql.ErrNoRows) {
		return nil, ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("key by kid %s: %w", kid, err)
	}
	ka.UserType = core.UserType(userType)
	return &ka, nil
}

// ListKeysByUser returns all of a user's registered dolt keys, newest first.
func (s *Store) ListKeysByUser(ctx context.Context, userID int) ([]*DoltKey, error) {
	const q = `
SELECT id, user_id, kid, pubkey, COALESCE(comment, ''), created, last_used
FROM dolt_key
WHERE user_id = $1
ORDER BY created DESC, id DESC`
	rows, err := s.q.QueryContext(ctx, q, userID)
	if err != nil {
		return nil, fmt.Errorf("list keys user=%d: %w", userID, err)
	}
	defer rows.Close()
	var keys []*DoltKey
	for rows.Next() {
		var (
			k        DoltKey
			lastUsed sql.NullTime
		)
		if err := rows.Scan(&k.ID, &k.UserID, &k.KID, &k.PubKey,
			&k.Comment, &k.Created, &lastUsed); err != nil {
			return nil, fmt.Errorf("scan key: %w", err)
		}
		if lastUsed.Valid {
			t := lastUsed.Time
			k.LastUsed = &t
		}
		keys = append(keys, &k)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate keys: %w", err)
	}
	return keys, nil
}

// DeleteKey removes one of a user's keys. It is scoped by userID so a user can
// only delete keys they own; a mismatch (or missing id) yields ErrNotFound.
func (s *Store) DeleteKey(ctx context.Context, id, userID int) error {
	res, err := s.q.ExecContext(ctx,
		`DELETE FROM dolt_key WHERE id = $1 AND user_id = $2`, id, userID)
	if err != nil {
		return fmt.Errorf("delete key %d: %w", id, err)
	}
	return requireOne(res, "delete key")
}

// TouchKeyLastUsed stamps a key's last_used with the current time, called after
// a successful keypair authentication. Returns ErrNotFound if the kid vanished
// (e.g. the key was deleted concurrently).
func (s *Store) TouchKeyLastUsed(ctx context.Context, kid string) error {
	res, err := s.q.ExecContext(ctx,
		`UPDATE dolt_key SET last_used = $2 WHERE kid = $1`, kid, time.Now().UTC())
	if err != nil {
		return fmt.Errorf("touch key %s: %w", kid, err)
	}
	return requireOne(res, "touch key")
}