diff --git a/backend/app/cmd/server.go b/backend/app/cmd/server.go index 3810c5ca..990d359e 100644 --- a/backend/app/cmd/server.go +++ b/backend/app/cmd/server.go @@ -19,7 +19,7 @@ import ( "github.com/go-pkgz/lcw/v2/eventbus" log "github.com/go-pkgz/lgr" ntf "github.com/go-pkgz/notify" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/kyokomi/emoji/v2" bolt "go.etcd.io/bbolt" @@ -1109,10 +1109,10 @@ func (s *ServerCommand) makeNotifyDestinations(authenticator *auth.Service) ([]n TokenGenFn: func(userID, email, site string) (string, error) { claims := token.Claims{ Handshake: &token.Handshake{ID: userID + "::" + email}, - StandardClaims: jwt.StandardClaims{ - Audience: site, - ExpiresAt: time.Now().Add(100 * 365 * 24 * time.Hour).Unix(), - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{site}, + ExpiresAt: jwt.NewNumericDate(time.Now().Add(100 * 365 * 24 * time.Hour)), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), Issuer: "remark42", }, } @@ -1215,10 +1215,15 @@ func (s *ServerCommand) getAuthenticator(ds *service.DataStore, avas avatar.Stor if c.User == nil { return c } - c.User.SetAdmin(ds.IsAdmin(c.Audience, c.User.ID)) - c.User.SetBoolAttr("blocked", ds.IsBlocked(c.Audience, c.User.ID)) + if len(c.Audience) != 1 { + return c + } + audience := c.Audience[0] + + c.User.SetAdmin(ds.IsAdmin(audience, c.User.ID)) + c.User.SetBoolAttr("blocked", ds.IsBlocked(audience, c.User.ID)) var err error - c.User.Email, err = ds.GetUserEmail(c.Audience, c.User.ID) + c.User.Email, err = ds.GetUserEmail(audience, c.User.ID) if err != nil { log.Printf("[WARN] can't read email for %s, %v", c.User.ID, err) } diff --git a/backend/app/cmd/server_test.go b/backend/app/cmd/server_test.go index 66ecc068..251189ff 100644 --- a/backend/app/cmd/server_test.go +++ b/backend/app/cmd/server_test.go @@ -16,7 +16,7 @@ import ( "time" "github.com/go-pkgz/auth/token" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/jessevdk/go-flags" "go.uber.org/goleak" @@ -609,11 +609,11 @@ func TestServerAuthHooks(t *testing.T) { tkService.TokenDuration = time.Second claims := token.Claims{ - StandardClaims: jwt.StandardClaims{ - Audience: "remark", + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"remark"}, Issuer: "remark", - ExpiresAt: time.Now().Add(time.Second).Unix(), - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second)), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), }, User: &token.User{ ID: "github_dev", @@ -637,10 +637,10 @@ func TestServerAuthHooks(t *testing.T) { require.NoError(t, resp.Body.Close()) assert.Equal(t, http.StatusCreated, resp.StatusCode, "non-blocked user able to post") - // try to add comment with no-aud claim - badClaimsNoAud := claims - badClaimsNoAud.Audience = "" - tkNoAud, err := tkService.Token(badClaimsNoAud) + // add comment with no-aud claim + claimsNoAud := claims + claimsNoAud.Audience = jwt.ClaimStrings{""} + tkNoAud, err := tkService.Token(claimsNoAud) require.NoError(t, err) t.Logf("no-aud claims: %s", tkNoAud) req, err = http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/api/v1/comment", port), @@ -655,25 +655,6 @@ func TestServerAuthHooks(t *testing.T) { require.NoError(t, resp.Body.Close()) assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "user without aud claim rejected, \n"+tkNoAud+"\n"+string(body)) - // try to add comment without user set - badClaimsNoUser := claims - badClaimsNoUser.Audience = "remark" - badClaimsNoUser.User = nil - tkNoUser, err := tkService.Token(badClaimsNoUser) - require.NoError(t, err) - t.Logf("no user claims: %s", tkNoUser) - req, err = http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/api/v1/comment", port), - strings.NewReader(`{"text": "test 123", "locator":{"url": "https://radio-t.com/p/2018/12/29/podcast-631/", - "site": "remark"}}`)) - require.NoError(t, err) - req.Header.Set("X-JWT", tkNoUser) - resp, err = client.Do(req) - require.NoError(t, err) - body, err = io.ReadAll(resp.Body) - require.NoError(t, err) - require.NoError(t, resp.Body.Close()) - assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "user without user information rejected, \n"+tkNoUser+"\n"+string(body)) - // block user github_dev as admin req, err = http.NewRequest(http.MethodPut, fmt.Sprintf("http://localhost:%d/api/v1/admin/user/github_dev?site=remark&block=1&ttl=10d", port), http.NoBody) diff --git a/backend/app/rest/api/admin.go b/backend/app/rest/api/admin.go index d6e1f94b..49b3bd8a 100644 --- a/backend/app/rest/api/admin.go +++ b/backend/app/rest/api/admin.go @@ -107,13 +107,20 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) { return } - if err = a.dataService.DeleteUserDetail(claims.Audience, claims.User.ID, engine.AllUserDetails); err != nil { + if len(claims.Audience) != 1 { + rest.SendErrorJSON(w, r, http.StatusBadRequest, fmt.Errorf("bad request"), "can't process token, aud is not a single element", rest.ErrActionRejected) + return + } + + audience := claims.Audience[0] + + if err = a.dataService.DeleteUserDetail(audience, claims.User.ID, engine.AllUserDetails); err != nil { code := parseError(err, rest.ErrInternal) rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user details for user", code) return } - if err = a.dataService.DeleteUser(claims.Audience, claims.User.ID, store.HardDelete); err != nil { + if err = a.dataService.DeleteUser(audience, claims.User.ID, store.HardDelete); err != nil { rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user", rest.ErrNoAccess) return } @@ -126,7 +133,7 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) { } } - a.cache.Flush(cache.Flusher(claims.Audience).Scopes(claims.Audience, claims.User.ID, lastCommentsScope)) + a.cache.Flush(cache.Flusher(audience).Scopes(audience, claims.User.ID, lastCommentsScope)) render.Status(r, http.StatusOK) render.JSON(w, r, R.JSON{"user_id": claims.User.ID, "site_id": claims.Audience}) } diff --git a/backend/app/rest/api/admin_test.go b/backend/app/rest/api/admin_test.go index 644a8ce5..577c9bb1 100644 --- a/backend/app/rest/api/admin_test.go +++ b/backend/app/rest/api/admin_test.go @@ -16,7 +16,7 @@ import ( "github.com/go-pkgz/auth/token" cache "github.com/go-pkgz/lcw/v2" R "github.com/go-pkgz/rest" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -708,12 +708,12 @@ func TestAdmin_DeleteMeRequest(t *testing.T) { claims := token.Claims{ SessionOnly: true, - StandardClaims: jwt.StandardClaims{ - Audience: "remark42", - Id: "1234567", + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"remark42"}, + ID: "1234567", Issuer: "remark42", - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), - ExpiresAt: time.Now().Add(30 * time.Minute).Unix(), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), }, User: &token.User{ ID: "user1", @@ -777,12 +777,12 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) { // try with bad auth claims := token.Claims{ SessionOnly: true, - StandardClaims: jwt.StandardClaims{ - Audience: "remark42", - Id: "provider1_1234567", + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"remark42"}, + ID: "provider1_1234567", Issuer: "remark42", - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), - ExpiresAt: time.Now().Add(30 * time.Minute).Unix(), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), }, User: &token.User{ ID: "provider1_user1", diff --git a/backend/app/rest/api/rest_private.go b/backend/app/rest/api/rest_private.go index d723a1a2..41e4b2ce 100644 --- a/backend/app/rest/api/rest_private.go +++ b/backend/app/rest/api/rest_private.go @@ -21,7 +21,7 @@ import ( cache "github.com/go-pkgz/lcw/v2" log "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/hashicorp/go-multierror" "github.com/umputun/remark42/backend/app/notify" @@ -362,10 +362,10 @@ func (s *private) sendEmailConfirmationCtrl(w http.ResponseWriter, r *http.Reque claims := token.Claims{ Handshake: &token.Handshake{ID: user.ID + "::" + subscribe.Address}, - StandardClaims: jwt.StandardClaims{ - Audience: r.URL.Query().Get("site"), - ExpiresAt: time.Now().Add(30 * time.Minute).Unix(), - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{r.URL.Query().Get("site")}, + ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), Issuer: "remark42", }, } @@ -704,11 +704,11 @@ func (s *private) deleteMeCtrl(w http.ResponseWriter, r *http.Request) { siteID := r.URL.Query().Get("site") claims := token.Claims{ - StandardClaims: jwt.StandardClaims{ - Audience: siteID, + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{siteID}, Issuer: "remark42", - ExpiresAt: time.Now().AddDate(0, 3, 0).Unix(), - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), + ExpiresAt: jwt.NewNumericDate(time.Now().AddDate(0, 3, 0)), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), }, User: &token.User{ ID: user.ID, diff --git a/backend/app/rest/api/rest_private_test.go b/backend/app/rest/api/rest_private_test.go index cea721fb..6d0d4c88 100644 --- a/backend/app/rest/api/rest_private_test.go +++ b/backend/app/rest/api/rest_private_test.go @@ -20,7 +20,7 @@ import ( "github.com/go-pkgz/auth/token" "github.com/go-pkgz/lgr" R "github.com/go-pkgz/rest" - "github.com/golang-jwt/jwt" + "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -861,10 +861,10 @@ func TestRest_EmailAndTelegram(t *testing.T) { // issue good token claims := token.Claims{ Handshake: &token.Handshake{ID: "provider1_dev::good@example.com"}, - StandardClaims: jwt.StandardClaims{ - Audience: "remark42", - ExpiresAt: time.Now().Add(10 * time.Minute).Unix(), - NotBefore: time.Now().Add(-1 * time.Minute).Unix(), + RegisteredClaims: jwt.RegisteredClaims{ + Audience: jwt.ClaimStrings{"remark42"}, + ExpiresAt: jwt.NewNumericDate(time.Now().Add(10 * time.Minute)), + NotBefore: jwt.NewNumericDate(time.Now().Add(-1 * time.Minute)), Issuer: "remark42", }, }