package core import ( "encoding/json" "errors" "github.com/google/uuid" "log" "net/http" "strings" ) type NKodeHandler struct { Api NKodeAPI } const ( CreateNewCustomer = "/create-new-customer" GenerateSignupResetInterface = "/generate-signup-reset-interface" SetNKode = "/set-nkode" ConfirmNKode = "/confirm-nkode" GetLoginInterface = "/get-login-interface" Login = "/login" RenewAttributes = "/renew-attributes" RandomSvgInterface = "/random-svg-interface" RefreshToken = "/refresh-token" ResetNKode = "/reset-nkode" ) const ( malformedCustomerId = "malformed customer id" malformedUserEmail = "malformed user email" malformedSessionId = "malformed session id" ) func (h *NKodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case CreateNewCustomer: h.CreateNewCustomerHandler(w, r) case GenerateSignupResetInterface: h.GenerateSignupResetInterfaceHandler(w, r) case SetNKode: h.SetNKodeHandler(w, r) case ConfirmNKode: h.ConfirmNKodeHandler(w, r) case GetLoginInterface: h.GetLoginInterfaceHandler(w, r) case Login: h.LoginHandler(w, r) case RenewAttributes: h.RenewAttributesHandler(w, r) case RandomSvgInterface: h.RandomSvgInterfaceHandler(w, r) case RefreshToken: h.RefreshTokenHandler(w, r) case ResetNKode: h.ResetNKode(w, r) default: w.WriteHeader(http.StatusNotFound) _, err := w.Write([]byte("404 not found")) log.Println(err) } } func (h *NKodeHandler) CreateNewCustomerHandler(w http.ResponseWriter, r *http.Request) { log.Print("create new customer") if r.Method != http.MethodPost { methodNotAllowed(w) return } var customerPost NewCustomerPost if err := decodeJson(w, r, &customerPost); err != nil { return } customerId, err := h.Api.CreateNewCustomer(customerPost.NKodePolicy, nil) if err != nil { handleError(w, err) return } respBody := CreateNewCustomerResp{ CustomerId: uuid.UUID(*customerId).String(), } marshalAndWriteBytes(w, respBody) } func (h *NKodeHandler) GenerateSignupResetInterfaceHandler(w http.ResponseWriter, r *http.Request) { log.Print("signup/reset interface") if r.Method != http.MethodPost { methodNotAllowed(w) return } var signupResetPost GenerateSignupRestInterfacePost if err := decodeJson(w, r, &signupResetPost); err != nil { return } kp := KeypadDimension{ AttrsPerKey: signupResetPost.AttrsPerKey, NumbOfKeys: signupResetPost.NumbOfKeys, } if err := kp.IsValidKeypadDimension(); err != nil { badRequest(w, "invalid keypad dimensions") return } customerId, err := uuid.Parse(signupResetPost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } userEmail, err := ParseEmail(signupResetPost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } resp, err := h.Api.GenerateSignupResetInterface(userEmail, CustomerId(customerId), kp, signupResetPost.Reset) if err != nil { handleError(w, err) return } marshalAndWriteBytes(w, resp) } func (h *NKodeHandler) SetNKodeHandler(w http.ResponseWriter, r *http.Request) { log.Print("set nkode") if r.Method != http.MethodPost { methodNotAllowed(w) return } var setNKodePost SetNKodePost if err := decodeJson(w, r, &setNKodePost); err != nil { return } customerId, err := uuid.Parse(setNKodePost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } sessionId, err := uuid.Parse(setNKodePost.SessionId) if err != nil { badRequest(w, malformedSessionId) return } confirmInterface, err := h.Api.SetNKode(CustomerId(customerId), SessionId(sessionId), setNKodePost.KeySelection) if err != nil { handleError(w, err) return } respBody := SetNKodeResp{UserInterface: confirmInterface} marshalAndWriteBytes(w, respBody) } func (h *NKodeHandler) ConfirmNKodeHandler(w http.ResponseWriter, r *http.Request) { log.Print("confirm nkode") if r.Method != http.MethodPost { methodNotAllowed(w) return } var confirmNKodePost ConfirmNKodePost if err := decodeJson(w, r, &confirmNKodePost); err != nil { return } customerId, err := uuid.Parse(confirmNKodePost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } sessionId, err := uuid.Parse(confirmNKodePost.SessionId) if err != nil { badRequest(w, malformedSessionId) return } if err = h.Api.ConfirmNKode(CustomerId(customerId), SessionId(sessionId), confirmNKodePost.KeySelection); err != nil { handleError(w, err) return } w.WriteHeader(http.StatusOK) } func (h *NKodeHandler) GetLoginInterfaceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w) return } var loginInterfacePost GetLoginInterfacePost if err := decodeJson(w, r, &loginInterfacePost); err != nil { return } customerId, err := uuid.Parse(loginInterfacePost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } userEmail, err := ParseEmail(loginInterfacePost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) } loginInterface, err := h.Api.GetLoginInterface(userEmail, CustomerId(customerId)) if err != nil { handleError(w, err) return } marshalAndWriteBytes(w, loginInterface) } func (h *NKodeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w) return } var loginPost LoginPost if err := decodeJson(w, r, &loginPost); err != nil { return } customerId, err := uuid.Parse(loginPost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } userEmail, err := ParseEmail(loginPost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } jwtTokens, err := h.Api.Login(CustomerId(customerId), userEmail, loginPost.KeySelection) if err != nil { handleError(w, err) return } marshalAndWriteBytes(w, jwtTokens) } func (h *NKodeHandler) RenewAttributesHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w) return } var renewAttributesPost RenewAttributesPost if err := decodeJson(w, r, &renewAttributesPost); err != nil { return } customerId, err := uuid.Parse(renewAttributesPost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } if err = h.Api.RenewAttributes(CustomerId(customerId)); err != nil { handleError(w, err) return } w.WriteHeader(http.StatusOK) } func (h *NKodeHandler) RandomSvgInterfaceHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { methodNotAllowed(w) } svgs, err := h.Api.RandomSvgInterface() if err != nil { handleError(w, err) return } respBody := RandomSvgInterfaceResp{Svgs: svgs} marshalAndWriteBytes(w, respBody) } func (h *NKodeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { methodNotAllowed(w) } refreshToken, err := getBearerToken(r) if err != nil { forbidden(w) return } refreshClaims, err := ParseRegisteredClaimToken(refreshToken) customerId, err := uuid.Parse(refreshClaims.Issuer) if err != nil { badRequest(w, malformedCustomerId) return } userEmail, err := ParseEmail(refreshClaims.Subject) if err != nil { badRequest(w, malformedUserEmail) log.Println(err) return } accessToken, err := h.Api.RefreshToken(userEmail, CustomerId(customerId), refreshToken) if err != nil { handleError(w, err) log.Println(err) return } marshalAndWriteBytes(w, RefreshTokenResp{AccessToken: accessToken}) } func (h *NKodeHandler) ResetNKode(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w) } var resetNKodePost ResetNKodePost if err := decodeJson(w, r, &resetNKodePost); err != nil { return } customerId, err := uuid.Parse(resetNKodePost.CustomerId) if err != nil { badRequest(w, malformedCustomerId) return } userEmail, err := ParseEmail(resetNKodePost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } if err = h.Api.ResetNKode(userEmail, CustomerId(customerId)); err != nil { internalServerError(w) log.Println(err) return } w.WriteHeader(http.StatusOK) } func decodeJson(w http.ResponseWriter, r *http.Request, post any) error { if r.Body == nil { badRequest(w, "unable to parse body") log.Println("error decoding json: body is nil") return errors.New("body is nil") } err := json.NewDecoder(r.Body).Decode(&post) if err != nil { badRequest(w, "unable to parse body") log.Println("error decoding json: ", err) return err } return nil } func internalServerError(w http.ResponseWriter) { log.Print("500 internal server error") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("500 Internal Server Error")) } func badRequest(w http.ResponseWriter, msg string) { log.Print("bad request: ", msg) w.WriteHeader(http.StatusBadRequest) if msg == "" { w.Write([]byte("400 Bad Request")) } else { w.Write([]byte(msg)) } } func methodNotAllowed(w http.ResponseWriter) { log.Print("405 method not allowed") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte("405 method not allowed")) } func forbidden(w http.ResponseWriter) { log.Print("403 forbidden") w.WriteHeader(http.StatusForbidden) w.Write([]byte("403 Forbidden")) } func handleError(w http.ResponseWriter, err error) { log.Print("handling error: ", err) statusCode, exists := HttpErrMap[err] if !exists { internalServerError(w) return } switch statusCode { case http.StatusBadRequest: badRequest(w, err.Error()) case http.StatusForbidden: forbidden(w) case http.StatusInternalServerError: internalServerError(w) default: log.Print("unknown error: ", err) internalServerError(w) } } func getBearerToken(r *http.Request) (string, error) { authHeader := r.Header.Get("Authorization") // Check if the Authorization header is present and starts with "Bearer " if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") { return "", errors.New("forbidden") } token := strings.TrimPrefix(authHeader, "Bearer ") return token, nil } func marshalAndWriteBytes(w http.ResponseWriter, data any) { respBytes, err := json.Marshal(data) if err != nil { internalServerError(w) log.Println(err) return } _, err = w.Write(respBytes) if err != nil { internalServerError(w) log.Println(err) return } }