Go · 3160 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 package sigverify
4
5 import (
6 "context"
7 "errors"
8
9 "github.com/jackc/pgx/v5"
10
11 usersdb "github.com/tenseleyFlow/shithub/internal/users/sqlc"
12 )
13
14 // DBTX is the pgx interface the sqlc-backed Lookups need. Matches the
15 // sqlc-generated DBTX exactly so callers can pass either a *pgxpool.Pool
16 // or a pgx.Tx without conversion.
17 type DBTX interface {
18 usersdb.DBTX
19 }
20
21 // SQLCLookups is the production Lookups implementation, backed by the
22 // usersdb-generated queries. Used by the orchestrator when invoked
23 // from the backfill worker, the verification render path, and the
24 // commits REST handler.
25 type SQLCLookups struct {
26 DB DBTX
27 Q *usersdb.Queries
28 }
29
30 // NewSQLCLookups constructs a Lookups bound to the given DB handle.
31 // The handle can be a connection pool (for concurrent reads) or a tx
32 // (for invalidation-followed-by-re-verify flows that need to see
33 // uncommitted writes within the same transaction).
34 func NewSQLCLookups(db DBTX) *SQLCLookups {
35 return &SQLCLookups{DB: db, Q: usersdb.New()}
36 }
37
38 // SubkeyByFingerprint resolves a 40-hex fingerprint to a Subkey row.
39 // Returns (_, false, nil) on miss; only DB errors propagate.
40 func (l *SQLCLookups) SubkeyByFingerprint(ctx context.Context, fingerprint string) (Subkey, bool, error) {
41 row, err := l.Q.GetUserGPGSubkeyByFingerprint(ctx, l.DB, fingerprint)
42 if errors.Is(err, pgx.ErrNoRows) {
43 return Subkey{}, false, nil
44 }
45 if err != nil {
46 return Subkey{}, false, err
47 }
48 sk := Subkey{
49 ID: row.ID,
50 GPGKeyID: row.GpgKeyID,
51 Fingerprint: row.Fingerprint,
52 KeyID: row.KeyID,
53 CanSign: row.CanSign,
54 CanEncryptComms: row.CanEncryptComms,
55 CanEncryptStorage: row.CanEncryptStorage,
56 CanCertify: row.CanCertify,
57 }
58 if row.ExpiresAt.Valid {
59 sk.ExpiresAt = row.ExpiresAt.Time
60 }
61 if row.RevokedAt.Valid {
62 sk.RevokedAt = row.RevokedAt.Time
63 }
64 return sk, true, nil
65 }
66
67 // GPGKeyByID resolves a primary user_gpg_keys row by id (NOT scoped
68 // to a user_id — the verification path discovers user_id from the
69 // row itself). Returns (_, false, nil) on miss; only DB errors
70 // propagate.
71 func (l *SQLCLookups) GPGKeyByID(ctx context.Context, id int64) (GPGKey, bool, error) {
72 row, err := l.Q.GetUserGPGKeyForVerification(ctx, l.DB, id)
73 if errors.Is(err, pgx.ErrNoRows) {
74 return GPGKey{}, false, nil
75 }
76 if err != nil {
77 return GPGKey{}, false, err
78 }
79 return GPGKey{
80 ID: row.ID,
81 UserID: row.UserID,
82 Fingerprint: row.Fingerprint,
83 KeyID: row.KeyID,
84 Armored: row.Armored,
85 }, true, nil
86 }
87
88 // UserEmailsByUserID returns every email associated with the user
89 // (verified or not) so the orchestrator can run the bad_email vs
90 // unverified_email vs valid discrimination.
91 func (l *SQLCLookups) UserEmailsByUserID(ctx context.Context, userID int64) ([]UserEmail, error) {
92 rows, err := l.Q.ListUserEmailsForUser(ctx, l.DB, userID)
93 if err != nil {
94 return nil, err
95 }
96 emails := make([]UserEmail, 0, len(rows))
97 for _, row := range rows {
98 emails = append(emails, UserEmail{
99 Email: row.Email,
100 Verified: row.Verified,
101 })
102 }
103 return emails, nil
104 }
105