summaryrefslogtreecommitdiff
path: root/main.go
blob: 4f8501a85fc98c9b355cf59a76cda031499cabb9 (plain) (blame)
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
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
	"regexp"
	// "strconv"
	"strings"
	"time"
)

// Example:
// curl 'http://localhost:8080/v1/auth/login' -X POST -H 'Content-Type: application/json' --data-raw $'{}}\n{"king": 5'

const log_length = 100

type accessLog struct {
	ClientAddr    string    `json:"clientAddr"`
	RequestedPath string    `json:"requestedPath"`
	RequestTime   time.Time `json:"requestTime"`
	HttpMethod    string    `json:"httpMethod"`
}

type loginAttemptLog struct {
	Email      string    `json:"email"`
	Password   string    `json:"password"`
	LoginTime  time.Time `json:"loginTime"`
	Success    bool      `json:"success"`
	RememberMe bool      `json:"rememberMe"`
}

func main() {
	fs := http.FileServer(http.Dir("static"))

	var logs [log_length]string
	n := 0

	// Define a handler function for the root path
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		jsonData, _ := json.Marshal(accessLog{
			ClientAddr:    RedactIP(r.RemoteAddr),
			RequestedPath: r.URL.Path,
			RequestTime:   time.Now().UTC(),
			HttpMethod:    r.Method,
		})
		addRotLog(&logs, &n, string(jsonData))
		// Serve the index.html file from the static directory
		http.StripPrefix("/", fs).ServeHTTP(w, r)
	})

	http.HandleFunc("/logs", createGetLogs(&logs))

	http.HandleFunc("/v1/auth/login", func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(r.Body)
		if err != nil {
			http.Error(w, "Bad Request", http.StatusBadRequest)
			return
		}
		defer r.Body.Close()

		var data map[string]any
		if json.Unmarshal(body, &data) != nil {
			addRotLog(&logs, &n, fmt.Sprintf(`{"authRequest": %s}`, string(body)))
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}

		if email, ok := data["email"].(string); ok {
			if rememberMe, ok := data["rememberMe"].(bool); ok {
				addRotLog(&logs, &n, fmt.Sprintf(`{"authRequest": {"email": "%s", "password": "XXXXXXXX", "loginTime": "%s", "success": false, "rememberMe": %t}}`, email, time.Now().UTC(), rememberMe))
			}
		}
		http.Error(w, "Forbidden", http.StatusForbidden)
	})

	// Start the server on port 8080
	fmt.Println("Server is listening on port 8080...")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Printf("Error starting server: %s\n", err)
	}
}

func createGetLogs(logs *[log_length]string) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		for _, s := range logs {
			fmt.Fprintln(w, s)
		}
	}
}

func addRotLog(logs *[log_length]string, last *int, value string) {
	if strings.Contains(value, "\n") {
		for _, v := range strings.Split(value, "\n") {
			addRotLog(logs, last, v)
		}
	} else {
		if *last == log_length {
			for i := 0; i < log_length-1; i++ {
				logs[i] = logs[i+1]
			}
			logs[log_length-1] = value
		} else {
			logs[*last] = value
			*last++
		}
	}
}

// RedactIP partially redacts an IP address by replacing the last octet with 'xxx'
func RedactIP(input string) string {
	ipRegex := `\b(?:\d{1,3}\.){3}\d{1,3}\b`
	re := regexp.MustCompile(ipRegex)

	return re.ReplaceAllStringFunc(input, func(match string) string {
		if ip := net.ParseIP(match); ip != nil {
			parts := strings.Split(match, ".")
			if len(parts) == 4 {
				parts[3] = "XXX"
				return strings.Join(parts, ".")
			}
		}
		return match
	})
}