BYOK Personal Data Server (PDS) written in Go
ipfs vow atproto pds go
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: improve repo lookups and align account deletion flow

+20 -140
+15 -86
server/common.go
··· 30 30 return &repo, nil 31 31 } 32 32 33 - func (s *Server) getRepoActorByEmail(ctx context.Context, email string) (*models.RepoActor, error) { 33 + func (s *Server) getRepoActor(ctx context.Context, where, arg string) (*models.RepoActor, error) { 34 34 var repo models.RepoActor 35 - // Use explicit column selection to ensure proper mapping to embedded structs. 36 35 if err := s.db.Raw(ctx, ` 37 - SELECT 38 - r.did, r.created_at, r.email, r.email_confirmed_at, r.email_verification_code, 39 - r.email_verification_code_expires_at, r.email_update_code, r.email_update_code_expires_at, 40 - r.password_reset_code, r.password_reset_code_expires_at, r.plc_operation_code, 41 - r.plc_operation_code_expires_at, r.account_delete_code, r.account_delete_code_expires_at, 42 - r.password, r.auth_public_key, r.signing_public_key, r.credential_id, r.compat_mode, 43 - r.rev, r.root, r.preferences, r.deactivated, 44 - a.handle 36 + SELECT r.*, a.handle 45 37 FROM repos r 46 38 LEFT JOIN actors a ON r.did = a.did 47 - WHERE r.email = ? 48 - `, nil, email).Scan(&repo).Error; err != nil { 39 + WHERE `+where, nil, arg).Scan(&repo).Error; err != nil { 49 40 return nil, err 50 41 } 51 42 if repo.Repo.Did == "" { ··· 54 45 return &repo, nil 55 46 } 56 47 48 + func (s *Server) getRepoActorByEmail(ctx context.Context, email string) (*models.RepoActor, error) { 49 + return s.getRepoActor(ctx, "r.email = ?", email) 50 + } 51 + 57 52 func (s *Server) getRepoActorByDid(ctx context.Context, did string) (*models.RepoActor, error) { 58 - var repo models.RepoActor 59 - // Use explicit column selection to ensure proper mapping to embedded structs. 60 - // The r.*, a.* pattern can cause issues with GORM's Scan when structs have 61 - // overlapping field names (both Repo and Actor have "Did"). 62 - if err := s.db.Raw(ctx, ` 63 - SELECT 64 - r.did, r.created_at, r.email, r.email_confirmed_at, r.email_verification_code, 65 - r.email_verification_code_expires_at, r.email_update_code, r.email_update_code_expires_at, 66 - r.password_reset_code, r.password_reset_code_expires_at, r.plc_operation_code, 67 - r.plc_operation_code_expires_at, r.account_delete_code, r.account_delete_code_expires_at, 68 - r.password, r.auth_public_key, r.signing_public_key, r.credential_id, r.compat_mode, 69 - r.rev, r.root, r.preferences, r.deactivated, 70 - a.handle 71 - FROM repos r 72 - LEFT JOIN actors a ON r.did = a.did 73 - WHERE r.did = ? 74 - `, nil, did).Scan(&repo).Error; err != nil { 75 - return nil, err 76 - } 77 - if repo.Repo.Did == "" { 78 - return nil, gorm.ErrRecordNotFound 79 - } 80 - return &repo, nil 53 + return s.getRepoActor(ctx, "r.did = ?", did) 81 54 } 82 55 83 56 func (s *Server) getRepoActorByIdentifier(ctx context.Context, identifier string) (*models.RepoActor, error) { 84 - var repo models.RepoActor 85 - var err error 86 - if strings.HasPrefix(identifier, "did:") { 87 - err = s.db.Raw(ctx, ` 88 - SELECT 89 - r.did, r.created_at, r.email, r.email_confirmed_at, r.email_verification_code, 90 - r.email_verification_code_expires_at, r.email_update_code, r.email_update_code_expires_at, 91 - r.password_reset_code, r.password_reset_code_expires_at, r.plc_operation_code, 92 - r.plc_operation_code_expires_at, r.account_delete_code, r.account_delete_code_expires_at, 93 - r.password, r.auth_public_key, r.signing_public_key, r.credential_id, r.compat_mode, 94 - r.rev, r.root, r.preferences, r.deactivated, 95 - a.handle 96 - FROM repos r 97 - LEFT JOIN actors a ON r.did = a.did 98 - WHERE r.did = ? 99 - `, nil, identifier).Scan(&repo).Error 100 - } else if strings.Contains(identifier, "@") { 101 - err = s.db.Raw(ctx, ` 102 - SELECT 103 - r.did, r.created_at, r.email, r.email_confirmed_at, r.email_verification_code, 104 - r.email_verification_code_expires_at, r.email_update_code, r.email_update_code_expires_at, 105 - r.password_reset_code, r.password_reset_code_expires_at, r.plc_operation_code, 106 - r.plc_operation_code_expires_at, r.account_delete_code, r.account_delete_code_expires_at, 107 - r.password, r.auth_public_key, r.signing_public_key, r.credential_id, r.compat_mode, 108 - r.rev, r.root, r.preferences, r.deactivated, 109 - a.handle 110 - FROM repos r 111 - LEFT JOIN actors a ON r.did = a.did 112 - WHERE r.email = ? 113 - `, nil, identifier).Scan(&repo).Error 114 - } else { 115 - err = s.db.Raw(ctx, ` 116 - SELECT 117 - r.did, r.created_at, r.email, r.email_confirmed_at, r.email_verification_code, 118 - r.email_verification_code_expires_at, r.email_update_code, r.email_update_code_expires_at, 119 - r.password_reset_code, r.password_reset_code_expires_at, r.plc_operation_code, 120 - r.plc_operation_code_expires_at, r.account_delete_code, r.account_delete_code_expires_at, 121 - r.password, r.auth_public_key, r.signing_public_key, r.credential_id, r.compat_mode, 122 - r.rev, r.root, r.preferences, r.deactivated, 123 - a.handle 124 - FROM repos r 125 - LEFT JOIN actors a ON r.did = a.did 126 - WHERE a.handle = ? 127 - `, nil, identifier).Scan(&repo).Error 57 + switch { 58 + case strings.HasPrefix(identifier, "did:"): 59 + return s.getRepoActorByDid(ctx, identifier) 60 + case strings.Contains(identifier, "@"): 61 + return s.getRepoActor(ctx, "r.email = ?", identifier) 62 + default: 63 + return s.getRepoActor(ctx, "a.handle = ?", identifier) 128 64 } 129 - if err != nil { 130 - return nil, err 131 - } 132 - if repo.Repo.Did == "" { 133 - return nil, gorm.ErrRecordNotFound 134 - } 135 - return &repo, nil 136 65 }
+5 -1
server/handle_server_delete_account.go
··· 71 71 } 72 72 73 73 // --------------------------------------------------------------------------- 74 - // com.atproto.server.deleteAccount — unsupported 74 + // com.atproto.server.requestAccountDelete/deleteAccount — unsupported 75 75 // --------------------------------------------------------------------------- 76 + 77 + func (s *Server) handleServerRequestAccountDelete(w http.ResponseWriter, r *http.Request) { 78 + helpers.InputError(w, new("Account deletion not supported here. Login on Vow and delete the account there.")) 79 + } 76 80 77 81 func (s *Server) handleServerDeleteAccount(w http.ResponseWriter, r *http.Request) { 78 82 helpers.InputError(w, new("Account deletion not supported here. Login on Vow and delete the account there."))
-53
server/handle_server_request_account_delete.go
··· 1 - package server 2 - 3 - import ( 4 - "fmt" 5 - "net/http" 6 - "time" 7 - 8 - "pkg.rbrt.fr/vow/internal/helpers" 9 - "pkg.rbrt.fr/vow/models" 10 - ) 11 - 12 - func (s *Server) handleServerRequestAccountDelete(w http.ResponseWriter, r *http.Request) { 13 - ctx := r.Context() 14 - logger := s.logger.With("name", "handleServerRequestAccountDelete") 15 - 16 - urepo, _ := getContextValue[*models.RepoActor](r, contextKeyRepo) 17 - 18 - token := fmt.Sprintf("%s-%s", helpers.RandomVarchar(5), helpers.RandomVarchar(5)) 19 - expiresAt := time.Now().UTC().Add(15 * time.Minute) 20 - 21 - if err := s.db.Exec(ctx, "UPDATE repos SET account_delete_code = ?, account_delete_code_expires_at = ? WHERE did = ?", nil, token, expiresAt, urepo.Repo.Did).Error; err != nil { 22 - logger.Error("error setting deletion token", "error", err) 23 - helpers.ServerError(w, nil) 24 - return 25 - } 26 - 27 - if urepo.Email != "" { 28 - if err := s.sendAccountDeleteEmail(urepo.Email, urepo.Handle, token); err != nil { 29 - logger.Error("error sending account deletion email", "error", err) 30 - } 31 - } 32 - 33 - w.WriteHeader(http.StatusOK) 34 - } 35 - 36 - func (s *Server) sendAccountDeleteEmail(email, handle, token string) error { 37 - if s.mail == nil { 38 - return nil 39 - } 40 - 41 - s.mailLk.Lock() 42 - defer s.mailLk.Unlock() 43 - 44 - s.mail.To(email) 45 - s.mail.Subject("Account Deletion Request for " + s.config.Hostname) 46 - s.mail.Plain().Set(fmt.Sprintf("Hello %s. Your account deletion code is %s. This code will expire in fifteen minutes. If you did not request this, please ignore this email.", handle, token)) 47 - 48 - if err := s.mail.Send(); err != nil { 49 - return err 50 - } 51 - 52 - return nil 53 - }