Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

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

appview/{dns,signup}: make signup flow transactional

authored by

Anirudh Oppiliappan and committed by
Tangled
ce1f2b90 0bba9fb3

+119 -44
+4 -4
appview/dns/cloudflare.go
··· 30 30 return &Cloudflare{api: api, zone: c.Cloudflare.ZoneId}, nil 31 31 } 32 32 33 - func (cf *Cloudflare) CreateDNSRecord(ctx context.Context, record Record) error { 34 - _, err := cf.api.CreateDNSRecord(ctx, cloudflare.ZoneIdentifier(cf.zone), cloudflare.CreateDNSRecordParams{ 33 + func (cf *Cloudflare) CreateDNSRecord(ctx context.Context, record Record) (string, error) { 34 + result, err := cf.api.CreateDNSRecord(ctx, cloudflare.ZoneIdentifier(cf.zone), cloudflare.CreateDNSRecordParams{ 35 35 Type: record.Type, 36 36 Name: record.Name, 37 37 Content: record.Content, ··· 39 39 Proxied: &record.Proxied, 40 40 }) 41 41 if err != nil { 42 - return fmt.Errorf("failed to create DNS record: %w", err) 42 + return "", fmt.Errorf("failed to create DNS record: %w", err) 43 43 } 44 - return nil 44 + return result.ID, nil 45 45 } 46 46 47 47 func (cf *Cloudflare) DeleteDNSRecord(ctx context.Context, recordID string) error {
+18
appview/signup/requests.go
··· 102 102 103 103 return result.DID, nil 104 104 } 105 + 106 + func (s *Signup) deleteAccountRequest(did string) error { 107 + body := map[string]string{ 108 + "did": did, 109 + } 110 + 111 + resp, err := s.makePdsRequest("POST", "com.atproto.admin.deleteAccount", body, true) 112 + if err != nil { 113 + return err 114 + } 115 + defer resp.Body.Close() 116 + 117 + if resp.StatusCode != http.StatusOK { 118 + return s.handlePdsError(resp, "delete account") 119 + } 120 + 121 + return nil 122 + }
+97 -40
appview/signup/signup.go
··· 2 2 3 3 import ( 4 4 "bufio" 5 + "context" 5 6 "encoding/json" 6 7 "errors" 7 8 "fmt" ··· 217 216 return 218 217 } 219 218 220 - did, err := s.createAccountRequest(username, password, email, code) 221 - if err != nil { 222 - s.l.Error("failed to create account", "error", err) 223 - s.pages.Notice(w, "signup-error", err.Error()) 224 - return 225 - } 226 - 227 219 if s.cf == nil { 228 220 s.l.Error("cloudflare client is nil", "error", "Cloudflare integration is not enabled in configuration") 229 221 s.pages.Notice(w, "signup-error", "Account signup is currently disabled. DNS record creation is not available. Please contact support.") 230 222 return 231 223 } 232 224 233 - err = s.cf.CreateDNSRecord(r.Context(), dns.Record{ 234 - Type: "TXT", 235 - Name: "_atproto." + username, 236 - Content: fmt.Sprintf(`"did=%s"`, did), 237 - TTL: 6400, 238 - Proxied: false, 239 - }) 225 + // Execute signup transactionally with rollback capability 226 + err = s.executeSignupTransaction(r.Context(), username, password, email, code, w) 240 227 if err != nil { 241 - s.l.Error("failed to create DNS record", "error", err) 242 - s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") 228 + // Error already logged and notice already sent 243 229 return 244 230 } 245 - 246 - err = db.AddEmail(s.db, models.Email{ 247 - Did: did, 248 - Address: email, 249 - Verified: true, 250 - Primary: true, 251 - }) 252 - if err != nil { 253 - s.l.Error("failed to add email", "error", err) 254 - s.pages.Notice(w, "signup-error", "Failed to complete sign up. Try again later.") 255 - return 256 - } 257 - 258 - s.pages.Notice(w, "signup-msg", fmt.Sprintf(`Account created successfully. You can now 259 - <a class="underline text-black dark:text-white" href="/login">login</a> 260 - with <code>%s.tngl.sh</code>.`, username)) 261 - 262 - go func() { 263 - err := db.DeleteInflightSignup(s.db, email) 264 - if err != nil { 265 - s.l.Error("failed to delete inflight signup", "error", err) 266 - } 267 - }() 268 - return 269 231 } 232 + } 233 + 234 + // executeSignupTransaction performs the signup process transactionally with rollback 235 + func (s *Signup) executeSignupTransaction(ctx context.Context, username, password, email, code string, w http.ResponseWriter) error { 236 + var recordID string 237 + var did string 238 + var emailAdded bool 239 + 240 + success := false 241 + defer func() { 242 + if !success { 243 + s.l.Info("rolling back signup transaction", "username", username, "did", did) 244 + 245 + // Rollback DNS record 246 + if recordID != "" { 247 + if err := s.cf.DeleteDNSRecord(ctx, recordID); err != nil { 248 + s.l.Error("failed to rollback DNS record", "error", err, "recordID", recordID) 249 + } else { 250 + s.l.Info("successfully rolled back DNS record", "recordID", recordID) 251 + } 252 + } 253 + 254 + // Rollback PDS account 255 + if did != "" { 256 + if err := s.deleteAccountRequest(did); err != nil { 257 + s.l.Error("failed to rollback PDS account", "error", err, "did", did) 258 + } else { 259 + s.l.Info("successfully rolled back PDS account", "did", did) 260 + } 261 + } 262 + 263 + // Rollback email from database 264 + if emailAdded { 265 + if err := db.DeleteEmail(s.db, did, email); err != nil { 266 + s.l.Error("failed to rollback email from database", "error", err, "email", email) 267 + } else { 268 + s.l.Info("successfully rolled back email from database", "email", email) 269 + } 270 + } 271 + } 272 + }() 273 + 274 + // step 1: create account in PDS 275 + did, err := s.createAccountRequest(username, password, email, code) 276 + if err != nil { 277 + s.l.Error("failed to create account", "error", err) 278 + s.pages.Notice(w, "signup-error", err.Error()) 279 + return err 280 + } 281 + 282 + // step 2: create DNS record with actual DID 283 + recordID, err = s.cf.CreateDNSRecord(ctx, dns.Record{ 284 + Type: "TXT", 285 + Name: "_atproto." + username, 286 + Content: fmt.Sprintf(`"did=%s"`, did), 287 + TTL: 6400, 288 + Proxied: false, 289 + }) 290 + if err != nil { 291 + s.l.Error("failed to create DNS record", "error", err) 292 + s.pages.Notice(w, "signup-error", "Failed to create DNS record for your handle. Please contact support.") 293 + return err 294 + } 295 + 296 + // step 3: add email to database 297 + err = db.AddEmail(s.db, models.Email{ 298 + Did: did, 299 + Address: email, 300 + Verified: true, 301 + Primary: true, 302 + }) 303 + if err != nil { 304 + s.l.Error("failed to add email", "error", err) 305 + s.pages.Notice(w, "signup-error", "Failed to complete sign up. Try again later.") 306 + return err 307 + } 308 + emailAdded = true 309 + 310 + // if we get here, we've successfully created the account and added the email 311 + success = true 312 + 313 + s.pages.Notice(w, "signup-msg", fmt.Sprintf(`Account created successfully. You can now 314 + <a class="underline text-black dark:text-white" href="/login">login</a> 315 + with <code>%s.tngl.sh</code>.`, username)) 316 + 317 + // clean up inflight signup asynchronously 318 + go func() { 319 + if err := db.DeleteInflightSignup(s.db, email); err != nil { 320 + s.l.Error("failed to delete inflight signup", "error", err) 321 + } 322 + }() 323 + 324 + return nil 270 325 } 271 326 272 327 type turnstileResponse struct {