diff options
Diffstat (limited to 'server/admin')
-rw-r--r-- | server/admin/handler.go | 119 | ||||
-rw-r--r-- | server/admin/models.go | 21 | ||||
-rw-r--r-- | server/admin/store.go | 49 |
3 files changed, 189 insertions, 0 deletions
diff --git a/server/admin/handler.go b/server/admin/handler.go new file mode 100644 index 0000000..5fd1fee --- /dev/null +++ b/server/admin/handler.go @@ -0,0 +1,119 @@ +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 == "/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, "failed to unmarshal request", http.StatusBadRequest} + } + _, err = adminHandler.adminStore.Find(requestAdmin) + if err != nil { + return []byte{}, &appError{err, "admin not found", http.StatusUnauthorized} + } + expirationTime := adminHandler.setExpirationTime() + claims := adminHandler.createClaims(requestAdmin, expirationTime) + key, err := adminHandler.readKey() + if err != nil { + return []byte{}, &appError{err, "failed to read secret key", http.StatusInternalServerError} + } + token, err := adminHandler.createToken(claims, key) + if err != nil { + return []byte{}, &appError{err, "failed to create token", http.StatusInternalServerError} + } + guests, err := adminHandler.guestStore.Get() + if err != nil { + return []byte{}, &appError{err, "failed to get guests", http.StatusInternalServerError} + } + jsonBytes, err := adminHandler.marshalResponse(guests, token) + if err != nil { + return []byte{}, &appError{err, "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) *LoginResponse { + return &LoginResponse{guests, token} +} diff --git a/server/admin/models.go b/server/admin/models.go new file mode 100644 index 0000000..d9b8232 --- /dev/null +++ b/server/admin/models.go @@ -0,0 +1,21 @@ +package admin + +import ( + "git.huntm.net/wedding/server/guest" + "github.com/golang-jwt/jwt/v5" +) + +type Admin struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type Claims struct { + Admin Admin `json:"admin"` + jwt.RegisteredClaims +} + +type LoginResponse struct { + Guests []guest.Guest `json:"guests"` + Token string `json:"token"` +} diff --git a/server/admin/store.go b/server/admin/store.go new file mode 100644 index 0000000..437e6af --- /dev/null +++ b/server/admin/store.go @@ -0,0 +1,49 @@ +package admin + +import ( + "context" + "errors" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" +) + +type Store struct { + database *pgxpool.Pool +} + +func NewStore(database *pgxpool.Pool) *Store { + return &Store{ + database, + } +} + +func (store Store) Find(requestAdmin Admin) (Admin, error) { + adminRows, err := store.database.Query(context.Background(), + "select * from admin") + if err != nil { + return Admin{}, err + } + defer adminRows.Close() + admin, found := createAdmin(requestAdmin, adminRows) + + if found { + return admin, nil + } + return Admin{}, errors.New("admin not found") +} + +func createAdmin(requestAdmin Admin, adminRows pgx.Rows) (Admin, bool) { + var databaseAdmin Admin + for adminRows.Next() { + err := adminRows.Scan(&databaseAdmin.Username, &databaseAdmin.Password) + if err != nil { + return Admin{}, false + } + if databaseAdmin.Username == requestAdmin.Username && + databaseAdmin.Password == requestAdmin.Password { + return databaseAdmin, true + } + } + return Admin{}, false +} |