package admin import ( "encoding/json" "net/http" "os" "time" "git.huntm.net/wedding/server/guest" "github.com/golang-jwt/jwt/v5" ) type AdminHandler struct { adminStore adminStore guestStore guest.GuestStore } type adminStore interface { Find(admin Admin) (Admin, error) } type appError struct { Error error Message string Code int } func NewAdminHandler(adminStore adminStore, guestStore guest.GuestStore) *AdminHandler { return &AdminHandler{adminStore, guestStore} } func (adminHandler *AdminHandler) ServeHTTP(responseWriter http.ResponseWriter, request *http.Request) { switch { case request.Method == http.MethodOptions: responseWriter.WriteHeader(http.StatusOK) case request.Method == http.MethodPost && request.URL.Path == "/api/admin/login": adminHandler.handleLogIn(responseWriter, request) default: responseWriter.WriteHeader(http.StatusNotFound) } } func (adminHandler *AdminHandler) handleLogIn(responseWriter http.ResponseWriter, request *http.Request) { token, err := adminHandler.logIn(request) if err != nil { http.Error(responseWriter, err.Message, err.Code) } else { responseWriter.Write(token) } } func (adminHandler *AdminHandler) logIn(request *http.Request) ([]byte, *appError) { requestAdmin, err := adminHandler.decodeCredentials(request) if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Failed to unmarshal request\" }", http.StatusBadRequest} } _, err = adminHandler.adminStore.Find(requestAdmin) if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Invalid username or password\" }", http.StatusUnauthorized} } expirationTime := adminHandler.setExpirationTime() claims := adminHandler.createClaims(requestAdmin, expirationTime) key, err := adminHandler.readKey() if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Failed to read secret key\" }", http.StatusInternalServerError} } token, err := adminHandler.createToken(claims, key) if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Failed to create token\" }", http.StatusInternalServerError} } guests, err := adminHandler.guestStore.Get() if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Failed to get guests\" }", http.StatusInternalServerError} } jsonBytes, err := adminHandler.marshalResponse(guests, token) if err != nil { return []byte{}, &appError{err, "{ \"message\": \"Failed to marshal response\" }", http.StatusInternalServerError} } return jsonBytes, nil } func (adminHandler *AdminHandler) decodeCredentials(request *http.Request) (Admin, error) { var admin Admin err := json.NewDecoder(request.Body).Decode(&admin) defer request.Body.Close() return admin, err } func (adminHandler *AdminHandler) setExpirationTime() time.Time { return time.Now().Add(15 * time.Minute) } func (adminHandler *AdminHandler) createClaims(admin Admin, expirationTime time.Time) *Claims { return &Claims{ admin, jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expirationTime), }, } } func (adminHandler *AdminHandler) readKey() ([]byte, error) { // TODO: use properties file return os.ReadFile("C:\\Users\\mhunt\\admin.pem") } func (adminHandler *AdminHandler) createToken(claims *Claims, key []byte) (string, error) { token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString(key) } func (adminHandler *AdminHandler) marshalResponse(guests []guest.Guest, token string) ([]byte, error) { loginResponse := adminHandler.createLoginResponse(guests, token) return json.Marshal(loginResponse) } func (adminHandler *AdminHandler) createLoginResponse(guests []guest.Guest, token string) *Login { return &Login{guests, token} }