forked from tangled.org/core
this repo has no description
1package state 2 3import ( 4 "database/sql" 5 "errors" 6 "fmt" 7 "log" 8 "net/http" 9 "strings" 10 "time" 11 12 comatproto "github.com/bluesky-social/indigo/api/atproto" 13 lexutil "github.com/bluesky-social/indigo/lex/util" 14 "github.com/gliderlabs/ssh" 15 "github.com/google/uuid" 16 "github.com/sotangled/tangled/api/tangled" 17 "github.com/sotangled/tangled/appview/db" 18 "github.com/sotangled/tangled/appview/email" 19 "github.com/sotangled/tangled/appview/pages" 20) 21 22func (s *State) Settings(w http.ResponseWriter, r *http.Request) { 23 user := s.auth.GetUser(r) 24 pubKeys, err := db.GetPublicKeys(s.db, user.Did) 25 if err != nil { 26 log.Println(err) 27 } 28 29 emails, err := db.GetAllEmails(s.db, user.Did) 30 if err != nil { 31 log.Println(err) 32 } 33 34 s.pages.Settings(w, pages.SettingsParams{ 35 LoggedInUser: user, 36 PubKeys: pubKeys, 37 Emails: emails, 38 }) 39} 40 41func (s *State) SettingsEmails(w http.ResponseWriter, r *http.Request) { 42 switch r.Method { 43 case http.MethodGet: 44 s.pages.Notice(w, "settings-emails", "Unimplemented.") 45 log.Println("unimplemented") 46 return 47 case http.MethodPut: 48 did := s.auth.GetDid(r) 49 emAddr := r.FormValue("email") 50 emAddr = strings.TrimSpace(emAddr) 51 52 if !email.IsValidEmail(emAddr) { 53 s.pages.Notice(w, "settings-emails-error", "Invalid email address.") 54 return 55 } 56 57 // check if email already exists in database 58 existingEmail, err := db.GetEmail(s.db, did, emAddr) 59 if err != nil && !errors.Is(err, sql.ErrNoRows) { 60 log.Printf("checking for existing email: %s", err) 61 s.pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 62 return 63 } 64 65 if err == nil { 66 if existingEmail.Verified { 67 s.pages.Notice(w, "settings-emails-error", "This email is already verified.") 68 return 69 } 70 71 s.pages.Notice(w, "settings-emails-error", "This email is already added but not verified. Check your inbox for the verification link.") 72 return 73 } 74 75 code := uuid.New().String() 76 77 // Begin transaction 78 tx, err := s.db.Begin() 79 if err != nil { 80 log.Printf("failed to start transaction: %s", err) 81 s.pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 82 return 83 } 84 defer tx.Rollback() 85 86 if err := db.AddEmail(tx, db.Email{ 87 Did: did, 88 Address: emAddr, 89 Verified: false, 90 VerificationCode: code, 91 }); err != nil { 92 log.Printf("adding email: %s", err) 93 s.pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 94 return 95 } 96 97 err = email.SendEmail(email.Email{ 98 APIKey: s.config.ResendApiKey, 99 100 From: "noreply@notifs.tangled.sh", 101 To: emAddr, 102 Subject: "Verify your Tangled email", 103 Text: `Click the link below (or copy and paste it into your browser) to verify your email address. 104` + s.verifyUrl(did, emAddr, code), 105 Html: `<p>Click the link (or copy and paste it into your browser) to verify your email address.</p> 106<p><a href="` + s.verifyUrl(did, emAddr, code) + `">` + s.verifyUrl(did, emAddr, code) + `</a></p>`, 107 }) 108 109 if err != nil { 110 log.Printf("sending email: %s", err) 111 s.pages.Notice(w, "settings-emails-error", "Unable to send verification email at this moment, try again later.") 112 return 113 } 114 115 // Commit transaction 116 if err := tx.Commit(); err != nil { 117 log.Printf("failed to commit transaction: %s", err) 118 s.pages.Notice(w, "settings-emails-error", "Unable to add email at this moment, try again later.") 119 return 120 } 121 122 s.pages.Notice(w, "settings-emails-success", "Click the link in the email we sent you to verify your email address.") 123 return 124 case http.MethodDelete: 125 did := s.auth.GetDid(r) 126 emailAddr := r.FormValue("email") 127 emailAddr = strings.TrimSpace(emailAddr) 128 129 // Begin transaction 130 tx, err := s.db.Begin() 131 if err != nil { 132 log.Printf("failed to start transaction: %s", err) 133 s.pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 134 return 135 } 136 defer tx.Rollback() 137 138 if err := db.DeleteEmail(tx, did, emailAddr); err != nil { 139 log.Printf("deleting email: %s", err) 140 s.pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 141 return 142 } 143 144 // Commit transaction 145 if err := tx.Commit(); err != nil { 146 log.Printf("failed to commit transaction: %s", err) 147 s.pages.Notice(w, "settings-emails-error", "Unable to delete email at this moment, try again later.") 148 return 149 } 150 151 s.pages.HxLocation(w, "/settings") 152 return 153 } 154} 155 156func (s *State) verifyUrl(did string, email string, code string) string { 157 var appUrl string 158 if s.config.Dev { 159 appUrl = "http://" + s.config.ListenAddr 160 } else { 161 appUrl = "https://tangled.sh" 162 } 163 164 return fmt.Sprintf("%s/settings/emails/verify?did=%s&email=%s&code=%s", appUrl, did, email, code) 165} 166 167func (s *State) SettingsEmailsVerify(w http.ResponseWriter, r *http.Request) { 168 q := r.URL.Query() 169 170 // Get the parameters directly from the query 171 emailAddr := q.Get("email") 172 did := q.Get("did") 173 code := q.Get("code") 174 175 valid, err := db.CheckValidVerificationCode(s.db, did, emailAddr, code) 176 if err != nil { 177 log.Printf("checking email verification: %s", err) 178 s.pages.Notice(w, "settings-emails-error", "Error verifying email. Please try again later.") 179 return 180 } 181 182 if !valid { 183 s.pages.Notice(w, "settings-emails-error", "Invalid verification code. Please request a new verification email.") 184 return 185 } 186 187 // Mark email as verified in the database 188 if err := db.MarkEmailVerified(s.db, did, emailAddr); err != nil { 189 log.Printf("marking email as verified: %s", err) 190 s.pages.Notice(w, "settings-emails-error", "Error updating email verification status. Please try again later.") 191 return 192 } 193 194 http.Redirect(w, r, "/settings", http.StatusSeeOther) 195} 196 197func (s *State) SettingsEmailsPrimary(w http.ResponseWriter, r *http.Request) { 198 did := s.auth.GetDid(r) 199 emailAddr := r.FormValue("email") 200 emailAddr = strings.TrimSpace(emailAddr) 201 202 if emailAddr == "" { 203 s.pages.Notice(w, "settings-emails-error", "Email address cannot be empty.") 204 return 205 } 206 207 if err := db.MakeEmailPrimary(s.db, did, emailAddr); err != nil { 208 log.Printf("setting primary email: %s", err) 209 s.pages.Notice(w, "settings-emails-error", "Error setting primary email. Please try again later.") 210 return 211 } 212 213 s.pages.HxLocation(w, "/settings") 214} 215 216func (s *State) SettingsKeys(w http.ResponseWriter, r *http.Request) { 217 switch r.Method { 218 case http.MethodGet: 219 s.pages.Notice(w, "settings-keys", "Unimplemented.") 220 log.Println("unimplemented") 221 return 222 case http.MethodPut: 223 did := s.auth.GetDid(r) 224 key := r.FormValue("key") 225 key = strings.TrimSpace(key) 226 name := r.FormValue("name") 227 client, _ := s.auth.AuthorizedClient(r) 228 229 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key)) 230 if err != nil { 231 log.Printf("parsing public key: %s", err) 232 s.pages.Notice(w, "settings-keys", "That doesn't look like a valid public key. Make sure it's a <strong>public</strong> key.") 233 return 234 } 235 236 rkey := s.TID() 237 238 tx, err := s.db.Begin() 239 if err != nil { 240 log.Printf("failed to start tx; adding public key: %s", err) 241 s.pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 242 return 243 } 244 defer tx.Rollback() 245 246 if err := db.AddPublicKey(tx, did, name, key, rkey); err != nil { 247 log.Printf("adding public key: %s", err) 248 s.pages.Notice(w, "settings-keys", "Failed to add public key.") 249 return 250 } 251 252 // store in pds too 253 resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 254 Collection: tangled.PublicKeyNSID, 255 Repo: did, 256 Rkey: rkey, 257 Record: &lexutil.LexiconTypeDecoder{ 258 Val: &tangled.PublicKey{ 259 Created: time.Now().Format(time.RFC3339), 260 Key: key, 261 Name: name, 262 }}, 263 }) 264 // invalid record 265 if err != nil { 266 log.Printf("failed to create record: %s", err) 267 s.pages.Notice(w, "settings-keys", "Failed to create record.") 268 return 269 } 270 271 log.Println("created atproto record: ", resp.Uri) 272 273 err = tx.Commit() 274 if err != nil { 275 log.Printf("failed to commit tx; adding public key: %s", err) 276 s.pages.Notice(w, "settings-keys", "Unable to add public key at this moment, try again later.") 277 return 278 } 279 280 s.pages.HxLocation(w, "/settings") 281 return 282 283 case http.MethodDelete: 284 did := s.auth.GetDid(r) 285 q := r.URL.Query() 286 287 name := q.Get("name") 288 rkey := q.Get("rkey") 289 key := q.Get("key") 290 291 log.Println(name) 292 log.Println(rkey) 293 log.Println(key) 294 295 client, _ := s.auth.AuthorizedClient(r) 296 297 if err := db.RemovePublicKey(s.db, did, name, key); err != nil { 298 log.Printf("removing public key: %s", err) 299 s.pages.Notice(w, "settings-keys", "Failed to remove public key.") 300 return 301 } 302 303 if rkey != "" { 304 // remove from pds too 305 _, err := comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 306 Collection: tangled.PublicKeyNSID, 307 Repo: did, 308 Rkey: rkey, 309 }) 310 311 // invalid record 312 if err != nil { 313 log.Printf("failed to delete record from PDS: %s", err) 314 s.pages.Notice(w, "settings-keys", "Failed to remove key from PDS.") 315 return 316 } 317 } 318 log.Println("deleted successfully") 319 320 s.pages.HxLocation(w, "/settings") 321 return 322 } 323}