Commit 7d004e2
Eric Bower
·
2026-07-16 22:11:42 -0400 EDT
parent b18549a
feat: block account creation by ip
7 files changed,
+111,
-79
M
Makefile
+2,
-1
1@@ -158,10 +158,11 @@ migrate:
2 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260305_add_forms_table.sql
3 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260503_add_analytics_summary_tables.sql
4 $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260504_add_analytics_summary_indexes.sql
5+ $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260716_block_signups.sql
6 .PHONY: migrate
7
8 latest:
9- $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260504_add_analytics_summary_indexes.sql
10+ $(DOCKER_CMD) exec -i $(DB_CONTAINER) psql -U $(PGUSER) -d $(PGDATABASE) < ./sql/migrations/20260716_block_signups.sql
11 .PHONY: latest
12
13 psql:
+8,
-1
1@@ -31,6 +31,13 @@ func tcast(value any) ([]byte, error) {
2 }
3 }
4
5+type BlockSignups struct {
6+ ID int64 `db:"id"`
7+ IP string `db:"ip"`
8+ Reason string `db:"reason"`
9+ CreatedAt *time.Time `db:"created_at"`
10+}
11+
12 type PublicKey struct {
13 ID string `json:"id" db:"id"`
14 UserID string `json:"user_id" db:"user_id"`
15@@ -559,7 +566,7 @@ var DenyList = []string{
16 }
17
18 type DB interface {
19- RegisterUser(name, pubkey, comment string) (*User, error)
20+ RegisterUser(name, pubkey, comment, ip string) (*User, error)
21 UpdatePublicKey(pubkeyID, name string) (*PublicKey, error)
22 InsertPublicKey(userID, pubkey, name string) error
23 FindKeysByUser(user *User) ([]*PublicKey, error)
+16,
-1
1@@ -91,13 +91,28 @@ func NewDB(databaseUrl string, logger *slog.Logger) *PsqlDB {
2 return d
3 }
4
5-func (me *PsqlDB) RegisterUser(username, pubkey, comment string) (*db.User, error) {
6+func (me *PsqlDB) shouldBlockSingup(ip string) error {
7+ blocked := &db.BlockSignups{}
8+ err := me.Db.Get(blocked, `SELECT * FROM block_signups WHERE ip = $1`, ip)
9+ // an error in this case means the result is empty (no record found)
10+ if err != nil {
11+ return nil
12+ }
13+ return fmt.Errorf("your IP address has been blocked: %s", blocked.Reason)
14+}
15+
16+func (me *PsqlDB) RegisterUser(username, pubkey, comment, ip string) (*db.User, error) {
17 lowerName := strings.ToLower(username)
18 valid, err := me.validateName(lowerName)
19 if !valid {
20 return nil, err
21 }
22
23+ err = me.shouldBlockSingup(ip)
24+ if err != nil {
25+ return nil, err
26+ }
27+
28 tx, err := me.Db.Beginx()
29 if err != nil {
30 return nil, err
+74,
-74
1@@ -190,7 +190,7 @@ func mustInsertPost(t *testing.T, post *db.Post) *db.Post {
2 func TestRegisterUser_Success(t *testing.T) {
3 cleanupTestData(t)
4
5- user, err := testDB.RegisterUser("testuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI testkey", "test comment")
6+ user, err := testDB.RegisterUser("testuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI testkey", "test comment", "")
7 if err != nil {
8 t.Fatalf("RegisterUser failed: %v", err)
9 return
10@@ -210,12 +210,12 @@ func TestRegisterUser_Success(t *testing.T) {
11 func TestRegisterUser_DuplicateName(t *testing.T) {
12 cleanupTestData(t)
13
14- _, err := testDB.RegisterUser("duplicateuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI key1", "comment1")
15+ _, err := testDB.RegisterUser("duplicateuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI key1", "comment1", "")
16 if err != nil {
17 t.Fatalf("first RegisterUser failed: %v", err)
18 }
19
20- _, err = testDB.RegisterUser("duplicateuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI key2", "comment2")
21+ _, err = testDB.RegisterUser("duplicateuser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI key2", "comment2", "")
22 if err == nil {
23 t.Error("expected error for duplicate name, got nil")
24 }
25@@ -224,7 +224,7 @@ func TestRegisterUser_DuplicateName(t *testing.T) {
26 func TestRegisterUser_DeniedName(t *testing.T) {
27 cleanupTestData(t)
28
29- _, err := testDB.RegisterUser("admin", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI adminkey", "comment")
30+ _, err := testDB.RegisterUser("admin", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI adminkey", "comment", "")
31 if err == nil {
32 t.Error("expected error for denied name 'admin', got nil")
33 }
34@@ -233,7 +233,7 @@ func TestRegisterUser_DeniedName(t *testing.T) {
35 func TestRegisterUser_InvalidName(t *testing.T) {
36 cleanupTestData(t)
37
38- _, err := testDB.RegisterUser("user@invalid", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI invalidkey", "comment")
39+ _, err := testDB.RegisterUser("user@invalid", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI invalidkey", "comment", "")
40 if err == nil {
41 t.Error("expected error for invalid name, got nil")
42 }
43@@ -242,7 +242,7 @@ func TestRegisterUser_InvalidName(t *testing.T) {
44 func TestFindUser(t *testing.T) {
45 cleanupTestData(t)
46
47- created, err := testDB.RegisterUser("findme", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findmekey", "comment")
48+ created, err := testDB.RegisterUser("findme", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findmekey", "comment", "")
49 if err != nil {
50 t.Fatalf("RegisterUser failed: %v", err)
51 }
52@@ -259,7 +259,7 @@ func TestFindUser(t *testing.T) {
53 func TestFindUserByName(t *testing.T) {
54 cleanupTestData(t)
55
56- _, err := testDB.RegisterUser("nameduser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI namedkey", "comment")
57+ _, err := testDB.RegisterUser("nameduser", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI namedkey", "comment", "")
58 if err != nil {
59 t.Fatalf("RegisterUser failed: %v", err)
60 }
61@@ -277,7 +277,7 @@ func TestFindUserByPubkey(t *testing.T) {
62 cleanupTestData(t)
63
64 pubkey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pubkeyuser"
65- _, err := testDB.RegisterUser("pubkeyuser", pubkey, "comment")
66+ _, err := testDB.RegisterUser("pubkeyuser", pubkey, "comment", "")
67 if err != nil {
68 t.Fatalf("RegisterUser failed: %v", err)
69 }
70@@ -295,7 +295,7 @@ func TestFindUserByKey(t *testing.T) {
71 cleanupTestData(t)
72
73 pubkey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI forkeyfind"
74- _, err := testDB.RegisterUser("forkeyfind", pubkey, "comment")
75+ _, err := testDB.RegisterUser("forkeyfind", pubkey, "comment", "")
76 if err != nil {
77 t.Fatalf("RegisterUser failed: %v", err)
78 }
79@@ -312,8 +312,8 @@ func TestFindUserByKey(t *testing.T) {
80 func TestFindUsers(t *testing.T) {
81 cleanupTestData(t)
82
83- _, _ = testDB.RegisterUser("user1", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI user1key", "comment")
84- _, _ = testDB.RegisterUser("user2", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI user2key", "comment")
85+ _, _ = testDB.RegisterUser("user1", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI user1key", "comment", "")
86+ _, _ = testDB.RegisterUser("user2", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI user2key", "comment", "")
87
88 users, err := testDB.FindUsers()
89 if err != nil {
90@@ -329,7 +329,7 @@ func TestFindUsers(t *testing.T) {
91 func TestInsertPublicKey_Success(t *testing.T) {
92 cleanupTestData(t)
93
94- user, err := testDB.RegisterUser("keyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI keyowner1", "comment")
95+ user, err := testDB.RegisterUser("keyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI keyowner1", "comment", "")
96 if err != nil {
97 t.Fatalf("RegisterUser failed: %v", err)
98 }
99@@ -351,7 +351,7 @@ func TestInsertPublicKey_Success(t *testing.T) {
100 func TestInsertPublicKey_Duplicate(t *testing.T) {
101 cleanupTestData(t)
102
103- user, _ := testDB.RegisterUser("dupkeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI dupkey", "comment")
104+ user, _ := testDB.RegisterUser("dupkeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI dupkey", "comment", "")
105
106 err := testDB.InsertPublicKey(user.ID, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI dupkey", "same key")
107 if err == nil {
108@@ -362,7 +362,7 @@ func TestInsertPublicKey_Duplicate(t *testing.T) {
109 func TestUpdatePublicKey(t *testing.T) {
110 cleanupTestData(t)
111
112- user, _ := testDB.RegisterUser("updatekeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI updatekeyowner", "original")
113+ user, _ := testDB.RegisterUser("updatekeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI updatekeyowner", "original", "")
114
115 updated, err := testDB.UpdatePublicKey(user.PublicKey.ID, "new-name")
116 if err != nil {
117@@ -376,7 +376,7 @@ func TestUpdatePublicKey(t *testing.T) {
118 func TestFindKeysByUser(t *testing.T) {
119 cleanupTestData(t)
120
121- user, _ := testDB.RegisterUser("multikeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI multikeyowner1", "key1")
122+ user, _ := testDB.RegisterUser("multikeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI multikeyowner1", "key1", "")
123 _ = testDB.InsertPublicKey(user.ID, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI multikeyowner2", "key2")
124
125 keys, err := testDB.FindKeysByUser(user)
126@@ -391,7 +391,7 @@ func TestFindKeysByUser(t *testing.T) {
127 func TestRemoveKeys(t *testing.T) {
128 cleanupTestData(t)
129
130- user, _ := testDB.RegisterUser("removekeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removekeyowner", "key1")
131+ user, _ := testDB.RegisterUser("removekeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removekeyowner", "key1", "")
132 _ = testDB.InsertPublicKey(user.ID, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removekeyowner2", "key2")
133
134 keys, _ := testDB.FindKeysByUser(user)
135@@ -415,7 +415,7 @@ func TestRemoveKeys(t *testing.T) {
136 func TestInsertToken(t *testing.T) {
137 cleanupTestData(t)
138
139- user, _ := testDB.RegisterUser("tokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tokenowner", "comment")
140+ user, _ := testDB.RegisterUser("tokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tokenowner", "comment", "")
141
142 token, err := testDB.InsertToken(user.ID, "my-token")
143 if err != nil {
144@@ -429,7 +429,7 @@ func TestInsertToken(t *testing.T) {
145 func TestUpsertToken(t *testing.T) {
146 cleanupTestData(t)
147
148- user, _ := testDB.RegisterUser("upserttokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI upserttokenowner", "comment")
149+ user, _ := testDB.RegisterUser("upserttokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI upserttokenowner", "comment", "")
150
151 token1, err := testDB.UpsertToken(user.ID, "upsert-token")
152 if err != nil {
153@@ -449,7 +449,7 @@ func TestUpsertToken(t *testing.T) {
154 func TestFindTokensByUser(t *testing.T) {
155 cleanupTestData(t)
156
157- user, _ := testDB.RegisterUser("tokensowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tokensowner", "comment")
158+ user, _ := testDB.RegisterUser("tokensowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tokensowner", "comment", "")
159 _, _ = testDB.InsertToken(user.ID, "token1")
160 _, _ = testDB.InsertToken(user.ID, "token2")
161
162@@ -465,7 +465,7 @@ func TestFindTokensByUser(t *testing.T) {
163 func TestFindUserByToken_Valid(t *testing.T) {
164 cleanupTestData(t)
165
166- user, _ := testDB.RegisterUser("validtokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI validtokenowner", "comment")
167+ user, _ := testDB.RegisterUser("validtokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI validtokenowner", "comment", "")
168 token, _ := testDB.InsertToken(user.ID, "valid-token")
169
170 found, err := testDB.FindUserByToken(token)
171@@ -480,7 +480,7 @@ func TestFindUserByToken_Valid(t *testing.T) {
172 func TestFindUserByToken_Expired(t *testing.T) {
173 cleanupTestData(t)
174
175- user, _ := testDB.RegisterUser("expiredtokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI expiredtokenowner", "comment")
176+ user, _ := testDB.RegisterUser("expiredtokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI expiredtokenowner", "comment", "")
177 token, _ := testDB.InsertToken(user.ID, "expired-token")
178
179 _, err := testDB.Db.Exec("UPDATE tokens SET expires_at = NOW() - INTERVAL '1 day' WHERE token = $1", token)
180@@ -497,7 +497,7 @@ func TestFindUserByToken_Expired(t *testing.T) {
181 func TestRemoveToken(t *testing.T) {
182 cleanupTestData(t)
183
184- user, _ := testDB.RegisterUser("removetokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removetokenowner", "comment")
185+ user, _ := testDB.RegisterUser("removetokenowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removetokenowner", "comment", "")
186 _, _ = testDB.InsertToken(user.ID, "remove-token")
187
188 tokens, _ := testDB.FindTokensByUser(user.ID)
189@@ -521,7 +521,7 @@ func TestRemoveToken(t *testing.T) {
190 func TestInsertPost(t *testing.T) {
191 cleanupTestData(t)
192
193- user, _ := testDB.RegisterUser("postowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postowner", "comment")
194+ user, _ := testDB.RegisterUser("postowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postowner", "comment", "")
195
196 now := time.Now()
197 post := &db.Post{
198@@ -552,7 +552,7 @@ func TestInsertPost(t *testing.T) {
199 func TestUpdatePost(t *testing.T) {
200 cleanupTestData(t)
201
202- user, _ := testDB.RegisterUser("updatepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI updatepostowner", "comment")
203+ user, _ := testDB.RegisterUser("updatepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI updatepostowner", "comment", "")
204
205 now := time.Now()
206 post := mustInsertPost(t, &db.Post{
207@@ -580,7 +580,7 @@ func TestUpdatePost(t *testing.T) {
208 func TestRemovePosts(t *testing.T) {
209 cleanupTestData(t)
210
211- user, _ := testDB.RegisterUser("removepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removepostowner", "comment")
212+ user, _ := testDB.RegisterUser("removepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removepostowner", "comment", "")
213
214 post := mustInsertPost(t, &db.Post{
215 UserID: user.ID,
216@@ -604,7 +604,7 @@ func TestRemovePosts(t *testing.T) {
217 func TestFindPost(t *testing.T) {
218 cleanupTestData(t)
219
220- user, _ := testDB.RegisterUser("findpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findpostowner", "comment")
221+ user, _ := testDB.RegisterUser("findpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findpostowner", "comment", "")
222
223 created := mustInsertPost(t, &db.Post{
224 UserID: user.ID,
225@@ -626,7 +626,7 @@ func TestFindPost(t *testing.T) {
226 func TestFindPostWithFilename(t *testing.T) {
227 cleanupTestData(t)
228
229- user, _ := testDB.RegisterUser("filenamepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI filenamepostowner", "comment")
230+ user, _ := testDB.RegisterUser("filenamepostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI filenamepostowner", "comment", "")
231
232 _ = mustInsertPost(t, &db.Post{
233 UserID: user.ID,
234@@ -648,7 +648,7 @@ func TestFindPostWithFilename(t *testing.T) {
235 func TestFindPostWithSlug(t *testing.T) {
236 cleanupTestData(t)
237
238- user, _ := testDB.RegisterUser("slugpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI slugpostowner", "comment")
239+ user, _ := testDB.RegisterUser("slugpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI slugpostowner", "comment", "")
240
241 _ = mustInsertPost(t, &db.Post{
242 UserID: user.ID,
243@@ -670,7 +670,7 @@ func TestFindPostWithSlug(t *testing.T) {
244 func TestFindPosts(t *testing.T) {
245 cleanupTestData(t)
246
247- user, _ := testDB.RegisterUser("postsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postsowner", "comment")
248+ user, _ := testDB.RegisterUser("postsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postsowner", "comment", "")
249
250 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "post1.md", Slug: "post1", Title: "Post 1", Space: "prose"})
251 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "post2.md", Slug: "post2", Title: "Post 2", Space: "prose"})
252@@ -687,7 +687,7 @@ func TestFindPosts(t *testing.T) {
253 func TestFindPostsByUser(t *testing.T) {
254 cleanupTestData(t)
255
256- user, _ := testDB.RegisterUser("userpostsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI userpostsowner", "comment")
257+ user, _ := testDB.RegisterUser("userpostsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI userpostsowner", "comment", "")
258 now := time.Now()
259
260 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "userpost1.md", Slug: "userpost1", Title: "User Post 1", Space: "prose", PublishAt: &now})
261@@ -706,7 +706,7 @@ func TestFindPostsByUser(t *testing.T) {
262 func TestFindAllPostsByUser(t *testing.T) {
263 cleanupTestData(t)
264
265- user, _ := testDB.RegisterUser("allpostsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI allpostsowner", "comment")
266+ user, _ := testDB.RegisterUser("allpostsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI allpostsowner", "comment", "")
267
268 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "allpost1.md", Slug: "allpost1", Title: "All Post 1", Space: "prose"})
269 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "allpost2.md", Slug: "allpost2", Title: "All Post 2", Space: "prose"})
270@@ -723,7 +723,7 @@ func TestFindAllPostsByUser(t *testing.T) {
271 func TestFindUsersWithPost(t *testing.T) {
272 cleanupTestData(t)
273
274- user, _ := testDB.RegisterUser("feedspostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedspostowner", "comment")
275+ user, _ := testDB.RegisterUser("feedspostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedspostowner", "comment", "")
276
277 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feed.txt", Slug: "feed", Title: "Feed", Space: "feeds"})
278
279@@ -739,7 +739,7 @@ func TestFindUsersWithPost(t *testing.T) {
280 func TestFindExpiredPosts(t *testing.T) {
281 cleanupTestData(t)
282
283- user, _ := testDB.RegisterUser("expiredpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI expiredpostowner", "comment")
284+ user, _ := testDB.RegisterUser("expiredpostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI expiredpostowner", "comment", "")
285
286 expired := time.Now().Add(-24 * time.Hour)
287 _ = mustInsertPost(t, &db.Post{
288@@ -763,7 +763,7 @@ func TestFindExpiredPosts(t *testing.T) {
289 func TestFindPostsByFeed(t *testing.T) {
290 cleanupTestData(t)
291
292- user, _ := testDB.RegisterUser("feedowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedowner", "comment")
293+ user, _ := testDB.RegisterUser("feedowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedowner", "comment", "")
294
295 now := time.Now()
296 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedpost.md", Slug: "feedpost", Title: "Feed Post", Space: "prose", PublishAt: &now})
297@@ -783,7 +783,7 @@ func TestFindPostsByFeed(t *testing.T) {
298 func TestReplaceTagsByPost(t *testing.T) {
299 cleanupTestData(t)
300
301- user, _ := testDB.RegisterUser("tagspostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tagspostowner", "comment")
302+ user, _ := testDB.RegisterUser("tagspostowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tagspostowner", "comment", "")
303 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "tagged.md", Slug: "tagged", Title: "Tagged", Space: "prose"})
304
305 err := testDB.ReplaceTagsByPost([]string{"tag1", "tag2"}, post.ID)
306@@ -800,7 +800,7 @@ func TestReplaceTagsByPost(t *testing.T) {
307 func TestFindUserPostsByTag(t *testing.T) {
308 cleanupTestData(t)
309
310- user, _ := testDB.RegisterUser("usertagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI usertagowner", "comment")
311+ user, _ := testDB.RegisterUser("usertagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI usertagowner", "comment", "")
312 now := time.Now()
313 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "usertag.md", Slug: "usertag", Title: "User Tag", Space: "prose", PublishAt: &now})
314 _ = testDB.ReplaceTagsByPost([]string{"mytag"}, post.ID)
315@@ -818,7 +818,7 @@ func TestFindUserPostsByTag(t *testing.T) {
316 func TestFindPostsByTag(t *testing.T) {
317 cleanupTestData(t)
318
319- user, _ := testDB.RegisterUser("tagsearchowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tagsearchowner", "comment")
320+ user, _ := testDB.RegisterUser("tagsearchowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tagsearchowner", "comment", "")
321 now := time.Now()
322 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "tagsearch.md", Slug: "tagsearch", Title: "Tag Search", Space: "prose", PublishAt: &now})
323 _ = testDB.ReplaceTagsByPost([]string{"searchtag"}, post.ID)
324@@ -836,7 +836,7 @@ func TestFindPostsByTag(t *testing.T) {
325 func TestFindPopularTags(t *testing.T) {
326 cleanupTestData(t)
327
328- user, _ := testDB.RegisterUser("populartagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI populartagowner", "comment")
329+ user, _ := testDB.RegisterUser("populartagowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI populartagowner", "comment", "")
330 post1 := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "pop1.md", Slug: "pop1", Title: "Pop 1", Space: "prose"})
331 post2 := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "pop2.md", Slug: "pop2", Title: "Pop 2", Space: "prose"})
332 _ = testDB.ReplaceTagsByPost([]string{"popular"}, post1.ID)
333@@ -856,7 +856,7 @@ func TestFindPopularTags(t *testing.T) {
334 func TestReplaceAliasesByPost(t *testing.T) {
335 cleanupTestData(t)
336
337- user, _ := testDB.RegisterUser("aliasowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aliasowner", "comment")
338+ user, _ := testDB.RegisterUser("aliasowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aliasowner", "comment", "")
339 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "aliased.md", Slug: "aliased", Title: "Aliased", Space: "prose"})
340
341 err := testDB.ReplaceAliasesByPost([]string{"alias1", "alias2"}, post.ID)
342@@ -868,7 +868,7 @@ func TestReplaceAliasesByPost(t *testing.T) {
343 func TestFindPostWithSlug_Alias(t *testing.T) {
344 cleanupTestData(t)
345
346- user, _ := testDB.RegisterUser("aliassearchowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aliassearchowner", "comment")
347+ user, _ := testDB.RegisterUser("aliassearchowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aliassearchowner", "comment", "")
348 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "original.md", Slug: "original", Title: "Original", Space: "prose"})
349 _ = testDB.ReplaceAliasesByPost([]string{"my-alias"}, post.ID)
350
351@@ -886,7 +886,7 @@ func TestFindPostWithSlug_Alias(t *testing.T) {
352 func TestInsertVisit(t *testing.T) {
353 cleanupTestData(t)
354
355- user, _ := testDB.RegisterUser("visitowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI visitowner", "comment")
356+ user, _ := testDB.RegisterUser("visitowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI visitowner", "comment", "")
357
358 visit := &db.AnalyticsVisits{
359 UserID: user.ID,
360@@ -907,7 +907,7 @@ func TestInsertVisit(t *testing.T) {
361 func TestVisitSummary(t *testing.T) {
362 cleanupTestData(t)
363
364- user, _ := testDB.RegisterUser("summaryowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI summaryowner", "comment")
365+ user, _ := testDB.RegisterUser("summaryowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI summaryowner", "comment", "")
366
367 visit := &db.AnalyticsVisits{
368 UserID: user.ID,
369@@ -937,7 +937,7 @@ func TestVisitSummary(t *testing.T) {
370 func TestFindVisitSiteList(t *testing.T) {
371 cleanupTestData(t)
372
373- user, _ := testDB.RegisterUser("sitelistowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI sitelistowner", "comment")
374+ user, _ := testDB.RegisterUser("sitelistowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI sitelistowner", "comment", "")
375
376 _ = testDB.InsertVisit(&db.AnalyticsVisits{
377 UserID: user.ID,
378@@ -960,7 +960,7 @@ func TestFindVisitSiteList(t *testing.T) {
379 func TestVisitUrlNotFound(t *testing.T) {
380 cleanupTestData(t)
381
382- user, _ := testDB.RegisterUser("notfoundowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI notfoundowner", "comment")
383+ user, _ := testDB.RegisterUser("notfoundowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI notfoundowner", "comment", "")
384
385 _ = testDB.InsertVisit(&db.AnalyticsVisits{
386 UserID: user.ID,
387@@ -989,7 +989,7 @@ func TestVisitUrlNotFound(t *testing.T) {
388 func TestInsertFeature(t *testing.T) {
389 cleanupTestData(t)
390
391- user, _ := testDB.RegisterUser("featureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featureowner", "comment")
392+ user, _ := testDB.RegisterUser("featureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featureowner", "comment", "")
393
394 expiresAt := time.Now().Add(365 * 24 * time.Hour)
395 feature, err := testDB.InsertFeature(user.ID, "plus", expiresAt)
396@@ -1004,7 +1004,7 @@ func TestInsertFeature(t *testing.T) {
397 func TestFindFeature(t *testing.T) {
398 cleanupTestData(t)
399
400- user, _ := testDB.RegisterUser("findfeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findfeatureowner", "comment")
401+ user, _ := testDB.RegisterUser("findfeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findfeatureowner", "comment", "")
402 expiresAt := time.Now().Add(365 * 24 * time.Hour)
403 _, _ = testDB.InsertFeature(user.ID, "plus", expiresAt)
404
405@@ -1020,7 +1020,7 @@ func TestFindFeature(t *testing.T) {
406 func TestFindFeaturesByUser(t *testing.T) {
407 cleanupTestData(t)
408
409- user, _ := testDB.RegisterUser("featuresowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featuresowner", "comment")
410+ user, _ := testDB.RegisterUser("featuresowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featuresowner", "comment", "")
411 expiresAt := time.Now().Add(365 * 24 * time.Hour)
412 _, _ = testDB.InsertFeature(user.ID, "plus", expiresAt)
413 _, _ = testDB.InsertFeature(user.ID, "pro", expiresAt)
414@@ -1037,7 +1037,7 @@ func TestFindFeaturesByUser(t *testing.T) {
415 func TestHasFeatureByUser(t *testing.T) {
416 cleanupTestData(t)
417
418- user, _ := testDB.RegisterUser("hasfeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI hasfeatureowner", "comment")
419+ user, _ := testDB.RegisterUser("hasfeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI hasfeatureowner", "comment", "")
420 expiresAt := time.Now().Add(365 * 24 * time.Hour)
421 _, _ = testDB.InsertFeature(user.ID, "plus", expiresAt)
422
423@@ -1055,7 +1055,7 @@ func TestHasFeatureByUser(t *testing.T) {
424 func TestRemoveFeature(t *testing.T) {
425 cleanupTestData(t)
426
427- user, _ := testDB.RegisterUser("removefeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removefeatureowner", "comment")
428+ user, _ := testDB.RegisterUser("removefeatureowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI removefeatureowner", "comment", "")
429 expiresAt := time.Now().Add(365 * 24 * time.Hour)
430 _, _ = testDB.InsertFeature(user.ID, "plus", expiresAt)
431
432@@ -1073,7 +1073,7 @@ func TestRemoveFeature(t *testing.T) {
433 func TestAddPicoPlusUser(t *testing.T) {
434 cleanupTestData(t)
435
436- _, _ = testDB.RegisterUser("picoplusowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI picoplusowner", "comment")
437+ _, _ = testDB.RegisterUser("picoplusowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI picoplusowner", "comment", "")
438
439 err := testDB.AddPicoPlusUser("picoplusowner", "test@example.com", "stripe", "tx123")
440 if err != nil {
441@@ -1086,7 +1086,7 @@ func TestAddPicoPlusUser(t *testing.T) {
442 func TestInsertFeedItems(t *testing.T) {
443 cleanupTestData(t)
444
445- user, _ := testDB.RegisterUser("feeditemsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feeditemsowner", "comment")
446+ user, _ := testDB.RegisterUser("feeditemsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feeditemsowner", "comment", "")
447 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feed.txt", Slug: "feed", Title: "Feed", Space: "feeds"})
448
449 items := []*db.FeedItem{
450@@ -1103,7 +1103,7 @@ func TestInsertFeedItems(t *testing.T) {
451 func TestFindFeedItemsByPostID(t *testing.T) {
452 cleanupTestData(t)
453
454- user, _ := testDB.RegisterUser("findfeeditemsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findfeeditemsowner", "comment")
455+ user, _ := testDB.RegisterUser("findfeeditemsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findfeeditemsowner", "comment", "")
456 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "findfeed.txt", Slug: "findfeed", Title: "Find Feed", Space: "feeds"})
457
458 items := []*db.FeedItem{
459@@ -1125,7 +1125,7 @@ func TestFindFeedItemsByPostID(t *testing.T) {
460 func TestUpsertProject(t *testing.T) {
461 cleanupTestData(t)
462
463- user, _ := testDB.RegisterUser("projectowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI projectowner", "comment")
464+ user, _ := testDB.RegisterUser("projectowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI projectowner", "comment", "")
465
466 project, err := testDB.UpsertProject(user.ID, "my-project", "my-project")
467 if err != nil {
468@@ -1147,7 +1147,7 @@ func TestUpsertProject(t *testing.T) {
469 func TestFindProjectByName(t *testing.T) {
470 cleanupTestData(t)
471
472- user, _ := testDB.RegisterUser("findprojectowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findprojectowner", "comment")
473+ user, _ := testDB.RegisterUser("findprojectowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findprojectowner", "comment", "")
474 _, _ = testDB.UpsertProject(user.ID, "findme-project", "findme-project")
475
476 project, err := testDB.FindProjectByName(user.ID, "findme-project")
477@@ -1164,7 +1164,7 @@ func TestFindProjectByName(t *testing.T) {
478 func TestFindUserStats(t *testing.T) {
479 cleanupTestData(t)
480
481- user, _ := testDB.RegisterUser("statsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI statsowner", "comment")
482+ user, _ := testDB.RegisterUser("statsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI statsowner", "comment", "")
483 _ = mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "stat.md", Slug: "stat", Title: "Stat", Space: "prose"})
484 _, _ = testDB.UpsertProject(user.ID, "stat-project", "stat-project")
485
486@@ -1185,7 +1185,7 @@ func TestFindUserStats(t *testing.T) {
487 func TestInsertTunsEventLog(t *testing.T) {
488 cleanupTestData(t)
489
490- user, _ := testDB.RegisterUser("tunsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tunsowner", "comment")
491+ user, _ := testDB.RegisterUser("tunsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tunsowner", "comment", "")
492
493 log := &db.TunsEventLog{
494 UserId: user.ID,
495@@ -1206,7 +1206,7 @@ func TestInsertTunsEventLog(t *testing.T) {
496 func TestFindTunsEventLogs(t *testing.T) {
497 cleanupTestData(t)
498
499- user, _ := testDB.RegisterUser("findtunsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findtunsowner", "comment")
500+ user, _ := testDB.RegisterUser("findtunsowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findtunsowner", "comment", "")
501 _ = testDB.InsertTunsEventLog(&db.TunsEventLog{
502 UserId: user.ID,
503 ServerID: "server-1",
504@@ -1229,7 +1229,7 @@ func TestFindTunsEventLogs(t *testing.T) {
505 func TestFindTunsEventLogsByAddr(t *testing.T) {
506 cleanupTestData(t)
507
508- user, _ := testDB.RegisterUser("tunsaddrowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tunsaddrowner", "comment")
509+ user, _ := testDB.RegisterUser("tunsaddrowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI tunsaddrowner", "comment", "")
510 _ = testDB.InsertTunsEventLog(&db.TunsEventLog{
511 UserId: user.ID,
512 ServerID: "server-1",
513@@ -1254,7 +1254,7 @@ func TestFindTunsEventLogsByAddr(t *testing.T) {
514 func TestInsertAccessLog(t *testing.T) {
515 cleanupTestData(t)
516
517- user, _ := testDB.RegisterUser("accesslogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI accesslogowner", "comment")
518+ user, _ := testDB.RegisterUser("accesslogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI accesslogowner", "comment", "")
519
520 log := &db.AccessLog{
521 UserID: user.ID,
522@@ -1272,7 +1272,7 @@ func TestInsertAccessLog(t *testing.T) {
523 func TestFindAccessLogs(t *testing.T) {
524 cleanupTestData(t)
525
526- user, _ := testDB.RegisterUser("findaccesslogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findaccesslogowner", "comment")
527+ user, _ := testDB.RegisterUser("findaccesslogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI findaccesslogowner", "comment", "")
528 _ = testDB.InsertAccessLog(&db.AccessLog{
529 UserID: user.ID,
530 Service: "pgs",
531@@ -1293,7 +1293,7 @@ func TestFindAccessLogs(t *testing.T) {
532 func TestFindPubkeysInAccessLogs(t *testing.T) {
533 cleanupTestData(t)
534
535- user, _ := testDB.RegisterUser("pubkeylogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pubkeylogowner", "comment")
536+ user, _ := testDB.RegisterUser("pubkeylogowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pubkeylogowner", "comment", "")
537 _ = testDB.InsertAccessLog(&db.AccessLog{
538 UserID: user.ID,
539 Service: "pgs",
540@@ -1313,7 +1313,7 @@ func TestFindPubkeysInAccessLogs(t *testing.T) {
541 func TestFindAccessLogsByPubkey(t *testing.T) {
542 cleanupTestData(t)
543
544- user, _ := testDB.RegisterUser("accessbypubkeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI accessbypubkeyowner", "comment")
545+ user, _ := testDB.RegisterUser("accessbypubkeyowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI accessbypubkeyowner", "comment", "")
546 pubkey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI accessbypubkeyowner"
547 _ = testDB.InsertAccessLog(&db.AccessLog{
548 UserID: user.ID,
549@@ -1337,7 +1337,7 @@ func TestFindAccessLogsByPubkey(t *testing.T) {
550 func TestPostData_JSONBRoundtrip(t *testing.T) {
551 cleanupTestData(t)
552
553- user, _ := testDB.RegisterUser("postdataowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postdataowner", "comment")
554+ user, _ := testDB.RegisterUser("postdataowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI postdataowner", "comment", "")
555
556 now := time.Now().Truncate(time.Second)
557 postData := db.PostData{
558@@ -1370,7 +1370,7 @@ func TestPostData_JSONBRoundtrip(t *testing.T) {
559 func TestProjectAcl_JSONBRoundtrip(t *testing.T) {
560 cleanupTestData(t)
561
562- user, _ := testDB.RegisterUser("aclowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aclowner", "comment")
563+ user, _ := testDB.RegisterUser("aclowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI aclowner", "comment", "")
564
565 project, err := testDB.UpsertProject(user.ID, "acl-project", "acl-project")
566 if err != nil {
567@@ -1390,7 +1390,7 @@ func TestProjectAcl_JSONBRoundtrip(t *testing.T) {
568 func TestFeedItemData_JSONBRoundtrip(t *testing.T) {
569 cleanupTestData(t)
570
571- user, _ := testDB.RegisterUser("feedjsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedjsonbowner", "comment")
572+ user, _ := testDB.RegisterUser("feedjsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI feedjsonbowner", "comment", "")
573 post := mustInsertPost(t, &db.Post{UserID: user.ID, Filename: "feedjsonb.txt", Slug: "feedjsonb", Title: "Feed JSONB", Space: "feeds"})
574
575 now := time.Now().Truncate(time.Second)
576@@ -1424,7 +1424,7 @@ func TestFeedItemData_JSONBRoundtrip(t *testing.T) {
577 func TestFeatureFlagData_JSONBRoundtrip(t *testing.T) {
578 cleanupTestData(t)
579
580- user, _ := testDB.RegisterUser("featurejsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featurejsonbowner", "comment")
581+ user, _ := testDB.RegisterUser("featurejsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI featurejsonbowner", "comment", "")
582
583 err := testDB.AddPicoPlusUser("featurejsonbowner", "test@example.com", "stripe", "tx456")
584 if err != nil {
585@@ -1446,7 +1446,7 @@ func TestFeatureFlagData_JSONBRoundtrip(t *testing.T) {
586 func TestPaymentHistoryData_JSONBRoundtrip(t *testing.T) {
587 cleanupTestData(t)
588
589- user, _ := testDB.RegisterUser("paymentjsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI paymentjsonbowner", "comment")
590+ user, _ := testDB.RegisterUser("paymentjsonbowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI paymentjsonbowner", "comment", "")
591
592 err := testDB.AddPicoPlusUser("paymentjsonbowner", "payment@example.com", "stripe", "tx789")
593 if err != nil {
594@@ -1468,7 +1468,7 @@ func TestPaymentHistoryData_JSONBRoundtrip(t *testing.T) {
595 func TestUpsertPipeMonitor(t *testing.T) {
596 cleanupTestData(t)
597
598- user, _ := testDB.RegisterUser("pipemonitorowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipemonitorowner", "comment")
599+ user, _ := testDB.RegisterUser("pipemonitorowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipemonitorowner", "comment", "")
600
601 winEnd := time.Now().Add(time.Hour)
602 err := testDB.UpsertPipeMonitor(user.ID, "test-topic", 5*time.Minute, &winEnd)
603@@ -1491,7 +1491,7 @@ func TestUpsertPipeMonitor(t *testing.T) {
604 func TestUpsertPipeMonitor_Update(t *testing.T) {
605 cleanupTestData(t)
606
607- user, _ := testDB.RegisterUser("pipeupdateowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipeupdateowner", "comment")
608+ user, _ := testDB.RegisterUser("pipeupdateowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipeupdateowner", "comment", "")
609
610 winEnd1 := time.Now().Add(time.Hour)
611 err := testDB.UpsertPipeMonitor(user.ID, "update-topic", 5*time.Minute, &winEnd1)
612@@ -1517,7 +1517,7 @@ func TestUpsertPipeMonitor_Update(t *testing.T) {
613 func TestUpdatePipeMonitorLastPing(t *testing.T) {
614 cleanupTestData(t)
615
616- user, _ := testDB.RegisterUser("pipepingowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipepingowner", "comment")
617+ user, _ := testDB.RegisterUser("pipepingowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipepingowner", "comment", "")
618
619 winEnd := time.Now().Add(time.Hour)
620 err := testDB.UpsertPipeMonitor(user.ID, "ping-topic", 5*time.Minute, &winEnd)
621@@ -1543,7 +1543,7 @@ func TestUpdatePipeMonitorLastPing(t *testing.T) {
622 func TestRemovePipeMonitor(t *testing.T) {
623 cleanupTestData(t)
624
625- user, _ := testDB.RegisterUser("piperemoveowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI piperemoveowner", "comment")
626+ user, _ := testDB.RegisterUser("piperemoveowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI piperemoveowner", "comment", "")
627
628 winEnd := time.Now().Add(time.Hour)
629 err := testDB.UpsertPipeMonitor(user.ID, "remove-topic", 5*time.Minute, &winEnd)
630@@ -1565,7 +1565,7 @@ func TestRemovePipeMonitor(t *testing.T) {
631 func TestFindPipeMonitorByTopic_NotFound(t *testing.T) {
632 cleanupTestData(t)
633
634- user, _ := testDB.RegisterUser("pipenotfoundowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipenotfoundowner", "comment")
635+ user, _ := testDB.RegisterUser("pipenotfoundowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipenotfoundowner", "comment", "")
636
637 _, err := testDB.FindPipeMonitorByTopic(user.ID, "nonexistent-topic")
638 if err == nil {
639@@ -1576,7 +1576,7 @@ func TestFindPipeMonitorByTopic_NotFound(t *testing.T) {
640 func TestFindPipeMonitorsByUser(t *testing.T) {
641 cleanupTestData(t)
642
643- user, _ := testDB.RegisterUser("pipemonlistowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipemonlistowner", "comment")
644+ user, _ := testDB.RegisterUser("pipemonlistowner", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipemonlistowner", "comment", "")
645
646 winEnd := time.Now().Add(time.Hour)
647 _ = testDB.UpsertPipeMonitor(user.ID, "service-a", 5*time.Minute, &winEnd)
648@@ -1607,7 +1607,7 @@ func TestFindPipeMonitorsByUser(t *testing.T) {
649 func TestFindPipeMonitorsByUser_Empty(t *testing.T) {
650 cleanupTestData(t)
651
652- user, _ := testDB.RegisterUser("pipenomonitors", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipenomonitors", "comment")
653+ user, _ := testDB.RegisterUser("pipenomonitors", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI pipenomonitors", "comment", "")
654
655 monitors, err := testDB.FindPipeMonitorsByUser(user.ID)
656 if err != nil {
+1,
-1
1@@ -24,7 +24,7 @@ func NewStubDB(logger *slog.Logger) *StubDB {
2
3 var errNotImpl = fmt.Errorf("not implemented")
4
5-func (me *StubDB) RegisterUser(username, pubkey, comment string) (*db.User, error) {
6+func (me *StubDB) RegisterUser(username, pubkey, comment, ip string) (*db.User, error) {
7 return nil, errNotImpl
8 }
9
+4,
-1
1@@ -2,6 +2,7 @@ package tui
2
3 import (
4 "fmt"
5+ "net"
6
7 "git.sr.ht/~rockorager/vaxis"
8 "git.sr.ht/~rockorager/vaxis/vxfw"
9@@ -45,7 +46,9 @@ func (m *SignupPage) createAccount(name string) (*db.User, error) {
10 return nil, fmt.Errorf("name cannot be empty")
11 }
12 key := shared.KeyForKeyText(m.shared.Session.PublicKey())
13- return m.shared.Dbpool.RegisterUser(name, key, "")
14+ remoteAddr := m.shared.Session.Conn.RemoteAddr().String()
15+ userIp, _, _ := net.SplitHostPort(remoteAddr)
16+ return m.shared.Dbpool.RegisterUser(name, key, "", userIp)
17 }
18
19 func (m *SignupPage) CaptureEvent(ev vaxis.Event) (vxfw.Command, error) {
1@@ -0,0 +1,6 @@
2+CREATE TABLE IF NOT EXISTS block_signups (
3+ id SERIAL PRIMARY KEY,
4+ ip inet NOT NULL,
5+ reason TEXT not null,
6+ created_at timestamp without time zone NOT NULL DEFAULT NOW()
7+);