1package pds
2
3import (
4 "bytes"
5 "context"
6 "database/sql"
7 "fmt"
8 "io"
9
10 comatprototypes "github.com/bluesky-social/indigo/api/atproto"
11 lexutil "github.com/bluesky-social/indigo/lex/util"
12 "github.com/bluesky-social/indigo/models"
13 "github.com/ipfs/go-cid"
14 "github.com/lestrrat-go/jwx/v2/jwt"
15)
16
17func (s *Server) handleComAtprotoServerCreateAccount(ctx context.Context, body *comatprototypes.ServerCreateAccount_Input) (*comatprototypes.ServerCreateAccount_Output, error) {
18 if body.Email == nil {
19 return nil, fmt.Errorf("email is required")
20 }
21
22 if body.Password == nil {
23 return nil, fmt.Errorf("password is required")
24 }
25
26 if err := validateEmail(*body.Email); err != nil {
27 return nil, err
28 }
29
30 if err := s.validateHandle(body.Handle); err != nil {
31 return nil, err
32 }
33
34 _, err := s.lookupUserByHandle(ctx, body.Handle)
35 switch err {
36 default:
37 return nil, err
38 case nil:
39 return nil, fmt.Errorf("handle already registered")
40 case ErrNoSuchUser:
41 // handle is available, lets go
42 }
43
44 var recoveryKey string
45 if body.RecoveryKey != nil {
46 recoveryKey = *body.RecoveryKey
47 }
48
49 u := User{
50 Handle: body.Handle,
51 Password: *body.Password,
52 RecoveryKey: recoveryKey,
53 Email: *body.Email,
54 }
55 if err := s.db.Create(&u).Error; err != nil {
56 return nil, err
57 }
58
59 if recoveryKey == "" {
60 recoveryKey = s.signingKey.Public().DID()
61 }
62
63 d, err := s.plc.CreateDID(ctx, s.signingKey, recoveryKey, body.Handle, s.serviceUrl)
64 if err != nil {
65 return nil, fmt.Errorf("create did: %w", err)
66 }
67
68 u.Did = d
69 if err := s.db.Save(&u).Error; err != nil {
70 return nil, err
71 }
72
73 ai := &models.ActorInfo{
74 Uid: u.ID,
75 Did: d,
76 Handle: sql.NullString{String: body.Handle, Valid: true},
77 }
78 if err := s.db.Create(ai).Error; err != nil {
79 return nil, err
80 }
81
82 if err := s.repoman.InitNewActor(ctx, u.ID, u.Handle, u.Did, "", "", ""); err != nil {
83 return nil, err
84 }
85
86 tok, err := s.createAuthTokenForUser(ctx, body.Handle, d)
87 if err != nil {
88 return nil, err
89 }
90
91 return &comatprototypes.ServerCreateAccount_Output{
92 Handle: body.Handle,
93 Did: d,
94 AccessJwt: tok.AccessJwt,
95 RefreshJwt: tok.RefreshJwt,
96 }, nil
97}
98
99func (s *Server) handleComAtprotoServerCreateInviteCode(ctx context.Context, body *comatprototypes.ServerCreateInviteCode_Input) (*comatprototypes.ServerCreateInviteCode_Output, error) {
100 u, err := s.getUser(ctx)
101 if err != nil {
102 return nil, err
103 }
104
105 _ = u
106
107 return nil, fmt.Errorf("invite codes not currently supported")
108}
109
110func (s *Server) handleComAtprotoServerRequestAccountDelete(ctx context.Context) error {
111 panic("not yet implemented")
112}
113
114func (s *Server) handleComAtprotoServerDeleteAccount(ctx context.Context, body *comatprototypes.ServerDeleteAccount_Input) error {
115 panic("not yet implemented")
116}
117
118func (s *Server) handleComAtprotoServerRequestPasswordReset(ctx context.Context, body *comatprototypes.ServerRequestPasswordReset_Input) error {
119 panic("not yet implemented")
120}
121
122func (s *Server) handleComAtprotoServerResetPassword(ctx context.Context, body *comatprototypes.ServerResetPassword_Input) error {
123 panic("not yet implemented")
124}
125
126func (s *Server) handleComAtprotoRepoUploadBlob(ctx context.Context, r io.Reader, contentType string) (*comatprototypes.RepoUploadBlob_Output, error) {
127 panic("not yet implemented")
128}
129
130func (s *Server) handleComAtprotoIdentityResolveHandle(ctx context.Context, handle string) (*comatprototypes.IdentityResolveHandle_Output, error) {
131 if handle == "" {
132 return &comatprototypes.IdentityResolveHandle_Output{Did: s.signingKey.Public().DID()}, nil
133 }
134 u, err := s.lookupUserByHandle(ctx, handle)
135 if err != nil {
136 return nil, err
137 }
138
139 return &comatprototypes.IdentityResolveHandle_Output{Did: u.Did}, nil
140}
141
142func (s *Server) handleComAtprotoRepoApplyWrites(ctx context.Context, body *comatprototypes.RepoApplyWrites_Input) error {
143 u, err := s.getUser(ctx)
144 if err != nil {
145 return err
146 }
147
148 if u.Did != body.Repo {
149 return fmt.Errorf("writes for non-user actors not supported (DID mismatch)")
150 }
151
152 return s.repoman.BatchWrite(ctx, u.ID, body.Writes)
153}
154
155func (s *Server) handleComAtprotoRepoCreateRecord(ctx context.Context, input *comatprototypes.RepoCreateRecord_Input) (*comatprototypes.RepoCreateRecord_Output, error) {
156 u, err := s.getUser(ctx)
157 if err != nil {
158 return nil, fmt.Errorf("get user: %w", err)
159 }
160
161 rpath, recid, err := s.repoman.CreateRecord(ctx, u.ID, input.Collection, input.Record.Val)
162 if err != nil {
163 return nil, fmt.Errorf("record create: %w", err)
164 }
165
166 return &comatprototypes.RepoCreateRecord_Output{
167 Uri: "at://" + u.Did + "/" + rpath,
168 Cid: recid.String(),
169 }, nil
170}
171
172func (s *Server) handleComAtprotoRepoDeleteRecord(ctx context.Context, input *comatprototypes.RepoDeleteRecord_Input) error {
173 u, err := s.getUser(ctx)
174 if err != nil {
175 return err
176 }
177
178 if u.Did != input.Repo {
179 return fmt.Errorf("specified DID did not match authed user")
180 }
181
182 return s.repoman.DeleteRecord(ctx, u.ID, input.Collection, input.Rkey)
183}
184
185func (s *Server) handleComAtprotoRepoGetRecord(ctx context.Context, c string, collection string, repo string, rkey string) (*comatprototypes.RepoGetRecord_Output, error) {
186 targetUser, err := s.lookupUser(ctx, repo)
187 if err != nil {
188 return nil, err
189 }
190
191 var maybeCid cid.Cid
192 if c != "" {
193 cc, err := cid.Decode(c)
194 if err != nil {
195 return nil, err
196 }
197 maybeCid = cc
198 }
199
200 reccid, rec, err := s.repoman.GetRecord(ctx, targetUser.ID, collection, rkey, maybeCid)
201 if err != nil {
202 return nil, fmt.Errorf("repoman GetRecord: %w", err)
203 }
204
205 ccstr := reccid.String()
206 return &comatprototypes.RepoGetRecord_Output{
207 Cid: &ccstr,
208 Uri: "at://" + targetUser.Did + "/" + collection + "/" + rkey,
209 Value: &lexutil.LexiconTypeDecoder{Val: rec},
210 }, nil
211}
212
213func (s *Server) handleComAtprotoRepoListRecords(ctx context.Context, collection string, cursor string, limit int, repo string, reverse *bool, rkeyEnd string, rkeyStart string) (*comatprototypes.RepoListRecords_Output, error) {
214 panic("not yet implemented")
215}
216
217func (s *Server) handleComAtprotoRepoPutRecord(ctx context.Context, input *comatprototypes.RepoPutRecord_Input) (*comatprototypes.RepoPutRecord_Output, error) {
218 panic("not yet implemented")
219}
220
221func (s *Server) handleComAtprotoServerDescribeServer(ctx context.Context) (*comatprototypes.ServerDescribeServer_Output, error) {
222 invcode := false
223 return &comatprototypes.ServerDescribeServer_Output{
224 InviteCodeRequired: &invcode,
225 AvailableUserDomains: []string{
226 s.handleSuffix,
227 },
228 Links: &comatprototypes.ServerDescribeServer_Links{},
229 }, nil
230}
231
232var ErrInvalidUsernameOrPassword = fmt.Errorf("invalid username or password")
233
234func (s *Server) handleComAtprotoServerCreateSession(ctx context.Context, body *comatprototypes.ServerCreateSession_Input) (*comatprototypes.ServerCreateSession_Output, error) {
235 u, err := s.lookupUserByHandle(ctx, body.Identifier)
236 if err != nil {
237 return nil, err
238 }
239
240 if body.Password != u.Password {
241 return nil, ErrInvalidUsernameOrPassword
242 }
243
244 tok, err := s.createAuthTokenForUser(ctx, body.Identifier, u.Did)
245 if err != nil {
246 return nil, err
247 }
248
249 return &comatprototypes.ServerCreateSession_Output{
250 Handle: body.Identifier,
251 Did: u.Did,
252 AccessJwt: tok.AccessJwt,
253 RefreshJwt: tok.RefreshJwt,
254 }, nil
255}
256
257func (s *Server) handleComAtprotoServerDeleteSession(ctx context.Context) error {
258 panic("not yet implemented")
259}
260
261func (s *Server) handleComAtprotoServerGetSession(ctx context.Context) (*comatprototypes.ServerGetSession_Output, error) {
262 u, err := s.getUser(ctx)
263 if err != nil {
264 return nil, err
265 }
266
267 return &comatprototypes.ServerGetSession_Output{
268 Handle: u.Handle,
269 Did: u.Did,
270 }, nil
271}
272
273func (s *Server) handleComAtprotoServerRefreshSession(ctx context.Context) (*comatprototypes.ServerRefreshSession_Output, error) {
274 u, err := s.getUser(ctx)
275 if err != nil {
276 return nil, err
277 }
278
279 scope, ok := ctx.Value("authScope").(string)
280 if !ok {
281 return nil, fmt.Errorf("scope not present in refresh token")
282 }
283
284 if scope != "com.atproto.refresh" {
285 return nil, fmt.Errorf("auth token did not have refresh scope")
286 }
287
288 tok, ok := ctx.Value("token").(*jwt.Token)
289 if !ok {
290 return nil, fmt.Errorf("internal auth error: token not set post auth check")
291 }
292
293 if err := s.invalidateToken(ctx, u, tok); err != nil {
294 return nil, err
295 }
296
297 outTok, err := s.createAuthTokenForUser(ctx, u.Handle, u.Did)
298 if err != nil {
299 return nil, err
300 }
301
302 return &comatprototypes.ServerRefreshSession_Output{
303 Handle: u.Handle,
304 Did: u.Did,
305 AccessJwt: outTok.AccessJwt,
306 RefreshJwt: outTok.RefreshJwt,
307 }, nil
308
309}
310
311func (s *Server) handleComAtprotoSyncUpdateRepo(ctx context.Context, r io.Reader) error {
312 panic("not yet implemented")
313}
314
315func (s *Server) handleComAtprotoSyncGetCheckout(ctx context.Context, did string) (io.Reader, error) {
316 panic("not yet implemented")
317}
318
319func (s *Server) handleComAtprotoSyncGetHead(ctx context.Context, did string) (*comatprototypes.SyncGetHead_Output, error) {
320 user, err := s.lookupUserByDid(ctx, did)
321 if err != nil {
322 return nil, err
323 }
324
325 root, err := s.repoman.GetRepoRoot(ctx, user.ID)
326 if err != nil {
327 return nil, err
328 }
329
330 return &comatprototypes.SyncGetHead_Output{
331 Root: root.String(),
332 }, nil
333}
334
335func (s *Server) handleComAtprotoSyncGetRecord(ctx context.Context, collection string, commit string, did string, rkey string) (io.Reader, error) {
336 panic("not yet implemented")
337}
338
339func (s *Server) handleComAtprotoSyncGetRepo(ctx context.Context, did string, since string) (io.Reader, error) {
340 targetUser, err := s.lookupUser(ctx, did)
341 if err != nil {
342 return nil, err
343 }
344
345 buf := new(bytes.Buffer)
346 if err := s.repoman.ReadRepo(ctx, targetUser.ID, since, buf); err != nil {
347 return nil, err
348 }
349
350 return buf, nil
351}
352
353func (s *Server) handleComAtprotoSyncGetBlocks(ctx context.Context, cids []string, did string) (io.Reader, error) {
354 panic("nyi")
355}
356
357func (s *Server) handleComAtprotoSyncNotifyOfUpdate(ctx context.Context, body *comatprototypes.SyncNotifyOfUpdate_Input) error {
358 panic("nyi")
359}
360
361func (s *Server) handleComAtprotoSyncRequestCrawl(ctx context.Context, body *comatprototypes.SyncRequestCrawl_Input) error {
362 panic("nyi")
363}
364
365func (s *Server) handleComAtprotoSyncGetBlob(ctx context.Context, cid string, did string) (io.Reader, error) {
366 panic("nyi")
367}
368
369func (s *Server) handleComAtprotoSyncListBlobs(ctx context.Context, cursor string, did string, limit int, since string) (*comatprototypes.SyncListBlobs_Output, error) {
370 panic("nyi")
371}
372
373func (s *Server) handleComAtprotoIdentityUpdateHandle(ctx context.Context, body *comatprototypes.IdentityUpdateHandle_Input) error {
374 if err := s.validateHandle(body.Handle); err != nil {
375 return err
376 }
377
378 u, err := s.getUser(ctx)
379 if err != nil {
380 return err
381 }
382
383 return s.UpdateUserHandle(ctx, u, body.Handle)
384}
385
386func (s *Server) handleComAtprotoModerationCreateReport(ctx context.Context, body *comatprototypes.ModerationCreateReport_Input) (*comatprototypes.ModerationCreateReport_Output, error) {
387 panic("nyi")
388}
389
390func (s *Server) handleComAtprotoRepoDescribeRepo(ctx context.Context, repo string) (*comatprototypes.RepoDescribeRepo_Output, error) {
391 panic("nyi")
392}
393
394func (s *Server) handleComAtprotoAdminDisableInviteCodes(ctx context.Context, body *comatprototypes.AdminDisableInviteCodes_Input) error {
395 panic("nyi")
396}
397
398func (s *Server) handleComAtprotoAdminGetInviteCodes(ctx context.Context, cursor string, limit int, sort string) (*comatprototypes.AdminGetInviteCodes_Output, error) {
399 panic("nyi")
400}
401
402func (s *Server) handleComAtprotoLabelQueryLabels(ctx context.Context, cursor string, limit int, sources []string, uriPatterns []string) (*comatprototypes.LabelQueryLabels_Output, error) {
403 panic("nyi")
404}
405
406func (s *Server) handleComAtprotoServerCreateInviteCodes(ctx context.Context, body *comatprototypes.ServerCreateInviteCodes_Input) (*comatprototypes.ServerCreateInviteCodes_Output, error) {
407 panic("nyi")
408}
409
410func (s *Server) handleComAtprotoServerGetAccountInviteCodes(ctx context.Context, createAvailable bool, includeUsed bool) (*comatprototypes.ServerGetAccountInviteCodes_Output, error) {
411 panic("nyi")
412}
413
414func (s *Server) handleComAtprotoSyncListRepos(ctx context.Context, cursor string, limit int) (*comatprototypes.SyncListRepos_Output, error) {
415 panic("nyi")
416}
417
418func (s *Server) handleComAtprotoAdminUpdateAccountEmail(ctx context.Context, body *comatprototypes.AdminUpdateAccountEmail_Input) error {
419 panic("nyi")
420}
421
422func (s *Server) handleComAtprotoAdminUpdateAccountHandle(ctx context.Context, body *comatprototypes.AdminUpdateAccountHandle_Input) error {
423 panic("nyi")
424}
425
426func (s *Server) handleComAtprotoServerCreateAppPassword(ctx context.Context, body *comatprototypes.ServerCreateAppPassword_Input) (*comatprototypes.ServerCreateAppPassword_AppPassword, error) {
427 panic("nyi")
428}
429
430func (s *Server) handleComAtprotoServerListAppPasswords(ctx context.Context) (*comatprototypes.ServerListAppPasswords_Output, error) {
431 panic("nyi")
432}
433
434func (s *Server) handleComAtprotoServerRevokeAppPassword(ctx context.Context, body *comatprototypes.ServerRevokeAppPassword_Input) error {
435 panic("nyi")
436}
437
438func (s *Server) handleComAtprotoAdminDisableAccountInvites(ctx context.Context, body *comatprototypes.AdminDisableAccountInvites_Input) error {
439 panic("nyi")
440}
441
442func (s *Server) handleComAtprotoAdminEnableAccountInvites(ctx context.Context, body *comatprototypes.AdminEnableAccountInvites_Input) error {
443 panic("nyi")
444}
445
446func (s *Server) handleComAtprotoAdminSendEmail(ctx context.Context, body *comatprototypes.AdminSendEmail_Input) (*comatprototypes.AdminSendEmail_Output, error) {
447 panic("nyi")
448}
449
450func (s *Server) handleComAtprotoSyncGetLatestCommit(ctx context.Context, did string) (*comatprototypes.SyncGetLatestCommit_Output, error) {
451 panic("nyi")
452}
453
454func (s *Server) handleComAtprotoAdminGetAccountInfo(ctx context.Context, did string) (*comatprototypes.AdminDefs_AccountView, error) {
455 panic("nyi")
456}
457func (s *Server) handleComAtprotoAdminGetSubjectStatus(ctx context.Context, blob string, did string, uri string) (*comatprototypes.AdminGetSubjectStatus_Output, error) {
458 panic("nyi")
459}
460
461func (s *Server) handleComAtprotoAdminUpdateSubjectStatus(ctx context.Context, body *comatprototypes.AdminUpdateSubjectStatus_Input) (*comatprototypes.AdminUpdateSubjectStatus_Output, error) {
462 panic("nyi")
463}
464func (s *Server) handleComAtprotoServerConfirmEmail(ctx context.Context, body *comatprototypes.ServerConfirmEmail_Input) error {
465 panic("nyi")
466}
467func (s *Server) handleComAtprotoServerRequestEmailConfirmation(ctx context.Context) error {
468 panic("nyi")
469}
470func (s *Server) handleComAtprotoServerRequestEmailUpdate(ctx context.Context) (*comatprototypes.ServerRequestEmailUpdate_Output, error) {
471 panic("nyi")
472}
473func (s *Server) handleComAtprotoServerReserveSigningKey(ctx context.Context, body *comatprototypes.ServerReserveSigningKey_Input) (*comatprototypes.ServerReserveSigningKey_Output, error) {
474 panic("nyi")
475}
476func (s *Server) handleComAtprotoServerUpdateEmail(ctx context.Context, body *comatprototypes.ServerUpdateEmail_Input) error {
477 panic("nyi")
478}
479func (s *Server) handleComAtprotoTempFetchLabels(ctx context.Context, limit int, since *int) (*comatprototypes.TempFetchLabels_Output, error) {
480 panic("nyi")
481}