this repo has no description
1package signup 2 3import ( 4 "fmt" 5 "log/slog" 6 "net/http" 7 8 "github.com/go-chi/chi/v5" 9 "github.com/posthog/posthog-go" 10 "tangled.sh/tangled.sh/core/appview/config" 11 "tangled.sh/tangled.sh/core/appview/db" 12 "tangled.sh/tangled.sh/core/appview/dns" 13 "tangled.sh/tangled.sh/core/appview/email" 14 "tangled.sh/tangled.sh/core/appview/pages" 15 "tangled.sh/tangled.sh/core/appview/state/userutil" 16 "tangled.sh/tangled.sh/core/appview/xrpcclient" 17) 18 19type Signup struct { 20 config *config.Config 21 db *db.DB 22 cf *dns.Cloudflare 23 posthog posthog.Client 24 xrpc *xrpcclient.Client 25 idResolver *idresolver.Resolver 26 pages *pages.Pages 27 l *slog.Logger 28} 29 30func New(cfg *config.Config, cf *dns.Cloudflare, database *db.DB, pc posthog.Client, idResolver *idresolver.Resolver, pages *pages.Pages, l *slog.Logger) *Signup { 31 return &Signup{ 32 config: cfg, 33 db: database, 34 cf: cf, 35 posthog: pc, 36 idResolver: idResolver, 37 pages: pages, 38 l: l, 39 } 40} 41 42func (s *Signup) Router() http.Handler { 43 r := chi.NewRouter() 44 r.Post("/", s.signup) 45 r.Get("/complete", s.complete) 46 r.Post("/complete", s.complete) 47 48 return r 49} 50 51func (s *Signup) signup(w http.ResponseWriter, r *http.Request) { 52 emailId := r.FormValue("email") 53 54 if !email.IsValidEmail(emailId) { 55 s.pages.Notice(w, "login-msg", "Invalid email address.") 56 return 57 } 58 59 exists, err := db.CheckEmailExistsAtAll(s.db, emailId) 60 if err != nil { 61 s.l.Error("failed to check email existence", "error", err) 62 s.pages.Notice(w, "login-msg", "Failed to complete signup. Try again later.") 63 return 64 } 65 if exists { 66 s.pages.Notice(w, "login-msg", "Email already exists.") 67 return 68 } 69 70 code, err := s.inviteCodeRequest() 71 if err != nil { 72 s.l.Error("failed to create invite code", "error", err) 73 s.pages.Notice(w, "login-msg", "Failed to create invite code.") 74 return 75 } 76 77 em := email.Email{ 78 APIKey: s.config.Resend.ApiKey, 79 From: s.config.Resend.SentFrom, 80 To: emailId, 81 Subject: "Verify your Tangled account", 82 Text: `Copy and paste this code below to verify your account on Tangled. 83 ` + code, 84 Html: `<p>Copy and paste this code below to verify your account on Tangled.</p> 85<p><code>` + code + `</code></p>`, 86 } 87 88 err = email.SendEmail(em) 89 if err != nil { 90 s.l.Error("failed to send email", "error", err) 91 s.pages.Notice(w, "login-msg", "Failed to send email.") 92 return 93 } 94 err = db.AddInflightSignup(s.db, db.InflightSignup{ 95 Email: emailId, 96 InviteCode: code, 97 }) 98 if err != nil { 99 s.l.Error("failed to add inflight signup", "error", err) 100 s.pages.Notice(w, "login-msg", "Failed to complete sign up. Try again later.") 101 return 102 } 103 104 s.pages.HxRedirect(w, "/signup/complete") 105} 106 107func (s *Signup) complete(w http.ResponseWriter, r *http.Request) { 108 switch r.Method { 109 case http.MethodGet: 110 s.pages.CompleteSignup(w, pages.SignupParams{}) 111 case http.MethodPost: 112 username := r.FormValue("username") 113 password := r.FormValue("password") 114 code := r.FormValue("code") 115 116 if !userutil.IsValidSubdomain(username) { 117 s.pages.Notice(w, "signup-error", "Invalid username. Username must be 4–63 characters, lowercase letters, digits, or hyphens, and can't start or end with a hyphen.") 118 return 119 } 120 121 email, err := db.GetEmailForCode(s.db, code) 122 if err != nil { 123 s.l.Error("failed to get email for code", "error", err) 124 s.pages.Notice(w, "signup-error", "Failed to complete sign up. Try again later.") 125 return 126 } 127 128 did, err := s.createAccountRequest(username, password, email, code) 129 if err != nil { 130 s.l.Error("failed to create account", "error", err) 131 s.pages.Notice(w, "signup-error", err.Error()) 132 return 133 } 134 135 err = s.cf.CreateDNSRecord(r.Context(), dns.Record{ 136 Type: "TXT", 137 Name: "_atproto." + username, 138 Content: "did=" + did, 139 TTL: 6400, 140 Proxied: false, 141 }) 142 if err != nil { 143 s.l.Error("failed to create DNS record", "error", err) 144 s.pages.Notice(w, "signup-error", "Failed to complete sign up. Try again later.") 145 return 146 } 147 148 err = db.AddEmail(s.db, db.Email{ 149 Did: did, 150 Address: email, 151 Verified: true, 152 Primary: true, 153 }) 154 if err != nil { 155 s.l.Error("failed to add email", "error", err) 156 s.pages.Notice(w, "signup-error", "Failed to complete sign up. Try again later.") 157 return 158 } 159 160 s.pages.Notice(w, "signup-msg", fmt.Sprintf(`Account created successfully. You can now 161 <a class="underline text-black dark:text-white" href="/login">login</a> 162 with <code>%s.tngl.sh</code>.`, username)) 163 164 go func() { 165 err := db.DeleteInflightSignup(s.db, email) 166 if err != nil { 167 s.l.Error("failed to delete inflight signup", "error", err) 168 } 169 }() 170 return 171 } 172}