welcome: username error handling (#75)
* username error handling welcome ui
* added short username check with separate error handling function
* nothing happaned here
* added username error handling, form saves info
* pull75 fixes
* pull75 fixes ui
* CSS change
* separate css for errors, added logic for several type of errors
* rename extractErrorMessage
* validation changes
* added validations in api
* changed rendering template, recives errors in JSON format
* rolled back schema and makefile in kratos
* changes in HTML
* combined kratos and manual validations
* fixed rendering and handling JSON error response
* rollback unused index.html
* minor fixes
* refactored the repeated logic of Errors into a separate function
* rollback
* refactor: group errors and form data together
* rollback picocss version
* use picocss 2.0.6
---------
Co-authored-by: Giorgi Lekveishvili <lekva@gl-mbp-m1-max.local>
diff --git a/core/auth/ui/api.go b/core/auth/ui/api.go
index a0065a6..ab0ea76 100644
--- a/core/auth/ui/api.go
+++ b/core/auth/ui/api.go
@@ -4,7 +4,6 @@
"bytes"
"encoding/json"
"fmt"
- "io"
"net/http"
"github.com/gorilla/mux"
@@ -16,6 +15,14 @@
kratosAddr string
}
+type ErrorResponse struct {
+ Error struct {
+ Code int `json:"code"`
+ Status string `json:"status"`
+ Message string `json:"message"`
+ } `json:"error"`
+}
+
func NewAPIServer(port int, kratosAddr string) *APIServer {
r := mux.NewRouter()
serv := &http.Server{
@@ -52,25 +59,85 @@
Password string `json:"password,omitempty"`
}
+func extractKratosErrorMessage(errResp ErrorResponse) []ValidationError {
+ var errors []ValidationError
+ switch errResp.Error.Status {
+ case "Conflict":
+ errors = append(errors, ValidationError{"username", "Username is not available."})
+ case "Bad Request":
+ errors = append(errors, ValidationError{"username", "Username is less than 3 characters."})
+ default:
+ errors = append(errors, ValidationError{"username", "Unexpexted Error."})
+ }
+ return errors
+}
+
+type ValidationError struct {
+ Field string `json:"field"`
+ Message string `json:"message"`
+}
+
+type CombinedErrors struct {
+ Errors []ValidationError `json:"errors"`
+}
+
+func validateUsername(username string) []ValidationError {
+ var errors []ValidationError
+ if len(username) < 3 {
+ errors = append(errors, ValidationError{"username", "Username must be at least 3 characters long."})
+ }
+ // TODO other validations
+ return errors
+}
+
+func validatePassword(password string) []ValidationError {
+ var errors []ValidationError
+ if len(password) < 6 {
+ errors = append(errors, ValidationError{"password", "Password must be at least 6 characters long."})
+ }
+ // TODO other validations
+ return errors
+}
+
+func replyWithErrors(w http.ResponseWriter, errors []ValidationError) {
+ response := CombinedErrors{Errors: errors}
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusBadRequest)
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ http.Error(w, "failed to decode", http.StatusInternalServerError)
+ return
+ }
+}
+
func (s *APIServer) identityCreate(w http.ResponseWriter, r *http.Request) {
var req identityCreateReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "request can not be parsed", http.StatusBadRequest)
return
}
+ usernameErrors := validateUsername(req.Username)
+ passwordErrors := validatePassword(req.Password)
+ allErrors := append(usernameErrors, passwordErrors...)
+ if len(allErrors) > 0 {
+ replyWithErrors(w, allErrors)
+ return
+ }
var buf bytes.Buffer
fmt.Fprintf(&buf, identityCreateTmpl, req.Password, req.Username)
resp, err := http.Post(s.identitiesEndpoint(), "application/json", &buf)
if err != nil {
http.Error(w, "failed", http.StatusInternalServerError)
return
- } else if resp.StatusCode != http.StatusCreated {
- var buf bytes.Buffer
- if _, err := io.Copy(&buf, resp.Body); err != nil {
- http.Error(w, "failed to copy response body", http.StatusInternalServerError)
- } else {
- http.Error(w, buf.String(), resp.StatusCode)
+ }
+ if resp.StatusCode != http.StatusCreated {
+ var e ErrorResponse
+ if err := json.NewDecoder(resp.Body).Decode(&e); err != nil {
+ http.Error(w, "failed to decode", http.StatusInternalServerError)
+ return
}
+ errorMessages := extractKratosErrorMessage(e)
+ replyWithErrors(w, errorMessages)
+ return
}
}