Compare commits
11 Commits
acaf4af4ce
...
d18855cd49
Author | SHA1 | Date | |
---|---|---|---|
d18855cd49 | |||
043026de75 | |||
e4b8480d28 | |||
081bfdee0f | |||
3f64e66f87 | |||
10464f402e | |||
555c937fce | |||
3d560cc5d8 | |||
7c659757e7 | |||
fd6f574b4f | |||
0963be7d72 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
.aider*
|
.aider*
|
||||||
golias
|
golias
|
||||||
|
store.json
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
{{define "main"}}
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -7,6 +8,8 @@
|
|||||||
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id=ancestor>
|
||||||
|
{{template "login"}}
|
||||||
<h1>Word Color Cards</h1>
|
<h1>Word Color Cards</h1>
|
||||||
<div style="display: flex; gap: 1rem; flex-wrap: wrap; padding: 1rem;">
|
<div style="display: flex; gap: 1rem; flex-wrap: wrap; padding: 1rem;">
|
||||||
{{range $word, $color := .}}
|
{{range $word, $color := .}}
|
||||||
@ -18,11 +21,13 @@
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: white;
|
color: white;
|
||||||
text-shadow: 0 1px 2px rgba(0,0,0,0.25);
|
text-shadow: 0 2px 4px rgba(0,0,0,0.8);
|
||||||
">
|
">
|
||||||
{{$word}}
|
{{$word}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
{{end}}
|
||||||
|
16
components/login.html
Normal file
16
components/login.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{{define "login"}}
|
||||||
|
<div id="logindiv">
|
||||||
|
<form class="space-y-6" hx-post="/login" hx-target="#ancestor">
|
||||||
|
<div>
|
||||||
|
<label For="username" class="block text-sm font-medium leading-6 text-white-900">tell us your username</label>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input id="username" name="username" hx-target="#login_notice" hx-swap="outerHTML" hx-post="/check/name" hx-trigger="input changed delay:400ms" autocomplete="username" required class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 text-center"/>
|
||||||
|
</div>
|
||||||
|
<div id="login_notice">this name looks available</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Sign in</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
@ -8,7 +8,6 @@ import (
|
|||||||
"golias/models"
|
"golias/models"
|
||||||
"golias/utils"
|
"golias/utils"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -24,7 +23,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
username := r.PostFormValue("username")
|
username := r.PostFormValue("username")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
msg := "username not provided"
|
msg := "username not provided"
|
||||||
slog.Error(msg)
|
log.Error(msg)
|
||||||
abortWithError(w, msg)
|
abortWithError(w, msg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -34,7 +33,7 @@ func HandleFrontLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
// login user
|
// login user
|
||||||
cookie, err := makeCookie(cleanName, r.RemoteAddr)
|
cookie, err := makeCookie(cleanName, r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to login", "error", err)
|
log.Error("failed to login", "error", err)
|
||||||
abortWithError(w, err.Error())
|
abortWithError(w, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -75,13 +74,13 @@ func makeCookie(username string, remote string) (*http.Cookie, error) {
|
|||||||
SameSite: http.SameSiteNoneMode,
|
SameSite: http.SameSiteNoneMode,
|
||||||
Domain: cfg.ServerConfig.Host,
|
Domain: cfg.ServerConfig.Host,
|
||||||
}
|
}
|
||||||
slog.Info("check remote addr for cookie set",
|
log.Info("check remote addr for cookie set",
|
||||||
"remote", remote, "session", session)
|
"remote", remote, "session", session)
|
||||||
if strings.Contains(remote, "192.168.0") {
|
if strings.Contains(remote, "192.168.0") {
|
||||||
// no idea what is going on
|
// no idea what is going on
|
||||||
// cookie.Domain = "192.168.0.15"
|
// cookie.Domain = "192.168.0.15"
|
||||||
cookie.Domain = "home.host"
|
cookie.Domain = "home.host"
|
||||||
slog.Info("changing cookie domain", "domain", cookie.Domain)
|
log.Info("changing cookie domain", "domain", cookie.Domain)
|
||||||
}
|
}
|
||||||
// set ctx?
|
// set ctx?
|
||||||
// set user in session
|
// set user in session
|
||||||
|
@ -2,9 +2,20 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var log *slog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log = slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
|
||||||
|
Level: slog.LevelDebug,
|
||||||
|
AddSource: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
var roundWords = map[string]string{
|
var roundWords = map[string]string{
|
||||||
"hamster": "blue",
|
"hamster": "blue",
|
||||||
"child": "red",
|
"child": "red",
|
||||||
@ -18,6 +29,10 @@ func HandlePing(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func HandleHome(w http.ResponseWriter, r *http.Request) {
|
func HandleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
tmpl := template.Must(template.ParseFiles("components/index.html"))
|
tmpl, err := template.ParseGlob("components/*.html")
|
||||||
tmpl.Execute(w, roundWords)
|
if err != nil {
|
||||||
|
abortWithError(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpl.ExecuteTemplate(w, "main", roundWords)
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"golias/config"
|
"golias/config"
|
||||||
"golias/pkg/cache"
|
"golias/pkg/cache"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -17,13 +17,41 @@ var (
|
|||||||
memcache cache.Cache
|
memcache cache.Cache
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// responseWriterWrapper wraps http.ResponseWriter to capture status code
|
||||||
|
type responseWriterWrapper struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriterWrapper) WriteHeader(status int) {
|
||||||
|
w.status = status
|
||||||
|
w.ResponseWriter.WriteHeader(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogRequests logs all HTTP requests with method, path and duration
|
||||||
|
func LogRequests(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
// Wrap response writer to capture status code
|
||||||
|
ww := &responseWriterWrapper{ResponseWriter: w}
|
||||||
|
next.ServeHTTP(ww, r)
|
||||||
|
duration := time.Since(start)
|
||||||
|
log.Debug("request completed",
|
||||||
|
"method", r.Method,
|
||||||
|
"path", r.URL.Path,
|
||||||
|
"status", ww.status,
|
||||||
|
"duration", duration.String(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func GetSession(next http.Handler) http.Handler {
|
func GetSession(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
cookieName := "session_token"
|
cookieName := "session_token"
|
||||||
sessionCookie, err := r.Cookie(cookieName)
|
sessionCookie, err := r.Cookie(cookieName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "auth failed; failed to get session token from cookies"
|
msg := "auth failed; failed to get session token from cookies"
|
||||||
slog.Debug(msg, "error", err)
|
log.Debug(msg, "error", err)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -31,13 +59,13 @@ func GetSession(next http.Handler) http.Handler {
|
|||||||
DecodeString(sessionCookie.Value)
|
DecodeString(sessionCookie.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "auth failed; failed to decode b64 cookie"
|
msg := "auth failed; failed to decode b64 cookie"
|
||||||
slog.Debug(msg, "error", err)
|
log.Debug(msg, "error", err)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookieValue := string(cookieValueB)
|
cookieValue := string(cookieValueB)
|
||||||
if len(cookieValue) < sha256.Size {
|
if len(cookieValue) < sha256.Size {
|
||||||
slog.Warn("small cookie", "size", len(cookieValue))
|
log.Warn("small cookie", "size", len(cookieValue))
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -50,7 +78,7 @@ func GetSession(next http.Handler) http.Handler {
|
|||||||
mac.Write([]byte(sessionToken))
|
mac.Write([]byte(sessionToken))
|
||||||
expectedSignature := mac.Sum(nil)
|
expectedSignature := mac.Sum(nil)
|
||||||
if !hmac.Equal([]byte(signature), expectedSignature) {
|
if !hmac.Equal([]byte(signature), expectedSignature) {
|
||||||
slog.Debug("cookie with an invalid sign")
|
log.Debug("cookie with an invalid sign")
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -58,14 +86,14 @@ func GetSession(next http.Handler) http.Handler {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
msg := "auth failed; session does not exists"
|
msg := "auth failed; session does not exists"
|
||||||
err = errors.New(msg)
|
err = errors.New(msg)
|
||||||
slog.Debug(msg, "error", err)
|
log.Debug(msg, "error", err)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if userSession.IsExpired() {
|
if userSession.IsExpired() {
|
||||||
memcache.RemoveKey(sessionToken)
|
memcache.RemoveKey(sessionToken)
|
||||||
msg := "session is expired"
|
msg := "session is expired"
|
||||||
slog.Debug(msg, "error", err, "token", sessionToken)
|
log.Debug(msg, "error", err, "token", sessionToken)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -74,7 +102,7 @@ func GetSession(next http.Handler) http.Handler {
|
|||||||
if err := cacheSetSession(sessionToken,
|
if err := cacheSetSession(sessionToken,
|
||||||
userSession); err != nil {
|
userSession); err != nil {
|
||||||
msg := "failed to marshal user session"
|
msg := "failed to marshal user session"
|
||||||
slog.Warn(msg, "error", err)
|
log.Warn(msg, "error", err)
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
4
main.go
4
main.go
@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"golias/handlers"
|
"golias/handlers"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ import (
|
|||||||
func ListenToRequests(port string) error {
|
func ListenToRequests(port string) error {
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Handler: mux,
|
Handler: handlers.LogRequests(handlers.GetSession(mux)),
|
||||||
Addr: port,
|
Addr: port,
|
||||||
ReadTimeout: time.Second * 5,
|
ReadTimeout: time.Second * 5,
|
||||||
WriteTimeout: time.Second * 5,
|
WriteTimeout: time.Second * 5,
|
||||||
|
Reference in New Issue
Block a user