From c72d27703287eb43a9e4882244eded672cd9ef82 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 16 Dec 2025 23:23:26 +0800 Subject: [PATCH 1/4] ooo refactor --- README.md | 16 +-- auth.go | 323 +++++++++++++++++++++++++++------------------------ auth_test.go | 44 +++---- go.mod | 28 +++-- go.sum | 83 ++++++------- jwt.go | 2 +- 6 files changed, 251 insertions(+), 245 deletions(-) diff --git a/README.md b/README.md index 775c570..d8f332c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Test](https://github.com/benitogf/auth/actions/workflows/tests.yml/badge.svg)](https://github.com/benitogf/auth/actions/workflows/tests.yml) -library to add jwt authentication to a katamari server +library to add jwt authentication to ooo server # creating rules and audits @@ -18,27 +18,27 @@ package main import ( "net/http" "github.com/gorilla/mux" - "github.com/benitogf/katamari" + "github.com/benitogf/ooo" "github.com/benitogf/auth" - "github.com/benitogf/level" + "github.com/benitogf/ko" ) func main() { // auth storage (users) - authStore := &level.Storage{Path: "/data/auth"} + authStore := &ko.Storage{Path: "/data/auth"} err := authStore.Start([]string{}, nil) if err != nil { log.Fatal(err) } // noop to capture the storage channel feed - go katamari.WatchStorageNoop(authStore) + go ooo.WatchStorageNoop(authStore) // set the JWT tokens expiry auth := auth.New( auth.NewJwtStore(*key, time.Minute*10), authStore, ) - app := katamari.Server{} + app := ooo.Server{} // set the server static mode (only defined filters and routes available) app.Static = true // perform audits on the request path/headers/referer @@ -52,8 +52,8 @@ func main() { return false } app.Router = mux.NewRouter() - katamari.OpenFilter(app, "open") // available withour token - katamari.OpenFilter(app, "closed") // valid token required + ooo.OpenFilter(app, "open") // available withour token + ooo.OpenFilter(app, "closed") // valid token required auth.Router(app) app.Start("localhost:8800") app.WaitClose() diff --git a/auth.go b/auth.go index ac3902a..cd3a28f 100644 --- a/auth.go +++ b/auth.go @@ -2,17 +2,17 @@ package auth import ( "bytes" + "encoding/json" "errors" "fmt" "net/http" "regexp" + "strconv" "strings" + "time" - "github.com/goccy/go-json" - - "github.com/benitogf/katamari" - "github.com/benitogf/katamari/objects" - "github.com/benitogf/pivot" + "github.com/benitogf/ooo" + "github.com/benitogf/ooo/meta" "github.com/gorilla/mux" "golang.org/x/crypto/bcrypt" ) @@ -20,8 +20,6 @@ import ( // User : type User struct { Name string `json:"name"` - Email string `json:"email"` - Phone string `json:"phone"` Account string `json:"account"` Password string `json:"password,omitempty"` Role string `json:"role"` @@ -38,10 +36,9 @@ type Credentials struct { // TokenAuth : type TokenAuth struct { tokenStore *JwtStore - store katamari.Database + store ooo.Database getter TokenGetter UnauthorizedHandler http.HandlerFunc - client *http.Client } // TokenGetter : @@ -71,12 +68,22 @@ type BearerGetter struct { Header string } +// Activity keeps the time of the last entry +type Activity struct { + LastEntry int64 `json:"lastEntry"` +} + var ( - userRegexp = regexp.MustCompile("^[a-zA-Z0-9_]{2,15}$") - emailRegexp = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") - roles = map[string]string{"root": "root"} + userRegexp = regexp.MustCompile("^[a-zA-Z0-9_]{2,15}$") + roles = map[string]string{"root": "root"} ) +// Time returns a string timestamp +func Time() string { + now := time.Now().UTC().UnixNano() + return strconv.FormatInt(now, 10) +} + // DefaultUnauthorizedHandler : func DefaultUnauthorizedHandler(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusUnauthorized) @@ -85,7 +92,6 @@ func DefaultUnauthorizedHandler(w http.ResponseWriter, req *http.Request) { // GetTokenFromRequest : func (b *BearerGetter) GetTokenFromRequest(req *http.Request) string { - // log.Println("header:", req.Header) authStr := req.Header.Get(b.Header) if !strings.HasPrefix(authStr, "Bearer ") { return "" @@ -114,7 +120,7 @@ func NewHeaderBearerTokenGetter(header string) *BearerGetter { // unauthorized handler is used. // // store is the TokenStore that stores and verify the tokens -func New(tokenStore *JwtStore, store katamari.Database) *TokenAuth { +func New(tokenStore *JwtStore, store ooo.Database) *TokenAuth { t := &TokenAuth{ tokenStore: tokenStore, store: store, @@ -127,7 +133,6 @@ func New(tokenStore *JwtStore, store katamari.Database) *TokenAuth { // Verify : wrap a HandlerFunc to be authenticated func (t *TokenAuth) Verify(req *http.Request) bool { _, err := t.Authenticate(req) - return err == nil } @@ -144,6 +149,22 @@ func (t *TokenAuth) Authenticate(r *http.Request) (Token, error) { return token, nil } +func (t *TokenAuth) AuditToken(strToken string) (string, string, error) { + if len(strToken) < 7 { + return "", "", errors.New("invalid token") + } + + token, err := t.tokenStore.CheckToken(strToken[7:]) + if err != nil { + return "", "", err + } + + role := token.Claims("role").(string) + account := token.Claims("iss").(string) + + return role, account, nil +} + // Audit : get websocket token, return token claims func (t *TokenAuth) Audit(r *http.Request) (string, string, error) { // get the header from a websocket connection @@ -167,7 +188,7 @@ func (t *TokenAuth) getUser(account string) (User, error) { if err != nil { return user, err } - var obj objects.Object + var obj meta.Object err = json.Unmarshal(raw, &obj) if err != nil { return user, err @@ -185,7 +206,7 @@ func (t *TokenAuth) getUsers() ([]User, error) { if err != nil { return nil, err } - var objects []objects.Object + var objects []meta.Object err = json.Unmarshal(raw, &objects) if err != nil { return nil, err @@ -227,11 +248,8 @@ func (t *TokenAuth) checkCredentials(credentials Credentials) (User, error) { } // Profile returns to the client the correspondent user profile for the token provided -func (t *TokenAuth) Profile(pivotIP string) func(w http.ResponseWriter, r *http.Request) { +func (t *TokenAuth) Profile() func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - if pivotIP != "" { - pivot.Synchronize(t.client, t.store, pivotIP, []string{"users/*"}) - } token, err := t.Authenticate(r) if err != nil { w.WriteHeader(http.StatusUnauthorized) @@ -260,12 +278,9 @@ func (t *TokenAuth) Profile(pivotIP string) func(w http.ResponseWriter, r *http. } // Authorize will claim a token on POST and refresh the claim on PUT -func (t *TokenAuth) Authorize(pivotIP string) func(w http.ResponseWriter, r *http.Request) { +func (t *TokenAuth) Authorize() func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - if pivotIP != "" { - pivot.Synchronize(t.client, t.store, pivotIP, []string{"users/*"}) - } credentials, err := getCredentials(r) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -295,7 +310,7 @@ func (t *TokenAuth) Authorize(pivotIP string) func(w http.ResponseWriter, r *htt return } - if err.Error() != "token expired" { + if err.Error() != "Token expired" { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, err) return @@ -328,7 +343,7 @@ func (t *TokenAuth) Authorize(pivotIP string) func(w http.ResponseWriter, r *htt } } -// Register will create a new user +// Register will create a new user with the default user role (open route) func (t *TokenAuth) Register(w http.ResponseWriter, r *http.Request) { var user User decoder := json.NewDecoder(r.Body) @@ -341,7 +356,7 @@ func (t *TokenAuth) Register(w http.ResponseWriter, r *http.Request) { return } - if user.Account == "" || user.Name == "" || user.Password == "" || user.Email == "" || user.Phone == "" { + if user.Account == "" || user.Name == "" || user.Password == "" { w.WriteHeader(http.StatusBadRequest) fmt.Fprintf(w, "%s", errors.New("new user data incomplete")) return @@ -359,18 +374,6 @@ func (t *TokenAuth) Register(w http.ResponseWriter, r *http.Request) { return } - if !userRegexp.MatchString(user.Phone) { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "%s", errors.New("phone cannot contain special characters othen than '-' and character count must be between 6 and 15")) - return - } - - if !emailRegexp.MatchString(user.Email) { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "%s", errors.New("invalid email address")) - return - } - _, err = t.getUser(user.Account) if err == nil { @@ -394,7 +397,7 @@ func (t *TokenAuth) Register(w http.ResponseWriter, r *http.Request) { } dataBytes := new(bytes.Buffer) json.NewEncoder(dataBytes).Encode(user) - _, err = t.store.Set("users/"+user.Account, dataBytes.String()) + _, err = t.store.Set("users/"+user.Account, json.RawMessage(dataBytes.String())) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -415,86 +418,6 @@ func (t *TokenAuth) Register(w http.ResponseWriter, r *http.Request) { enc.Encode(&credentials) } -// NewPassword is for updating account password -func (t *TokenAuth) NewPassword(w http.ResponseWriter, r *http.Request) { - token, err := t.Authenticate(r) - authorized := (err == nil) - role := "user" - if authorized { - role = token.Claims("role").(string) - } - - // root authorization - if role != "root" { - w.WriteHeader(http.StatusForbidden) - fmt.Fprintf(w, "Method not suported for your role") - return - } - account := mux.Vars(r)["account"] - - user, err := t.getUser(account) - if err != nil { - w.WriteHeader(http.StatusNotFound) - fmt.Fprint(w, err.Error()) - return - } - switch r.Method { - case "PUT": - dec := json.NewDecoder(r.Body) - var userData User - err := dec.Decode(&userData) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, errors.New("invalid user data")) - return - } - hash, err := bcrypt.GenerateFromPassword([]byte(userData.Password), bcrypt.MinCost) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, err.Error()) - return - } - user.Password = string(hash) - - dataBytes := new(bytes.Buffer) - json.NewEncoder(dataBytes).Encode(user) - _, err = t.store.Set("users/"+user.Account, dataBytes.String()) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprint(w, err) - return - } - user.Password = "" - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "application/json") - enc := json.NewEncoder(w) - enc.Encode(&user) - default: - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "Method not suported") - return - } -} - -// Available will check if an account is taken -func (t *TokenAuth) Available(pivotIP string) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if pivotIP != "" { - pivot.Synchronize(t.client, t.store, pivotIP, []string{"users/*"}) - } - account := r.FormValue("account") - _, err := t.getUser(account) - - if err == nil { - w.WriteHeader(http.StatusConflict) - fmt.Fprintf(w, "account taken") - return - } - w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, "account available") - } -} - // Create will create a new user (root only access) func (t *TokenAuth) Create(w http.ResponseWriter, r *http.Request) { token, err := t.Authenticate(r) @@ -511,7 +434,7 @@ func (t *TokenAuth) Create(w http.ResponseWriter, r *http.Request) { } // root authorization - if role != "root" { + if role != "root" && role != "admin" { w.WriteHeader(http.StatusForbidden) fmt.Fprintf(w, "Method not suported for your role") return @@ -552,10 +475,53 @@ func (t *TokenAuth) Create(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", errors.New("account name taken")) return } + + hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.MinCost) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + return + } + + user.Password = string(hash) + dataBytes := new(bytes.Buffer) + json.NewEncoder(dataBytes).Encode(user) + _, err = t.store.Set("users/"+user.Account, json.RawMessage(dataBytes.String())) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + return + } + + credentials := Credentials{ + Account: user.Account, + Role: user.Role, + } + w.WriteHeader(http.StatusOK) + enc := json.NewEncoder(w) + enc.Encode(&credentials) +} + +// Available will check if an account is taken +func (t *TokenAuth) Available() func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + account := r.FormValue("account") + _, err := t.getUser(account) + + w.Header().Set("Content-Type", "application/json") + if err == nil { + w.WriteHeader(http.StatusConflict) + fmt.Fprintf(w, "account taken") + return + } + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "account available") + } } // Users will send the user list to a root user -func (t *TokenAuth) Users(pivotIP string) func(w http.ResponseWriter, r *http.Request) { +func (t *TokenAuth) Users() func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { token, err := t.Authenticate(r) authorized := (err == nil) @@ -565,16 +531,12 @@ func (t *TokenAuth) Users(pivotIP string) func(w http.ResponseWriter, r *http.Re } // root authorization - if !authorized || role != "root" { + if !authorized || (role != "root" && role != "admin") { w.WriteHeader(http.StatusForbidden) fmt.Fprintf(w, "Method not suported for your role") return } - if pivotIP != "" { - pivot.Synchronize(t.client, t.store, pivotIP, []string{"users/*"}) - } - users, err := t.getUsers() if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -593,17 +555,19 @@ func (t *TokenAuth) User(w http.ResponseWriter, r *http.Request) { token, err := t.Authenticate(r) authorized := (err == nil) role := "user" + issuer := "" if authorized { role = token.Claims("role").(string) + issuer = token.Claims("iss").(string) } - // root authorization - if !authorized || role != "root" { + account := mux.Vars(r)["account"] + // root, admin or your own user + if !authorized || (role != "root" && role != "admin" && issuer != account) { w.WriteHeader(http.StatusForbidden) fmt.Fprintf(w, "Method not suported for your role") return } - account := mux.Vars(r)["account"] user, err := t.getUser(account) if err != nil { @@ -624,8 +588,9 @@ func (t *TokenAuth) User(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%s", err) return } + // t.store.Set("delete/users", Time()) w.WriteHeader(http.StatusNoContent) - fmt.Fprintf(w, "deleted "+account) + fmt.Fprintf(w, "%s", "deleted "+account) case "POST": dec := json.NewDecoder(r.Body) var userData User @@ -635,21 +600,15 @@ func (t *TokenAuth) User(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, errors.New("invalid user data")) return } - if userData.Email != "" { - user.Email = userData.Email - } if userData.Name != "" { user.Name = userData.Name } - if userData.Phone != "" { - user.Phone = userData.Phone - } if userData.Role != "" { user.Role = userData.Role } dataBytes := new(bytes.Buffer) json.NewEncoder(dataBytes).Encode(user) - _, err = t.store.Set("users/"+user.Account, dataBytes.String()) + _, err = t.store.Set("users/"+user.Account, json.RawMessage(dataBytes.String())) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, err) @@ -667,17 +626,77 @@ func (t *TokenAuth) User(w http.ResponseWriter, r *http.Request) { } } -// Router handle for auth enpoints -func (t *TokenAuth) Router(server *katamari.Server) { - server.Router.HandleFunc("/authorize", t.Authorize(server.Pivot)) - server.Router.HandleFunc("/profile", t.Profile(server.Pivot)) - server.Router.HandleFunc("/users", t.Users(server.Pivot)).Methods("GET") - server.Router.HandleFunc("/user/{account:[a-zA-Z\\d]+}", t.User).Methods("GET", "POST", "DELETE") - server.Router.HandleFunc("/password/{account:[a-zA-Z\\d]+}", t.NewPassword).Methods("PUT") - server.Router.HandleFunc("/register", t.Register).Methods("POST") - server.Router.HandleFunc("/create", t.Create).Methods("POST") - server.Router.HandleFunc("/available", t.Available(server.Pivot)).Queries("account", "{[a-zA-Z\\d]}").Methods("GET") - - t.client = server.Client - pivot.Router(server.Router, t.store, server.Client, server.Pivot, []string{"users/*"}) +// NewPassword is for updating account password +func (t *TokenAuth) NewPassword(w http.ResponseWriter, r *http.Request) { + token, err := t.Authenticate(r) + authorized := (err == nil) + role := "user" + issuer := "" + if authorized { + role = token.Claims("role").(string) + issuer = token.Claims("iss").(string) + } + + account := mux.Vars(r)["account"] + // root, admin or your own user + if !authorized || (role != "root" && role != "admin" && issuer != account) { + w.WriteHeader(http.StatusForbidden) + fmt.Fprintf(w, "Method not suported for your role") + return + } + + user, err := t.getUser(account) + if err != nil { + w.WriteHeader(http.StatusNotFound) + fmt.Fprint(w, err.Error()) + return + } + switch r.Method { + case "PUT": + dec := json.NewDecoder(r.Body) + var userData User + err := dec.Decode(&userData) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprint(w, errors.New("invalid user data")) + return + } + hash, err := bcrypt.GenerateFromPassword([]byte(userData.Password), bcrypt.MinCost) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err.Error()) + return + } + user.Password = string(hash) + + dataBytes := new(bytes.Buffer) + json.NewEncoder(dataBytes).Encode(user) + _, err = t.store.Set("users/"+user.Account, json.RawMessage(dataBytes.String())) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprint(w, err) + return + } + user.Password = "" + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(w) + enc.Encode(&user) + default: + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Method not suported") + return + } } + +// Routes handle for auth enpoints +func (t *TokenAuth) Routes(server *ooo.Server) { + server.Router.HandleFunc("/authorize", t.Authorize()).Methods(http.MethodPost, http.MethodPut) + server.Router.HandleFunc("/profile", t.Profile()).Methods(http.MethodGet) + server.Router.HandleFunc("/users", t.Users()).Methods(http.MethodGet) + server.Router.HandleFunc("/user/{account:[a-zA-Z\\d]+}", t.User).Methods(http.MethodPost, http.MethodGet, http.MethodDelete) + server.Router.HandleFunc("/password/{account:[a-zA-Z\\d]+}", t.NewPassword).Methods(http.MethodPut) + server.Router.HandleFunc("/register", t.Register).Methods(http.MethodPost) + server.Router.HandleFunc("/create", t.Create).Methods(http.MethodPost) + server.Router.HandleFunc("/available", t.Available()).Queries("account", "{[a-zA-Z\\d]}").Methods(http.MethodGet) +} \ No newline at end of file diff --git a/auth_test.go b/auth_test.go index 0f85834..89cc947 100644 --- a/auth_test.go +++ b/auth_test.go @@ -2,7 +2,8 @@ package auth import ( "bytes" - "io" + "encoding/json" + "io/ioutil" "log" "net/http" "net/http/httptest" @@ -11,30 +12,28 @@ import ( "testing" "time" - "github.com/goccy/go-json" - - "github.com/benitogf/katamari" + "github.com/benitogf/ooo" "github.com/gorilla/mux" "github.com/stretchr/testify/require" ) func TestRegisterAndAuthorize(t *testing.T) { var c Credentials - authStore := &katamari.MemoryStorage{} - err := authStore.Start(katamari.StorageOpt{}) + authStore := &ooo.MemoryStorage{} + err := authStore.Start(ooo.StorageOpt{}) if err != nil { log.Fatal(err) } - go katamari.WatchStorageNoop(authStore) + go ooo.WatchStorageNoop(authStore) auth := New( NewJwtStore("a-secret-key-0-asdasdada-asdasdasd-asdasdsaweenvurh@!@#12", time.Second*1), authStore, ) - server := &katamari.Server{} + server := &ooo.Server{} server.Silence = true server.Audit = auth.Verify server.Router = mux.NewRouter() - auth.Router(server) + auth.Routes(server) server.Start("localhost:9060") defer server.Close(os.Interrupt) @@ -52,7 +51,7 @@ func TestRegisterAndAuthorize(t *testing.T) { "account":"root", "password": "000", "email": "root@root.test", - "phone": "123123123" + "phone": "123123123" }`) req, err = http.NewRequest("POST", "/register", bytes.NewBuffer(payload)) require.NoError(t, err) @@ -167,7 +166,7 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err := io.ReadAll(response.Body) + body, err := ioutil.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, "{\"keys\":[]}", strings.TrimRight(string(body), "\n")) @@ -180,9 +179,9 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) - require.Equal(t, `{"name":"root","email":"root@root.test","phone":"123123123","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) + require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) // users req, err = http.NewRequest("GET", "/users", nil) @@ -199,9 +198,9 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) - require.Equal(t, `[{"name":"root","email":"root@root.test","phone":"123123123","account":"root","role":"root"}]`, strings.TrimRight(string(body), "\n")) + require.Equal(t, `[{"name":"root","account":"root","role":"root"}]`, strings.TrimRight(string(body), "\n")) // get user req, err = http.NewRequest("GET", "/user/root", nil) @@ -218,9 +217,9 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) - require.Equal(t, `{"name":"root","email":"root@root.test","phone":"123123123","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) + require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) // update user payload = []byte(`{"phone":"321321321"}`) @@ -231,9 +230,9 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) - require.Equal(t, `{"name":"root","email":"root@root.test","phone":"321321321","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) + require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) // get updated user req, err = http.NewRequest("GET", "/user/root", nil) @@ -243,9 +242,9 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) - require.Equal(t, `{"name":"root","email":"root@root.test","phone":"321321321","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) + require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) // delete user req, err = http.NewRequest("DELETE", "/user/root", nil) @@ -255,7 +254,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusNoContent, response.StatusCode) - body, err = io.ReadAll(response.Body) + body, err = ioutil.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `deleted root`, strings.TrimRight(string(body), "\n")) @@ -267,3 +266,4 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) } + diff --git a/go.mod b/go.mod index 6a94848..4bfbbc7 100644 --- a/go.mod +++ b/go.mod @@ -1,28 +1,32 @@ module github.com/benitogf/auth -go 1.19 +go 1.25 require ( - github.com/benitogf/katamari v0.0.0-20220924134414-1f73e9bf0582 - github.com/benitogf/pivot v0.0.0-20210825050412-ec2fd40ab94f - github.com/goccy/go-json v0.9.11 + github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/gorilla/mux v1.8.0 - github.com/stretchr/testify v1.8.0 + github.com/gorilla/mux v1.8.1 + github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 ) require ( github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84 // indirect github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec // indirect - github.com/benitogf/jsonpatch v0.0.0-20210608090026-cebd07eba361 // indirect - github.com/cristalhq/base64 v0.1.2 // indirect + github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559 // indirect + github.com/benitogf/jsonpatch v0.0.0-20250219100646-458e71efe70d // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/gorilla/handlers v1.5.1 // indirect - github.com/gorilla/websocket v1.5.0 // 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.8.2 // 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 6c85d31..46336c5 100644 --- a/go.sum +++ b/go.sum @@ -1,44 +1,31 @@ github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84 h1:cutFptzj+ospnc1PETUqcSVTH3VQ44Bi0rpt3nE9gvo= github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84/go.mod h1:Va9ap1qxjAWkIVaW1E9rH0aNgE8SDI5A4n8Ds8P0fAA= -github.com/benitogf/auth v0.0.0-20210608091125-ea2b5e03d348/go.mod h1:F1Ms4xpRLbOB7PyDPFyojdxf5mi1QTZs/iCcaiEBVRI= 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-20200402083139-355fc2c95658 h1:wCbyHDE170Pt3F2YcJn9Q82ed9f/PB2YE4lYRDvJLf4= -github.com/benitogf/jsondiff v0.0.0-20200402083139-355fc2c95658/go.mod h1:w/Y8qUTRw+ewq+K23wa0CrhT19d1nsboJd8mFJoE9+0= -github.com/benitogf/jsonpatch v0.0.0-20200902061850-bc36be00f341/go.mod h1:/dqfb1omxp7PkYMtIxjlLRZleHKq0VNPh/yhrejRCqU= -github.com/benitogf/jsonpatch v0.0.0-20210608090026-cebd07eba361 h1:c5ECN02frRXad0hmD6Y6WNIHL+kc6oV8ejErH5r/65M= -github.com/benitogf/jsonpatch v0.0.0-20210608090026-cebd07eba361/go.mod h1:/dqfb1omxp7PkYMtIxjlLRZleHKq0VNPh/yhrejRCqU= -github.com/benitogf/jwt v0.0.0-20190207101038-ba98fe84e46d/go.mod h1:OfnivP1y+CLGDu5ipImwnF7O46UnZQqzDGjAZ5pezYk= -github.com/benitogf/katamari v0.0.0-20210319080900-6fc5d277bd9f/go.mod h1:GbqR1boBoMLVEAPbtGn1P+OGUhPNQRa6Cgt1T94z9RI= -github.com/benitogf/katamari v0.0.0-20210609042642-ac908da81479/go.mod h1:GbqR1boBoMLVEAPbtGn1P+OGUhPNQRa6Cgt1T94z9RI= -github.com/benitogf/katamari v0.0.0-20220924134414-1f73e9bf0582 h1:10WiimQZ0R+orpFernegnRa5BUkf2w6+qCaisTlmmhg= -github.com/benitogf/katamari v0.0.0-20220924134414-1f73e9bf0582/go.mod h1:eKWAY47aRo+KtEx5SvgTy+lgACEBPmcw1nx1LgN5ITo= -github.com/benitogf/pivot v0.0.0-20210319080220-3e80433245e9/go.mod h1:zuVMyAwWE24XuRaOTx8/NeE3teCkRaf4dRJKbTVd+YA= -github.com/benitogf/pivot v0.0.0-20210825050412-ec2fd40ab94f h1:WBctKgnCiGBsLsxPV6HjjaUPa3CLWZQfwEg/5SJpmPA= -github.com/benitogf/pivot v0.0.0-20210825050412-ec2fd40ab94f/go.mod h1:LKLdnBjWJALeBRdN91Vt9mLvX+aUsIjTH3tN+tL6pu8= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cristalhq/base64 v0.1.2 h1:edsefYyYDiac7Ytdh2xdaiiSSJzcI2f0yIkdGEf1qY0= -github.com/cristalhq/base64 v0.1.2/go.mod h1:sy4+2Hale2KbtSqkzpdMeYTP/IrB+HCvxVHWsh2VSYk= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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-20250219100646-458e71efe70d h1:ZpQQZiG0Lca3Vb9EPVD2aOwRQiDBRXrpTCg1tF397Hk= +github.com/benitogf/jsonpatch v0.0.0-20250219100646-458e71efe70d/go.mod h1:O1Z+hTPUrXzHUjmKlpmNYK9wcncZ5rS3K8PqItDO0x0= +github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c h1:V5OrDdzZ3PCTO3jvPddqh4l9ou/BGcAdY+ETs1zQIQ8= +github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c/go.mod h1:j6JBofwK7qqwDTALTBUk3f00qcOklU7FL7vh6c49JnU= 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.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= -github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590 h1:OhyiFx+yBN30O3IHrIq+9LAEhy6o7fin21wUQxF8NiE= +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/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +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.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -49,29 +36,25 @@ github.com/pkg/expect v0.0.0-20191209053905-1fe4c9394a8a h1:vt+MDYAsRPsJSRWY9m6l github.com/pkg/expect v0.0.0-20191209053905-1fe4c9394a8a/go.mod h1:FWZrgn7FR+z4swTr6vGIJQu0ZPLHThpHDSTgzlIOQ9Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +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= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/jwt.go b/jwt.go index d7d5cbb..0220879 100644 --- a/jwt.go +++ b/jwt.go @@ -72,7 +72,7 @@ func (s *JwtStore) CheckToken(token string) (Token, error) { } jtoken := &JwtToken{s.tokenKey, *t} if jtoken.IsExpired() { - return jtoken, errors.New("token expired") + return jtoken, errors.New("Token expired") } return jtoken, nil } From c2c3beee8379c9889b70f69d6a5de19b464278c6 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 30 Dec 2025 02:42:16 +0800 Subject: [PATCH 2/4] refactor to match ooo --- README.md | 111 +++++++++++++++++++++++++++++++-------------------- auth.go | 45 ++++++--------------- auth_test.go | 32 ++++++--------- go.mod | 2 + go.sum | 2 - 5 files changed, 96 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index d8f332c..9e0a48f 100644 --- a/README.md +++ b/README.md @@ -2,60 +2,85 @@ [![Test](https://github.com/benitogf/auth/actions/workflows/tests.yml/badge.svg)](https://github.com/benitogf/auth/actions/workflows/tests.yml) -library to add jwt authentication to ooo server +JWT authentication library for the [ooo](https://github.com/benitogf/ooo) ecosystem. -# creating rules and audits +## Features - Define ad lib filters to send and receive criteria using key glob patterns, audit middleware +- **JWT token authentication** with configurable expiry +- **User management** with registration and login +- **Audit middleware** for access control +- **Compatible with ooo** server and filters -Using the default open setting is usefull while prototyping, but maybe not ideal to deploy as a public service. +## Installation -jwt auth enabled with static routing server example: +```bash +go get github.com/benitogf/auth +``` -```golang +## Usage + +```go package main import ( - "net/http" - "github.com/gorilla/mux" - "github.com/benitogf/ooo" - "github.com/benitogf/auth" - "github.com/benitogf/ko" + "log" + "net/http" + "time" + + "github.com/gorilla/mux" + "github.com/benitogf/auth" + "github.com/benitogf/ko" + "github.com/benitogf/ooo" ) func main() { - // auth storage (users) - authStore := &ko.Storage{Path: "/data/auth"} - err := authStore.Start([]string{}, nil) - if err != nil { - log.Fatal(err) - } - // noop to capture the storage channel feed - go ooo.WatchStorageNoop(authStore) - // set the JWT tokens expiry - auth := auth.New( - auth.NewJwtStore(*key, time.Minute*10), - authStore, - ) - - app := ooo.Server{} - // set the server static mode (only defined filters and routes available) - app.Static = true - // perform audits on the request path/headers/referer - // if the function returns false the request will return - // status 401 - app.Audit = func(r *http.Request, auth *auth.TokenAuth) bool { - if r.URL.Path == "/open" { - return true + // Auth storage (users) + authStore := &ko.Storage{Path: "/data/auth"} + err := authStore.Start([]string{}, nil) + if err != nil { + log.Fatal(err) + } + go ooo.WatchStorageNoop(authStore) + + // Create auth with JWT token expiry + key := "your-secret-key" + tokenAuth := auth.New( + auth.NewJwtStore(key, time.Minute*10), + authStore, + ) + + // Create server with static mode + app := ooo.Server{Static: true} + + // 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 } - return false - } - app.Router = mux.NewRouter() - ooo.OpenFilter(app, "open") // available withour token - ooo.OpenFilter(app, "closed") // valid token required - auth.Router(app) - app.Start("localhost:8800") - app.WaitClose() + app.Router = mux.NewRouter() + app.OpenFilter("open") // Available without token + app.OpenFilter("closed") // Requires valid token + tokenAuth.Router(&app) // Add auth routes + + app.Start("localhost:8800") + app.WaitClose() } -``` \ No newline at end of file +``` + +## Auth Routes + +| Method | Path | Description | +|--------|------|-------------| +| POST | `/register` | Register new user | +| POST | `/authorize` | Login and get token | +| GET | `/verify` | Verify token validity | + +## Related Projects + +- [ooo](https://github.com/benitogf/ooo) - Main server library +- [ko](https://github.com/benitogf/ko) - Persistent storage adapter +- [ooo-client](https://github.com/benitogf/ooo-client) - JavaScript client +- [mono](https://github.com/benitogf/mono) - Full-stack boilerplate \ No newline at end of file diff --git a/auth.go b/auth.go index cd3a28f..ac91712 100644 --- a/auth.go +++ b/auth.go @@ -12,7 +12,7 @@ import ( "time" "github.com/benitogf/ooo" - "github.com/benitogf/ooo/meta" + "github.com/benitogf/ooo/storage" "github.com/gorilla/mux" "golang.org/x/crypto/bcrypt" ) @@ -36,7 +36,7 @@ type Credentials struct { // TokenAuth : type TokenAuth struct { tokenStore *JwtStore - store ooo.Database + store storage.Database getter TokenGetter UnauthorizedHandler http.HandlerFunc } @@ -120,7 +120,7 @@ func NewHeaderBearerTokenGetter(header string) *BearerGetter { // unauthorized handler is used. // // store is the TokenStore that stores and verify the tokens -func New(tokenStore *JwtStore, store ooo.Database) *TokenAuth { +func New(tokenStore *JwtStore, store storage.Database) *TokenAuth { t := &TokenAuth{ tokenStore: tokenStore, store: store, @@ -183,43 +183,24 @@ func (t *TokenAuth) Audit(r *http.Request) (string, string, error) { // Authorize method func (t *TokenAuth) getUser(account string) (User, error) { - var user User - raw, err := t.store.Get("users/" + account) - if err != nil { - return user, err - } - var obj meta.Object - err = json.Unmarshal(raw, &obj) + obj, err := ooo.Get[User](&ooo.Server{Storage: t.store}, "users/"+account) if err != nil { - return user, err + return User{}, err } - err = json.Unmarshal([]byte(obj.Data), &user) - if err != nil { - return user, err - } - return user, nil + return obj.Data, nil } func (t *TokenAuth) getUsers() ([]User, error) { - var users []User - raw, err := t.store.Get("users/*") + users, err := ooo.GetList[User](&ooo.Server{Storage: t.store}, "users/*") if err != nil { return nil, err } - var objects []meta.Object - err = json.Unmarshal(raw, &objects) - if err != nil { - return nil, err - } - for _, object := range objects { - var user User - err = json.Unmarshal([]byte(object.Data), &user) - if err == nil { - user.Password = "" - users = append(users, user) - } + var result []User + for _, user := range users { + user.Data.Password = "" + result = append(result, user.Data) } - return users, nil + return result, nil } func getCredentials(r *http.Request) (Credentials, error) { @@ -699,4 +680,4 @@ func (t *TokenAuth) Routes(server *ooo.Server) { server.Router.HandleFunc("/register", t.Register).Methods(http.MethodPost) server.Router.HandleFunc("/create", t.Create).Methods(http.MethodPost) server.Router.HandleFunc("/available", t.Available()).Queries("account", "{[a-zA-Z\\d]}").Methods(http.MethodGet) -} \ No newline at end of file +} diff --git a/auth_test.go b/auth_test.go index 89cc947..09aaf69 100644 --- a/auth_test.go +++ b/auth_test.go @@ -3,8 +3,6 @@ package auth import ( "bytes" "encoding/json" - "io/ioutil" - "log" "net/http" "net/http/httptest" "os" @@ -12,19 +10,20 @@ import ( "testing" "time" + "io" + "github.com/benitogf/ooo" + "github.com/benitogf/ooo/storage" "github.com/gorilla/mux" "github.com/stretchr/testify/require" ) func TestRegisterAndAuthorize(t *testing.T) { var c Credentials - authStore := &ooo.MemoryStorage{} - err := authStore.Start(ooo.StorageOpt{}) - if err != nil { - log.Fatal(err) - } - go ooo.WatchStorageNoop(authStore) + authStore := storage.New(storage.LayeredConfig{ + Memory: storage.NewMemoryLayer(), + }) + go storage.WatchStorageNoop(authStore) auth := New( NewJwtStore("a-secret-key-0-asdasdada-asdasdasd-asdasdsaweenvurh@!@#12", time.Second*1), authStore, @@ -166,10 +165,6 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err := ioutil.ReadAll(response.Body) - require.NoError(t, err) - require.Equal(t, "{\"keys\":[]}", strings.TrimRight(string(body), "\n")) - // profile req, err = http.NewRequest("GET", "/profile", nil) require.NoError(t, err) @@ -179,7 +174,7 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err := io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) @@ -198,7 +193,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err = io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `[{"name":"root","account":"root","role":"root"}]`, strings.TrimRight(string(body), "\n")) @@ -217,7 +212,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err = io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) @@ -230,7 +225,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err = io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) @@ -242,7 +237,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err = io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `{"name":"root","account":"root","role":"root"}`, strings.TrimRight(string(body), "\n")) @@ -254,7 +249,7 @@ func TestRegisterAndAuthorize(t *testing.T) { server.Router.ServeHTTP(w, req) response = w.Result() require.Equal(t, http.StatusNoContent, response.StatusCode) - body, err = ioutil.ReadAll(response.Body) + body, err = io.ReadAll(response.Body) require.NoError(t, err) require.Equal(t, `deleted root`, strings.TrimRight(string(body), "\n")) @@ -266,4 +261,3 @@ func TestRegisterAndAuthorize(t *testing.T) { response = w.Result() require.Equal(t, http.StatusOK, response.StatusCode) } - diff --git a/go.mod b/go.mod index 4bfbbc7..18a0e27 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 ) +replace github.com/benitogf/ooo => ../ooo + require ( github.com/bclicn/color v0.0.0-20180711051946-108f2023dc84 // indirect github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec // indirect diff --git a/go.sum b/go.sum index 46336c5..af5cf3b 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,6 @@ github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559 h1:0ERndS9JkEvEq github.com/benitogf/jsondiff v0.0.0-20220926080659-c3db9b84b559/go.mod h1:gg2qmcNCGq/FXh5kQpsaEIf6GebYe2JMKrDSqZkuImc= github.com/benitogf/jsonpatch v0.0.0-20250219100646-458e71efe70d h1:ZpQQZiG0Lca3Vb9EPVD2aOwRQiDBRXrpTCg1tF397Hk= github.com/benitogf/jsonpatch v0.0.0-20250219100646-458e71efe70d/go.mod h1:O1Z+hTPUrXzHUjmKlpmNYK9wcncZ5rS3K8PqItDO0x0= -github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c h1:V5OrDdzZ3PCTO3jvPddqh4l9ou/BGcAdY+ETs1zQIQ8= -github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c/go.mod h1:j6JBofwK7qqwDTALTBUk3f00qcOklU7FL7vh6c49JnU= 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= From e3ba68e78413a30eb8d153f65120901e5f707106 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 9 Jan 2026 14:19:08 +0800 Subject: [PATCH 3/4] remove go.mod replace --- go.mod | 6 ++---- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 18a0e27..e038e0e 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,18 @@ module github.com/benitogf/auth go 1.25 require ( - github.com/benitogf/ooo v0.0.0-20251124052607-b19fb995a95c + github.com/benitogf/ooo v0.0.0-20260109055348-efad8e781ed7 github.com/golang-jwt/jwt v3.2.2+incompatible github.com/gorilla/mux v1.8.1 github.com/stretchr/testify v1.11.1 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 ) -replace github.com/benitogf/ooo => ../ooo - 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-20250219100646-458e71efe70d // indirect + github.com/benitogf/jsonpatch v0.0.0-20260109052650-eec54232a9a2 // 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 diff --git a/go.sum b/go.sum index af5cf3b..b87d10b 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,10 @@ github.com/benitogf/coat v0.0.0-20200402073050-ff807656cbec h1:I2p/9YsiAMB+J2DrC 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-20250219100646-458e71efe70d h1:ZpQQZiG0Lca3Vb9EPVD2aOwRQiDBRXrpTCg1tF397Hk= -github.com/benitogf/jsonpatch v0.0.0-20250219100646-458e71efe70d/go.mod h1:O1Z+hTPUrXzHUjmKlpmNYK9wcncZ5rS3K8PqItDO0x0= +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/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= From 4cbdeab316a1a80cffd889326256cad16d0f222e Mon Sep 17 00:00:00 2001 From: root Date: Fri, 9 Jan 2026 14:23:19 +0800 Subject: [PATCH 4/4] ci fix --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a779b43..9b17d18 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [1.19.x] + go-version: [1.25.x] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: @@ -18,7 +18,7 @@ jobs: run: | go get github.com/stretchr/testify go get github.com/benitogf/jwt - go get github.com/benitogf/katamari + go get github.com/benitogf/ooo go get github.com/benitogf/pivot go get golang.org/x/crypto/bcrypt - name: lint