88 lines
2.6 KiB
Go
Executable File
88 lines
2.6 KiB
Go
Executable File
package server
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"ddc/internal/auth"
|
|
)
|
|
|
|
type loginRequest struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
type sessionResponse struct {
|
|
Authenticated bool `json:"authenticated"`
|
|
}
|
|
|
|
func (server *Server) handleLogin(writer http.ResponseWriter, request *http.Request) {
|
|
client := server.clientAddress(request)
|
|
if !server.limiter.Allowed(client) {
|
|
slog.Warn("login blocked after too many failures", "client", client, "request_id", requestIDFrom(request))
|
|
writeError(writer, http.StatusTooManyRequests, "too many failed attempts, try again later")
|
|
return
|
|
}
|
|
|
|
var body loginRequest
|
|
if !decodeBody(writer, request, &body) {
|
|
return
|
|
}
|
|
if !auth.CredentialsMatch(server.settings.Username, server.settings.Password, body.Username, body.Password) {
|
|
server.limiter.RecordFailure(client)
|
|
slog.Warn("login failed", "client", client, "username", body.Username, "request_id", requestIDFrom(request))
|
|
writeError(writer, http.StatusUnauthorized, "invalid username or password")
|
|
return
|
|
}
|
|
|
|
server.limiter.Reset(client)
|
|
token, err := server.sessions.Create()
|
|
if err != nil {
|
|
writeFailure(writer, request, fmt.Errorf("could not create session: %w", err))
|
|
return
|
|
}
|
|
slog.Info("login succeeded", "client", client, "request_id", requestIDFrom(request))
|
|
http.SetCookie(writer, server.sessionCookie(token, int(server.settings.SessionTTL.Seconds())))
|
|
writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: true})
|
|
}
|
|
|
|
func (server *Server) handleLogout(writer http.ResponseWriter, request *http.Request) {
|
|
if cookie, err := request.Cookie(sessionCookieName); err == nil {
|
|
server.sessions.Delete(cookie.Value)
|
|
}
|
|
http.SetCookie(writer, server.sessionCookie("", -1))
|
|
writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: false})
|
|
}
|
|
|
|
func (server *Server) handleSession(writer http.ResponseWriter, request *http.Request) {
|
|
writeJSON(writer, http.StatusOK, sessionResponse{Authenticated: server.authenticated(request)})
|
|
}
|
|
|
|
func (server *Server) sessionCookie(value string, maxAge int) *http.Cookie {
|
|
return &http.Cookie{
|
|
Name: sessionCookieName,
|
|
Value: value,
|
|
Path: "/",
|
|
MaxAge: maxAge,
|
|
HttpOnly: true,
|
|
Secure: server.settings.CookieSecure,
|
|
SameSite: http.SameSiteStrictMode,
|
|
}
|
|
}
|
|
|
|
func (server *Server) clientAddress(request *http.Request) string {
|
|
if server.settings.TrustProxyHeaders {
|
|
if realIP := strings.TrimSpace(request.Header.Get("X-Real-IP")); realIP != "" {
|
|
return realIP
|
|
}
|
|
}
|
|
host, _, err := net.SplitHostPort(request.RemoteAddr)
|
|
if err != nil {
|
|
return request.RemoteAddr
|
|
}
|
|
return host
|
|
}
|