summaryrefslogtreecommitdiff
path: root/server/admin/handler.go
blob: 0aa96594e7c22b5d96b25dd8cba0ac667138ba04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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}
}