4 files changed,
90 insertions(+),
4 deletions(-)
Author:
Oleksandr Smirnov
olexsmir@gmail.com
Committed at:
2026-04-22 20:13:38 +0300
Authored at:
2026-04-22 19:52:45 +0300
Change ID:
wsuzotwwmmskkuwwnqmnqnxtzqsswyxw
Parent:
4f31c23
jump to
| M | app/app.go |
| M | app/config.go |
| M | app/http.go |
| A | app/http_test.go |
M
app/app.go
··· 85 85 } 86 86 87 87 // http server 88 - // TODO: opt in auth middleware 89 88 handler := a.recoverMiddleware(a.mux) 89 + handler = a.authMiddleware(handler) 90 90 handler = a.loggingMiddleware(handler) 91 91 httpSrv := &http.Server{ 92 92 Addr: fmt.Sprintf(":%d", a.Config.Port), // fixme
M
app/config.go
··· 6 6 ) 7 7 8 8 type Config struct { 9 - Port int `json:"port"` 10 - TGUserID int64 `json:"tg_userid"` 11 - TGToken string `json:"tg_token"` 9 + Port int `json:"port"` 10 + AuthToken string `json:"auth_token"` 11 + TGUserID int64 `json:"tg_userid"` 12 + TGToken string `json:"tg_token"` 12 13 } 13 14 14 15 func NewConfig(fpath string) (*Config, error) {
M
app/http.go
··· 3 3 import ( 4 4 "log/slog" 5 5 "net/http" 6 + "strings" 6 7 "time" 7 8 ) 8 9 ··· 30 31 "latency", time.Since(start).String(), 31 32 "ua", r.UserAgent(), 32 33 ) 34 + }) 35 +} 36 + 37 +func (a *App) authMiddleware(next http.Handler) http.Handler { 38 + if a.Config == nil || strings.TrimSpace(a.Config.AuthToken) == "" { 39 + return next 40 + } 41 + 42 + expected := strings.TrimSpace(a.Config.AuthToken) 43 + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 44 + queryToken := strings.TrimSpace(r.URL.Query().Get("token")) 45 + headerToken := strings.TrimSpace(r.Header.Get("Authorization")) 46 + if queryToken == expected || headerToken == expected { 47 + next.ServeHTTP(w, r) 48 + return 49 + } 50 + 51 + http.Error(w, "unauthorized", http.StatusUnauthorized) 33 52 }) 34 53 } 35 54
A
app/http_test.go
··· 1 +package app 2 + 3 +import ( 4 + "net/http" 5 + "net/http/httptest" 6 + "strings" 7 + "testing" 8 + 9 + "olexsmir.xyz/x/is" 10 +) 11 + 12 +func TestAuthMiddlewareDisabledWithoutToken(t *testing.T) { 13 + a := &App{Config: &Config{}} 14 + handler := a.authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 15 + w.WriteHeader(http.StatusNoContent) 16 + })) 17 + 18 + r := httptest.NewRequest(http.MethodGet, "/telegram", nil) 19 + w := httptest.NewRecorder() 20 + handler.ServeHTTP(w, r) 21 + 22 + is.Equal(t, http.StatusNoContent, w.Code) 23 +} 24 + 25 +func TestAuthMiddlewareAllowsQueryToken(t *testing.T) { 26 + a := &App{Config: &Config{AuthToken: "secret"}} 27 + handler := a.authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 28 + w.WriteHeader(http.StatusNoContent) 29 + })) 30 + 31 + r := httptest.NewRequest(http.MethodGet, "/telegram?token=secret", nil) 32 + w := httptest.NewRecorder() 33 + handler.ServeHTTP(w, r) 34 + 35 + is.Equal(t, http.StatusNoContent, w.Code) 36 +} 37 + 38 +func TestAuthMiddlewareAllowsAuthorizationHeader(t *testing.T) { 39 + a := &App{Config: &Config{AuthToken: "secret"}} 40 + handler := a.authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 41 + w.WriteHeader(http.StatusNoContent) 42 + })) 43 + 44 + r := httptest.NewRequest(http.MethodGet, "/telegram", nil) 45 + r.Header.Set("Authorization", "secret") 46 + w := httptest.NewRecorder() 47 + handler.ServeHTTP(w, r) 48 + 49 + is.Equal(t, http.StatusNoContent, w.Code) 50 +} 51 + 52 +func TestAuthMiddlewareRejectsBadToken(t *testing.T) { 53 + a := &App{Config: &Config{AuthToken: "secret"}} 54 + handler := a.authMiddleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 55 + w.WriteHeader(http.StatusNoContent) 56 + })) 57 + 58 + r := httptest.NewRequest(http.MethodGet, "/telegram?token=bad", nil) 59 + w := httptest.NewRecorder() 60 + handler.ServeHTTP(w, r) 61 + 62 + is.Equal(t, http.StatusUnauthorized, w.Code) 63 + if !strings.Contains(w.Body.String(), "unauthorized") { 64 + t.Fatalf("unexpected body: %q", w.Body.String()) 65 + } 66 +}