From 9d19a567b4f9ac67b1813388abc04ea9b9a8d81f Mon Sep 17 00:00:00 2001 From: Benito Gomez Date: Thu, 30 Jul 2026 15:57:38 +0800 Subject: [PATCH 1/2] Add MIT license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..46cb3f5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Benito Gomez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. From 77ec0356f267b41afb1d0212c45b86876740c41b Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Jul 2026 16:15:03 +0800 Subject: [PATCH 2/2] fix: adopt ooo Router.Use() gate, drop removed Server.Audit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ooo removed Server.Audit in favor of gating requests through gorilla/mux middleware registered with Router.Use(). CI pulls ooo at HEAD via `go get`, so the stale go.mod pin masked the breakage locally while `go vet` failed in CI (server.Audit undefined). - add TokenAuth.Middleware(): a Router.Use()-compatible gate that requires a valid token for data routes and leaves the auth-managed routes (register/authorize/available/…) open, matching the old Audit-hook scope - switch the test to server.Router.Use(auth.Middleware()) - bump the ooo pin to the current release so local matches CI - refresh the README usage example and route table (Audit hook and ko/Routes signatures were stale) Co-Authored-By: Claude Opus 4.8 --- README.md | 48 ++++++++++++++++++++++++++---------------------- auth.go | 41 +++++++++++++++++++++++++++++++++++++++++ auth_test.go | 2 +- go.mod | 12 +++--------- go.sum | 21 ++++++++------------- 5 files changed, 79 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 9e0a48f..1496643 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ JWT authentication library for the [ooo](https://github.com/benitogf/ooo) ecosys - **JWT token authentication** with configurable expiry - **User management** with registration and login -- **Audit middleware** for access control +- **Gate middleware** for access control via `Router.Use()` - **Compatible with ooo** server and filters ## Installation @@ -24,23 +24,23 @@ package main import ( "log" - "net/http" "time" - "github.com/gorilla/mux" "github.com/benitogf/auth" "github.com/benitogf/ko" "github.com/benitogf/ooo" + "github.com/benitogf/ooo/storage" + "github.com/gorilla/mux" ) func main() { - // Auth storage (users) - authStore := &ko.Storage{Path: "/data/auth"} - err := authStore.Start([]string{}, nil) - if err != nil { - log.Fatal(err) - } - go ooo.WatchStorageNoop(authStore) + // Auth storage (users), persisted with ko + authStore := storage.New(storage.LayeredConfig{ + Memory: storage.NewMemoryLayer(), + Embedded: ko.NewEmbeddedStorage("./auth_data"), + }) + authStore.Start(storage.Options{}) + go storage.WatchStorageNoop(authStore) // Create auth with JWT token expiry key := "your-secret-key" @@ -51,19 +51,18 @@ func main() { // Create server with static mode app := ooo.Server{Static: true} + app.Router = mux.NewRouter() - // Audit middleware for access control - app.Audit = func(r *http.Request) bool { - if r.URL.Path == "/open" { - return true - } - return tokenAuth.Verify(r) // Require valid token - } + // Gate access with the auth middleware. ooo's Server.Audit hook was + // removed in favor of Router.Use(): the middleware fans out to every + // matched route, requiring a valid token for the data routes while + // leaving the auth-managed routes (register, authorize, ...) open. + // Pass extra open paths to exempt them. + app.Router.Use(tokenAuth.Middleware("/open")) - app.Router = mux.NewRouter() app.OpenFilter("open") // Available without token app.OpenFilter("closed") // Requires valid token - tokenAuth.Router(&app) // Add auth routes + tokenAuth.Routes(&app) // Add auth routes app.Start("localhost:8800") app.WaitClose() @@ -74,9 +73,14 @@ func main() { | Method | Path | Description | |--------|------|-------------| -| POST | `/register` | Register new user | -| POST | `/authorize` | Login and get token | -| GET | `/verify` | Verify token validity | +| POST | `/register` | Register new user (open) | +| POST / PUT | `/authorize` | Login and get token / refresh an expired token | +| GET | `/available?account=` | Check if an account name is taken (open) | +| GET | `/profile` | Get the profile for the request token | +| POST | `/create` | Create a user (root/admin only) | +| GET | `/users` | List users (root/admin only) | +| GET / POST / DELETE | `/user/{account}` | Read, update or delete a user | +| PUT | `/password/{account}` | Update an account password | ## Related Projects diff --git a/auth.go b/auth.go index e39dedb..e143954 100644 --- a/auth.go +++ b/auth.go @@ -139,6 +139,47 @@ func (t *TokenAuth) Verify(req *http.Request) bool { return err == nil } +// isAuthRoute reports whether path is one of the endpoints registered by +// Routes. Those handlers either are open (register, authorize, available) or +// self-guard by inspecting the token themselves (users, user, profile, +// password, create), so Middleware never gates them — mirroring the previous +// behavior where ooo's Server.Audit hook did not cover these custom handlers. +func isAuthRoute(path string) bool { + switch path { + case "/authorize", "/profile", "/users", "/register", "/create", "/available": + return true + } + return strings.HasPrefix(path, "/user/") || strings.HasPrefix(path, "/password/") +} + +// Middleware returns a gorilla/mux middleware that gates requests with a valid +// token. It is the replacement for ooo's removed Server.Audit hook: register it +// before starting the server with +// +// server.Router.Use(tokenAuth.Middleware()) +// +// gorilla/mux fans the middleware out to every matched route (REST, WebSocket +// upgrades, custom endpoints and the explorer UI), so any data route without a +// valid token is answered by UnauthorizedHandler. The auth-managed routes +// registered by Routes are never gated; pass extra open paths (exact match) to +// exempt additional routes. +func (t *TokenAuth) Middleware(open ...string) mux.MiddlewareFunc { + openSet := make(map[string]struct{}, len(open)) + for _, path := range open { + openSet[path] = struct{}{} + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, isOpen := openSet[r.URL.Path] + if isOpen || isAuthRoute(r.URL.Path) || t.Verify(r) { + next.ServeHTTP(w, r) + return + } + t.UnauthorizedHandler(w, r) + }) + } +} + // Authenticate : func (t *TokenAuth) Authenticate(r *http.Request) (Token, error) { strToken := t.getter.GetTokenFromRequest(r) diff --git a/auth_test.go b/auth_test.go index e932e04..ca6d2a1 100644 --- a/auth_test.go +++ b/auth_test.go @@ -31,8 +31,8 @@ func TestRegisterAndAuthorize(t *testing.T) { ) server := &ooo.Server{} server.Silence = true - server.Audit = auth.Verify server.Router = mux.NewRouter() + server.Router.Use(auth.Middleware()) auth.Routes(server) server.Start("localhost:9060") defer server.Close(os.Interrupt) diff --git a/go.mod b/go.mod index 7071d26..1e7b567 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/benitogf/auth go 1.25 require ( - github.com/benitogf/ooo v0.0.0-20260202060447-566ed1c50fb9 + github.com/benitogf/ooo v0.0.0-20260728151819-8c38013e1823 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/gorilla/mux v1.8.1 github.com/stretchr/testify v1.11.1 @@ -13,20 +13,14 @@ require ( require ( github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84 // indirect github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec // indirect - github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559 // indirect - github.com/benitogf/jsonpatch v0.0.0-20260109052650-eec54232a9a2 // indirect + github.com/benitogf/go-json v0.0.0-20260410172501-727f5690408b // indirect + github.com/benitogf/jsonpatch v0.0.0-20260413094158-a4a6cc1a3382 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/goccy/go-json v0.10.5 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/kr/pretty v0.2.1 // indirect - github.com/pkg/expect v0.0.0-20191209053905-1fe4c9394a8a // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/cors v1.11.1 // indirect - github.com/tidwall/gjson v1.18.0 // indirect - github.com/tidwall/match v1.2.0 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1e7a0d4..b875dae 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,14 @@ github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84 h1:cutFptzj+ospnc1PET github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84/go.mod h1:Va9ap1qxjAWkIVaW1E9rH0aNgE8SDI5A4n8Ds8P0fAA= github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec h1:I2p/9YsiAMB+J2DrC+e7OvEWZCmpwolMQ23ejoEMFEE= github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec/go.mod h1:hl/Xd8DKuMgC/ClZ2dPZz6mvXaxdkjrt0mr0+jF4UkQ= -github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559 h1:0ERndS9JkEvEq/yXjiZq41lMb3QfmQWUIXycjkRbWOc= -github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559/go.mod h1:gg2qmcNCGq/FXh5kQpsaEIf6GebYe2JMKrDSqZkuImc= -github.com/benitogf/jsonpatch v0.0.0-20260109052650-eec54232a9a2 h1:OC5ZiOG0sQ5Qmp5XOA3iIBBjhDjze010jHicNTD27kU= -github.com/benitogf/jsonpatch v0.0.0-20260109052650-eec54232a9a2/go.mod h1:O1Z+hTPUrXzHUjmKlpmNYK9wcncZ5rS3K8PqItDO0x0= -github.com/benitogf/ooo v0.0.0-20260109055348-efad8e781ed7 h1:qNmJ0qTWXW8k8XYLMfXy4cEYEA2VwMTL6P+GUFbdhHo= -github.com/benitogf/ooo v0.0.0-20260109055348-efad8e781ed7/go.mod h1:fqf1bDkBBOOiCExWW1qdj59ldb/yqfJc3gTfskl02cU= -github.com/benitogf/ooo v0.0.0-20260202060447-566ed1c50fb9 h1:P4TAfI7Hr8NzOuHqnwkO21lef3yaMG0TEo0M+F0ue4Y= -github.com/benitogf/ooo v0.0.0-20260202060447-566ed1c50fb9/go.mod h1:fqf1bDkBBOOiCExWW1qdj59ldb/yqfJc3gTfskl02cU= +github.com/benitogf/go-json v0.0.0-20260410172501-727f5690408b h1:EXDsF3gMYyR8ipJsGuyHB4d3+XovPT8d4UM6cfBII14= +github.com/benitogf/go-json v0.0.0-20260410172501-727f5690408b/go.mod h1:bv78ZxbWzHLAwEcSgQNjDUVgl3F4nOuYln98xHfUyjw= +github.com/benitogf/jsondiff v0.0.0-20260413094925-a4be838c278b h1:u7JAhkxSL5nMmH0HbpIg9w6qs7CYpdXiOiYemC3Zodo= +github.com/benitogf/jsondiff v0.0.0-20260413094925-a4be838c278b/go.mod h1:u6UwX3TEBaFuJVD3NuCdMp3RyPsb71DxRdMptVDwTaA= +github.com/benitogf/jsonpatch v0.0.0-20260413094158-a4a6cc1a3382 h1:invi4tSUoH4HMxFbM65sA3osc0wk9j3Za2JmtIqFRJc= +github.com/benitogf/jsonpatch v0.0.0-20260413094158-a4a6cc1a3382/go.mod h1:ZOQm93UFJwYDZLHQVGt2/JmNOltb6LRIJUuys0uMWKY= +github.com/benitogf/ooo v0.0.0-20260728151819-8c38013e1823 h1:n1UJP0mPGK0XwTJAPwqIQ7RteRKEuM09paZ5E7bdkLw= +github.com/benitogf/ooo v0.0.0-20260728151819-8c38013e1823/go.mod h1:WvPwWgfK2mo3UKfR+BM/9hwHe+ZjVwZZ3rxOPcBcXnw= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -18,8 +18,6 @@ github.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590 h1:OhyiFx+yBN3 github.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590/go.mod h1:rE/jidqqHHG9sjSxC24Gd5YCfZ1AT91C2wjJ28TAOfA= github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848 h1:2MhMMVBTnaHrst6HyWFDhwQCaJ05PZuOv1bE2gN8WFY= github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848/go.mod h1:+F5GJ7qGpQ03DBtcOEyQpM30ix4BLswdaojecFtsdy8= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -42,13 +40,10 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.2.0 h1:0pt8FlkOwjN2fPt4bIl4BoNxb98gGHN2ObFEDkrfZnM= github.com/tidwall/match v1.2.0/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=