···102102 case http.MethodGet:
103103 o.pages.Login(w, pages.LoginParams{})
104104 case http.MethodPost:
105105- handle := strings.TrimPrefix(r.FormValue("handle"), "@")
105105+ handle := r.FormValue("handle")
106106+107107+ // when users copy their handle from bsky.app, it tends to have these characters around it:
108108+ //
109109+ // @nelind.dk:
110110+ // \u202a ensures that the handle is always rendered left to right and
111111+ // \u202c reverts that so the rest of the page renders however it should
112112+ handle = strings.TrimPrefix(handle, "\u202a")
113113+ handle = strings.TrimSuffix(handle, "\u202c")
114114+115115+ // `@` is harmless
116116+ handle = strings.TrimPrefix(handle, "@")
117117+118118+ // basic handle validation
119119+ if !strings.Contains(handle, ".") {
120120+ log.Println("invalid handle format", "raw", handle)
121121+ o.pages.Notice(w, "login-msg", fmt.Sprintf("\"%s\" is an invalid handle. Did you mean %s.bsky.social?", handle, handle))
122122+ return
123123+ }
106124107125 resolved, err := o.idResolver.ResolveIdent(r.Context(), handle)
108126 if err != nil {