1package db
2
3import (
4 "crypto/rand"
5 "database/sql"
6 "encoding/hex"
7 "fmt"
8 "log"
9 "strings"
10 "time"
11)
12
13// Registration represents a knot registration. Knot would've been a better
14// name but we're stuck with this for historical reasons.
15type Registration struct {
16 Id int64
17 Domain string
18 ByDid string
19 Created *time.Time
20 Registered *time.Time
21}
22
23func (r *Registration) Status() Status {
24 if r.Registered != nil {
25 return Registered
26 } else {
27 return Pending
28 }
29}
30
31type Status uint32
32
33const (
34 Registered Status = iota
35 Pending
36)
37
38// returns registered status, did of owner, error
39func RegistrationsByDid(e Execer, did string) ([]Registration, error) {
40 var registrations []Registration
41
42 rows, err := e.Query(`
43 select id, domain, did, created, registered from registrations
44 where did = ?
45 `, did)
46 if err != nil {
47 return nil, err
48 }
49
50 for rows.Next() {
51 var createdAt *string
52 var registeredAt *string
53 var registration Registration
54 err = rows.Scan(®istration.Id, ®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)
55
56 if err != nil {
57 log.Println(err)
58 } else {
59 createdAtTime, _ := time.Parse(time.RFC3339, *createdAt)
60 var registeredAtTime *time.Time
61 if registeredAt != nil {
62 x, _ := time.Parse(time.RFC3339, *registeredAt)
63 registeredAtTime = &x
64 }
65
66 registration.Created = &createdAtTime
67 registration.Registered = registeredAtTime
68 registrations = append(registrations, registration)
69 }
70 }
71
72 return registrations, nil
73}
74
75// returns registered status, did of owner, error
76func RegistrationByDomain(e Execer, domain string) (*Registration, error) {
77 var createdAt *string
78 var registeredAt *string
79 var registration Registration
80
81 err := e.QueryRow(`
82 select id, domain, did, created, registered from registrations
83 where domain = ?
84 `, domain).Scan(®istration.Id, ®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)
85
86 if err != nil {
87 if err == sql.ErrNoRows {
88 return nil, nil
89 } else {
90 return nil, err
91 }
92 }
93
94 createdAtTime, _ := time.Parse(time.RFC3339, *createdAt)
95 var registeredAtTime *time.Time
96 if registeredAt != nil {
97 x, _ := time.Parse(time.RFC3339, *registeredAt)
98 registeredAtTime = &x
99 }
100
101 registration.Created = &createdAtTime
102 registration.Registered = registeredAtTime
103
104 return ®istration, nil
105}
106
107func genSecret() string {
108 key := make([]byte, 32)
109 rand.Read(key)
110 return hex.EncodeToString(key)
111}
112
113func GenerateRegistrationKey(e Execer, domain, did string) (string, error) {
114 // sanity check: does this domain already have a registration?
115 reg, err := RegistrationByDomain(e, domain)
116 if err != nil {
117 return "", err
118 }
119
120 // registration is open
121 if reg != nil {
122 switch reg.Status() {
123 case Registered:
124 // already registered by `owner`
125 return "", fmt.Errorf("%s already registered by %s", domain, reg.ByDid)
126 case Pending:
127 // TODO: be loud about this
128 log.Printf("%s registered by %s, status pending", domain, reg.ByDid)
129 }
130 }
131
132 secret := genSecret()
133
134 _, err = e.Exec(`
135 insert into registrations (domain, did, secret)
136 values (?, ?, ?)
137 on conflict(domain) do update set did = excluded.did, secret = excluded.secret, created = excluded.created
138 `, domain, did, secret)
139
140 if err != nil {
141 return "", err
142 }
143
144 return secret, nil
145}
146
147func GetRegistrationKey(e Execer, domain string) (string, error) {
148 res := e.QueryRow(`select secret from registrations where domain = ?`, domain)
149
150 var secret string
151 err := res.Scan(&secret)
152 if err != nil || secret == "" {
153 return "", err
154 }
155
156 return secret, nil
157}
158
159func GetCompletedRegistrations(e Execer) ([]string, error) {
160 rows, err := e.Query(`select domain from registrations where registered not null`)
161 if err != nil {
162 return nil, err
163 }
164
165 var domains []string
166 for rows.Next() {
167 var domain string
168 err = rows.Scan(&domain)
169
170 if err != nil {
171 log.Println(err)
172 } else {
173 domains = append(domains, domain)
174 }
175 }
176
177 if err = rows.Err(); err != nil {
178 return nil, err
179 }
180
181 return domains, nil
182}
183
184func Register(e Execer, domain string) error {
185 _, err := e.Exec(`
186 update registrations
187 set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
188 where domain = ?;
189 `, domain)
190
191 return err
192}
193
194func AddKnot(e Execer, domain, did string) error {
195 _, err := e.Exec(`
196 insert into registrations (domain, did)
197 values (?, ?)
198 `, domain, did)
199 return err
200}
201
202func DeleteKnot(e Execer, filters ...filter) error {
203 var conditions []string
204 var args []any
205 for _, filter := range filters {
206 conditions = append(conditions, filter.Condition())
207 args = append(args, filter.Arg()...)
208 }
209
210 whereClause := ""
211 if conditions != nil {
212 whereClause = " where " + strings.Join(conditions, " and ")
213 }
214
215 query := fmt.Sprintf(`delete from registrations %s`, whereClause)
216
217 _, err := e.Exec(query, args...)
218 return err
219}