add deleteme flag to jwt token

it should prevent misuse of laked token to request user's data removal
This commit is contained in:
Umputun
2018-07-18 16:56:23 -05:00
parent c1e2616764
commit 3abe4146b1
6 changed files with 56 additions and 1 deletions
+5
View File
@@ -108,6 +108,11 @@ func (a *admin) deleteMeRequestCtrl(w http.ResponseWriter, r *http.Request) {
log.Printf("[INFO] delete all user comments by request for %s, site %s", claims.User.ID, claims.SiteID)
if !claims.DeleteMe { // deletme set by deleteMeCtrl, this check just to make sure we not trying to delete with leaked token
rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("forbidden"), "can't use provided token")
return
}
if err := a.dataService.DeleteUser(claims.SiteID, claims.User.ID); err != nil {
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete user")
return
+17
View File
@@ -497,6 +497,7 @@ func TestAdmin_DeleteMeRequest(t *testing.T) {
User: &store.User{
ID: "user1",
},
DeleteMe: true,
}
token, err := srv.Authenticator.JWTService.Token(&claims)
@@ -551,6 +552,7 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
User: &store.User{
ID: "user1",
},
DeleteMe: true,
}
token, err := srv.Authenticator.JWTService.Token(&claims)
@@ -573,6 +575,21 @@ func TestAdmin_DeleteMeRequestFailed(t *testing.T) {
resp, err = client.Do(req)
assert.Nil(t, err)
assert.Equal(t, 400, resp.StatusCode, resp.Status)
// try without deleteme flag
badClaims2 := claims
badClaims2.DeleteMe = false
token, err = srv.Authenticator.JWTService.Token(&badClaims2)
assert.Nil(t, err)
req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/admin/deleteme?token=%s", ts.URL, token), nil)
assert.Nil(t, err)
req.SetBasicAuth("dev", "password")
resp, err = client.Do(req)
assert.Nil(t, err)
assert.Equal(t, 403, resp.StatusCode)
b, err := ioutil.ReadAll(resp.Body)
assert.Nil(t, err)
assert.True(t, strings.Contains(string(b), "can't use provided token"))
}
func TestAdmin_GetUserInfo(t *testing.T) {
+3 -1
View File
@@ -255,7 +255,8 @@ func (s *Rest) deleteMeCtrl(w http.ResponseWriter, r *http.Request) {
ExpiresAt: time.Now().AddDate(0, 3, 0).Unix(),
NotBefore: time.Now().Add(-1 * time.Minute).Unix(),
},
User: &user,
User: &user,
DeleteMe: true, // prevent this token from being used for login
}
tokenStr, err := s.Authenticator.JWTService.Token(&claims)
@@ -263,6 +264,7 @@ func (s *Rest) deleteMeCtrl(w http.ResponseWriter, r *http.Request) {
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't make token")
return
}
link := fmt.Sprintf("%s/web/deleteme.html?token=%s", s.RemarkURL, tokenStr)
render.JSON(w, r, JSON{"site": siteID, "user_id": user.ID, "token": tokenStr, "link": link})
}
+6
View File
@@ -73,6 +73,12 @@ func (a *Authenticator) Auth(reqAuth bool) func(http.Handler) http.Handler {
return
}
if claims.DeleteMe {
log.Printf("[DEBUG] invalid token flags for %s/%s", claims.User.Name, claims.User.ID)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if a.JWTService.IsExpired(claims) {
if claims, err = a.refreshExpiredToken(w, claims); err != nil {
log.Printf("[DEBUG] can't refresh jwt, %s", err)
+22
View File
@@ -15,6 +15,8 @@ import (
var testJwtUserBlocked = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4MjIsImp0aSI6InJhbmRvbSBpZCIsImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyLCJ1c2VyIjp7Im5hbWUiOiJuYW1lMSIsImlkIjoiaWQxIiwicGljdHVyZSI6IiIsImFkbWluIjpmYWxzZSwiYmxvY2siOnRydWV9LCJzdGF0ZSI6IjEyMzQ1NiIsImZyb20iOiJmcm9tIn0.6P_OwGf8CUJRtvNSlW20GmaMb5pFvCNemP94fHCqb5Q"
var testJwtDeleteMe = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjI3ODkxOTE4MjIsImp0aSI6InJhbmRvbSBpZCIsImlzcyI6InJlbWFyazQyIiwibmJmIjoxNTI2ODg0MjIyLCJ1c2VyIjp7Im5hbWUiOiJuYW1lMSIsImlkIjoiaWQxIiwicGljdHVyZSI6IiIsImFkbWluIjpmYWxzZSwiYmxvY2siOmZhbHNlfSwiZGVsZXRlbWUiOnRydWV9.3wiT5fqDv_bzPky6-3IilU8ExfzCyvLpKDMPYOAFWEo"
func TestAuthJWTCookie(t *testing.T) {
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT("xyz 12345", false, time.Hour, time.Hour),
PermissionChecker: &mockUserPermissions{}}
@@ -100,6 +102,26 @@ func TestAuthJWtBlocked(t *testing.T) {
assert.Equal(t, 401, resp.StatusCode, "blocked user")
}
func TestAuthJWtFlags(t *testing.T) {
a := Authenticator{DevPasswd: "123456", JWTService: NewJWT("xyz 12345", false, time.Hour, time.Hour)}
router := chi.NewRouter()
router.With(a.Auth(true)).Get("/auth", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(201)
})
server := httptest.NewServer(router)
defer server.Close()
jar, err := cookiejar.New(nil)
require.Nil(t, err)
client := &http.Client{Jar: jar, Timeout: 5 * time.Second}
req, err := http.NewRequest("GET", server.URL+"/auth", nil)
require.Nil(t, err)
req.Header.Add("X-JWT", testJwtDeleteMe)
resp, err := client.Do(req)
require.NoError(t, err)
assert.Equal(t, 401, resp.StatusCode, "blocked user")
}
func TestAuthRequired(t *testing.T) {
a := Authenticator{DevPasswd: "123456"}
router := chi.NewRouter()
+3
View File
@@ -29,6 +29,9 @@ type CustomClaims struct {
From string `json:"from,omitempty"`
SiteID string `json:"site_id,omitempty"`
SessionOnly bool `json:"sess_only,omitempty"`
// flags indicate different uses
DeleteMe bool `json:"deleteme,omitempty"`
}
const jwtCookieName = "JWT"