Skip to content

Commit 3063a3d

Browse files
heiytorgustavosbarreto
authored andcommitted
feat(api): clear cached user token upon exiting authenticated namespace
Ensure user tokens are removed from cache when users leave the authenticated namespace.
1 parent 6c5c156 commit 3063a3d

File tree

4 files changed

+77
-23
lines changed

4 files changed

+77
-23
lines changed

api/routes/nsadm_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,12 @@ func TestHandler_LeaveNamespace(t *testing.T) {
410410
description: "fails to leave the namespace",
411411
tenantID: "00000000-0000-4000-0000-000000000000",
412412
headers: map[string]string{
413-
"X-ID": "000000000000000000000000",
413+
"X-ID": "000000000000000000000000",
414+
"X-Tenant-ID": "00000000-0000-4000-0000-000000000000",
414415
},
415416
requiredMocks: func() {
416417
svcMock.
417-
On("LeaveNamespace", gomock.Anything, &requests.LeaveNamespace{UserID: "000000000000000000000000", TenantID: "00000000-0000-4000-0000-000000000000"}).
418+
On("LeaveNamespace", gomock.Anything, &requests.LeaveNamespace{UserID: "000000000000000000000000", TenantID: "00000000-0000-4000-0000-000000000000", AuthenticatedTenantID: "00000000-0000-4000-0000-000000000000"}).
418419
Return(errors.New("error")).
419420
Once()
420421
},
@@ -429,7 +430,7 @@ func TestHandler_LeaveNamespace(t *testing.T) {
429430
},
430431
requiredMocks: func() {
431432
svcMock.
432-
On("LeaveNamespace", gomock.Anything, &requests.LeaveNamespace{UserID: "000000000000000000000000", TenantID: "00000000-0000-4000-0000-000000000000"}).
433+
On("LeaveNamespace", gomock.Anything, &requests.LeaveNamespace{UserID: "000000000000000000000000", TenantID: "00000000-0000-4000-0000-000000000000", AuthenticatedTenantID: "00000000-0000-4000-0000-000000000000"}).
433434
Return(nil).
434435
Once()
435436
},

api/services/member.go

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ type MemberService interface {
4242
// authority than the target member. Owners cannot be removed. Returns the updated namespace and an error, if any.
4343
RemoveNamespaceMember(ctx context.Context, req *requests.NamespaceRemoveMember) (*models.Namespace, error)
4444

45-
// LeaveNamespace allows the authenticated user to remove themselves from the namespace. Owners cannot leave the namespace.
45+
// LeaveNamespace allows an authenticated user to remove themselves from a namespace. Owners cannot leave a namespace.
46+
// If the user attempts to leave the namespace they are authenticated to, their authentication token will be invalidated.
4647
// Returns an error, if any.
4748
LeaveNamespace(ctx context.Context, req *requests.LeaveNamespace) error
4849
}
@@ -221,6 +222,13 @@ func (s *service) RemoveNamespaceMember(ctx context.Context, req *requests.Names
221222
return nil, err
222223
}
223224

225+
if err := s.AuthUncacheToken(ctx, req.TenantID, req.UserID); err != nil {
226+
log.WithError(err).
227+
WithField("tenant_id", req.TenantID).
228+
WithField("user_id", req.UserID).
229+
Error("failed to uncache the token")
230+
}
231+
224232
return s.store.NamespaceGet(ctx, req.TenantID, s.store.Options().CountAcceptedDevices(), s.store.Options().EnrichMembersData())
225233
}
226234

@@ -238,6 +246,15 @@ func (s *service) LeaveNamespace(ctx context.Context, req *requests.LeaveNamespa
238246
return err
239247
}
240248

249+
if req.TenantID == req.AuthenticatedTenantID {
250+
if err := s.AuthUncacheToken(ctx, req.TenantID, req.UserID); err != nil {
251+
log.WithError(err).
252+
WithField("tenant_id", req.TenantID).
253+
WithField("user_id", req.UserID).
254+
Error("failed to uncache the token")
255+
}
256+
}
257+
241258
return nil
242259
}
243260

@@ -253,12 +270,5 @@ func (s *service) removeMember(ctx context.Context, ns *models.Namespace, userID
253270
}
254271
}
255272

256-
if err := s.AuthUncacheToken(ctx, ns.TenantID, userID); err != nil {
257-
log.WithError(err).
258-
WithField("tenant_id", ns.TenantID).
259-
WithField("user_id", userID).
260-
Error("failed to remove the member")
261-
}
262-
263273
return nil
264274
}

api/services/member_test.go

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/shellhub-io/shellhub/pkg/api/authorizer"
1212
"github.com/shellhub-io/shellhub/pkg/api/requests"
1313
storecache "github.com/shellhub-io/shellhub/pkg/cache"
14+
cachemock "github.com/shellhub-io/shellhub/pkg/cache/mocks"
1415
"github.com/shellhub-io/shellhub/pkg/clock"
1516
clockmock "github.com/shellhub-io/shellhub/pkg/clock/mocks"
1617
"github.com/shellhub-io/shellhub/pkg/envs"
@@ -1565,6 +1566,7 @@ func TestService_LeaveNamespace(t *testing.T) {
15651566
}
15661567

15671568
storeMock := new(storemock.Store)
1569+
cacheMock := new(cachemock.Cache)
15681570

15691571
cases := []struct {
15701572
description string
@@ -1575,8 +1577,9 @@ func TestService_LeaveNamespace(t *testing.T) {
15751577
{
15761578
description: "fails when the namespace was not found",
15771579
req: &requests.LeaveNamespace{
1578-
UserID: "000000000000000000000000",
1579-
TenantID: "00000000-0000-4000-0000-000000000000",
1580+
UserID: "000000000000000000000000",
1581+
TenantID: "00000000-0000-4000-0000-000000000000",
1582+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000001",
15801583
},
15811584
requiredMocks: func(ctx context.Context) {
15821585
storeMock.
@@ -1589,8 +1592,9 @@ func TestService_LeaveNamespace(t *testing.T) {
15891592
{
15901593
description: "fails when the user is not on the namespace",
15911594
req: &requests.LeaveNamespace{
1592-
UserID: "000000000000000000000000",
1593-
TenantID: "00000000-0000-4000-0000-000000000000",
1595+
UserID: "000000000000000000000000",
1596+
TenantID: "00000000-0000-4000-0000-000000000000",
1597+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000001",
15941598
},
15951599
requiredMocks: func(ctx context.Context) {
15961600
storeMock.
@@ -1608,8 +1612,9 @@ func TestService_LeaveNamespace(t *testing.T) {
16081612
{
16091613
description: "fails when the user is owner",
16101614
req: &requests.LeaveNamespace{
1611-
UserID: "000000000000000000000000",
1612-
TenantID: "00000000-0000-4000-0000-000000000000",
1615+
UserID: "000000000000000000000000",
1616+
TenantID: "00000000-0000-4000-0000-000000000000",
1617+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000001",
16131618
},
16141619
requiredMocks: func(ctx context.Context) {
16151620
storeMock.
@@ -1632,8 +1637,9 @@ func TestService_LeaveNamespace(t *testing.T) {
16321637
{
16331638
description: "fails when cannot remove the member",
16341639
req: &requests.LeaveNamespace{
1635-
UserID: "000000000000000000000000",
1636-
TenantID: "00000000-0000-4000-0000-000000000000",
1640+
UserID: "000000000000000000000000",
1641+
TenantID: "00000000-0000-4000-0000-000000000000",
1642+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000001",
16371643
},
16381644
requiredMocks: func(ctx context.Context) {
16391645
storeMock.
@@ -1660,8 +1666,38 @@ func TestService_LeaveNamespace(t *testing.T) {
16601666
{
16611667
description: "succeeds",
16621668
req: &requests.LeaveNamespace{
1663-
UserID: "000000000000000000000000",
1664-
TenantID: "00000000-0000-4000-0000-000000000000",
1669+
UserID: "000000000000000000000000",
1670+
TenantID: "00000000-0000-4000-0000-000000000000",
1671+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000001",
1672+
},
1673+
requiredMocks: func(ctx context.Context) {
1674+
storeMock.
1675+
On("NamespaceGet", ctx, "00000000-0000-4000-0000-000000000000").
1676+
Return(&models.Namespace{
1677+
TenantID: "00000000-0000-4000-0000-000000000000",
1678+
Name: "namespace",
1679+
Owner: "000000000000000000000000",
1680+
Members: []models.Member{
1681+
{
1682+
ID: "000000000000000000000000",
1683+
Role: authorizer.RoleAdministrator,
1684+
},
1685+
},
1686+
}, nil).
1687+
Once()
1688+
storeMock.
1689+
On("NamespaceRemoveMember", ctx, "00000000-0000-4000-0000-000000000000", "000000000000000000000000").
1690+
Return(nil).
1691+
Once()
1692+
},
1693+
expected: Expected{err: nil},
1694+
},
1695+
{
1696+
description: "succeeds when TenantID is equal to AuthenticatedTenantID",
1697+
req: &requests.LeaveNamespace{
1698+
UserID: "000000000000000000000000",
1699+
TenantID: "00000000-0000-4000-0000-000000000000",
1700+
AuthenticatedTenantID: "00000000-0000-4000-0000-000000000000",
16651701
},
16661702
requiredMocks: func(ctx context.Context) {
16671703
storeMock.
@@ -1682,12 +1718,16 @@ func TestService_LeaveNamespace(t *testing.T) {
16821718
On("NamespaceRemoveMember", ctx, "00000000-0000-4000-0000-000000000000", "000000000000000000000000").
16831719
Return(nil).
16841720
Once()
1721+
cacheMock.
1722+
On("Delete", ctx, "token_00000000-0000-4000-0000-000000000000000000000000000000000000").
1723+
Return(nil).
1724+
Once()
16851725
},
16861726
expected: Expected{err: nil},
16871727
},
16881728
}
16891729

1690-
s := NewService(storeMock, privateKey, publicKey, storecache.NewNullCache(), clientMock)
1730+
s := NewService(storeMock, privateKey, publicKey, cacheMock, clientMock)
16911731

16921732
for _, tc := range cases {
16931733
t.Run(tc.description, func(t *testing.T) {

pkg/api/requests/namespace.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,11 @@ type NamespaceRemoveMember struct {
7575
}
7676

7777
type LeaveNamespace struct {
78-
UserID string `header:"X-ID" validate:"required"`
78+
UserID string `header:"X-ID" validate:"required"`
79+
// TenantID represents the namespace that the user intends to leave.
7980
TenantID string `param:"tenant" validate:"required,uuid"`
81+
// AuthenticatedTenantID represents the namespace to which the user is currently authenticated.
82+
AuthenticatedTenantID string `header:"X-Tenant-ID" validate:"required"`
8083
}
8184

8285
// SessionEditRecordStatus is the structure to represent the request data for edit session record status endpoint.

0 commit comments

Comments
 (0)