Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NOISSUE - Revoke Refresh Token #2240

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ The service is configured using the environment variables presented in the follo
| MG_SPICEDB_PORT | SpiceDB host port | 50051 |
| MG_SPICEDB_PRE_SHARED_KEY | SpiceDB pre-shared key | 12345678 |
| MG_SPICEDB_SCHEMA_FILE | Path to SpiceDB schema file | ./docker/spicedb/schema.zed |
| MG_AUTH_CACHE_URL | Cache server URL | "redis://localhost:6379/0" |
| MG_AUTH_CACHE_KEY_DURATION | Cache key expiration period | "1h" |
| MG_JAEGER_URL | Jaeger server URL | <http://jaeger:14268/api/traces> |
| MG_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 |
| MG_SEND_TELEMETRY | Send telemetry to magistrala call home server | true |
Expand Down Expand Up @@ -142,6 +144,8 @@ MG_SPICEDB_HOST=localhost \
MG_SPICEDB_PORT=50051 \
MG_SPICEDB_PRE_SHARED_KEY=12345678 \
MG_SPICEDB_SCHEMA_FILE=./docker/spicedb/schema.zed \
MG_AUTH_CACHE_URL=redis://localhost:6379/0 \
MG_AUTH_CACHE_KEY_DURATION=1h \
MG_JAEGER_URL=http://localhost:14268/api/traces \
MG_JAEGER_TRACE_RATIO=1.0 \
MG_SEND_TELEMETRY=true \
Expand Down
15 changes: 15 additions & 0 deletions auth/api/http/keys/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,18 @@ func revokeEndpoint(svc auth.Service) endpoint.Endpoint {
return revokeKeyRes{}, nil
}
}

func revokeTokenEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(revokeTokenReq)
if err := req.validate(); err != nil {
return nil, err
}

if err := svc.RevokeToken(ctx, req.token); err != nil {
return nil, err
}

return revokeKeyRes{}, nil
}
}
148 changes: 89 additions & 59 deletions auth/api/http/keys/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
package keys_test

import (
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -16,12 +15,11 @@ import (

"github.com/absmach/magistrala/auth"
httpapi "github.com/absmach/magistrala/auth/api/http"
"github.com/absmach/magistrala/auth/jwt"
"github.com/absmach/magistrala/auth/mocks"
"github.com/absmach/magistrala/internal/testsutil"
mglog "github.com/absmach/magistrala/logger"
"github.com/absmach/magistrala/pkg/apiutil"
svcerr "github.com/absmach/magistrala/pkg/errors/service"
"github.com/absmach/magistrala/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
Expand Down Expand Up @@ -66,17 +64,6 @@ func (tr testRequest) make() (*http.Response, error) {
return tr.client.Do(req)
}

func newService() (auth.Service, *mocks.KeyRepository) {
krepo := new(mocks.KeyRepository)
prepo := new(mocks.PolicyAgent)
drepo := new(mocks.DomainsRepository)
idProvider := uuid.NewMock()

t := jwt.New([]byte(secret))

return auth.New(krepo, drepo, idProvider, t, prepo, loginDuration, refreshDuration, invalidDuration), krepo
}

func newServer(svc auth.Service) *httptest.Server {
mux := httpapi.MakeHandler(svc, mglog.NewMock(), "")
return httptest.NewServer(mux)
Expand All @@ -91,9 +78,7 @@ func toJSON(data interface{}) string {
}

func TestIssue(t *testing.T) {
svc, krepo := newService()
token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
svc := new(mocks.Service)

ts := newServer(svc)
defer ts.Close()
Expand All @@ -108,76 +93,82 @@ func TestIssue(t *testing.T) {
req string
ct string
token string
resp auth.Token
err error
status int
}{
{
desc: "issue login key with empty token",
req: toJSON(lk),
resp: auth.Token{AccessToken: "token"},
ct: contentType,
token: "",
status: http.StatusUnauthorized,
},
{
desc: "issue API key",
req: toJSON(ak),
resp: auth.Token{AccessToken: "token"},
ct: contentType,
token: token.AccessToken,
token: "token",
status: http.StatusCreated,
},
{
desc: "issue recovery key",
req: toJSON(rk),
ct: contentType,
token: token.AccessToken,
token: "token",
status: http.StatusCreated,
},
{
desc: "issue login key wrong content type",
req: toJSON(lk),
ct: "",
token: token.AccessToken,
token: "token",
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue recovery key wrong content type",
req: toJSON(rk),
ct: "",
token: token.AccessToken,
token: "token",
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key with an invalid token",
req: toJSON(ak),
ct: contentType,
token: "wrong",
err: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
},
{
desc: "issue recovery key with empty token",
req: toJSON(rk),
ct: contentType,
token: "",
err: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
},
{
desc: "issue key with invalid request",
req: "{",
ct: contentType,
token: token.AccessToken,
token: "token",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON",
req: "{invalid}",
ct: contentType,
token: token.AccessToken,
token: "token",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON content",
req: `{"Type":{"key":"AccessToken"}}`,
ct: contentType,
token: token.AccessToken,
token: "token",
status: http.StatusBadRequest,
},
}
Expand All @@ -191,24 +182,16 @@ func TestIssue(t *testing.T) {
token: tc.token,
body: strings.NewReader(tc.req),
}
repocall := krepo.On("Save", mock.Anything, mock.Anything).Return("", nil)
svcCall := svc.On("Issue", mock.Anything, tc.token, mock.Anything).Return(tc.resp, tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
repocall.Unset()
svcCall.Unset()
}
}

func TestRetrieve(t *testing.T) {
svc, krepo := newService()
token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), Subject: id}

repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil)
k, err := svc.Issue(context.Background(), token.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
repocall.Unset()
svc := new(mocks.Service)

ts := newServer(svc)
defer ts.Close()
Expand All @@ -224,8 +207,8 @@ func TestRetrieve(t *testing.T) {
}{
{
desc: "retrieve an existing key",
id: k.AccessToken,
token: token.AccessToken,
id: testsutil.GenerateUUID(t),
token: "token",
key: auth.Key{
Subject: id,
Type: auth.AccessKey,
Expand All @@ -238,21 +221,21 @@ func TestRetrieve(t *testing.T) {
{
desc: "retrieve a non-existing key",
id: "non-existing",
token: token.AccessToken,
status: http.StatusBadRequest,
token: "token",
status: http.StatusNotFound,
err: svcerr.ErrNotFound,
},
{
desc: "retrieve a key with an invalid token",
id: k.AccessToken,
id: testsutil.GenerateUUID(t),
token: "wrong",
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
{
desc: "retrieve a key with an empty token",
token: "",
id: k.AccessToken,
id: testsutil.GenerateUUID(t),
status: http.StatusUnauthorized,
err: svcerr.ErrAuthentication,
},
Expand All @@ -265,24 +248,16 @@ func TestRetrieve(t *testing.T) {
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
repocall := krepo.On("Retrieve", mock.Anything, mock.Anything, mock.Anything).Return(tc.key, tc.err)
svcCall := svc.On("RetrieveKey", mock.Anything, tc.token, tc.id).Return(tc.key, tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
repocall.Unset()
svcCall.Unset()
}
}

func TestRevoke(t *testing.T) {
svc, krepo := newService()
token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.AccessKey, IssuedAt: time.Now(), Subject: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), Subject: id}

repocall := krepo.On("Save", mock.Anything, mock.Anything).Return(mock.Anything, nil)
k, err := svc.Issue(context.Background(), token.AccessToken, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
repocall.Unset()
svc := new(mocks.Service)

ts := newServer(svc)
defer ts.Close()
Expand All @@ -292,29 +267,31 @@ func TestRevoke(t *testing.T) {
desc string
id string
token string
err error
status int
}{
{
desc: "revoke an existing key",
id: k.AccessToken,
token: token.AccessToken,
id: testsutil.GenerateUUID(t),
token: "token",
status: http.StatusNoContent,
},
{
desc: "revoke a non-existing key",
id: "non-existing",
token: token.AccessToken,
token: "token",
status: http.StatusNoContent,
},
{
desc: "revoke key with invalid token",
id: k.AccessToken,
id: testsutil.GenerateUUID(t),
token: "wrong",
err: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
},
{
desc: "revoke key with empty token",
id: k.AccessToken,
id: testsutil.GenerateUUID(t),
token: "",
status: http.StatusUnauthorized,
},
Expand All @@ -327,10 +304,63 @@ func TestRevoke(t *testing.T) {
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
repocall := krepo.On("Remove", mock.Anything, mock.Anything, mock.Anything).Return(nil)
svcCall := svc.On("Revoke", mock.Anything, tc.token, tc.id).Return(tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
svcCall.Unset()
}
}

func TestRevokeToken(t *testing.T) {
svc := new(mocks.Service)

ts := newServer(svc)
defer ts.Close()
client := ts.Client()

cases := []struct {
desc string
id string
token string
err error
status int
}{
{
desc: "revoke an existing token",
token: "token",
status: http.StatusNoContent,
},
{
desc: "revoke a non-existing token",
token: "token",
err: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
},
{
desc: "revoke invalid token",
token: "wrong",
err: svcerr.ErrAuthentication,
status: http.StatusUnauthorized,
},
{
desc: "revoke empty token",
token: "",
status: http.StatusUnauthorized,
},
}

for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodDelete,
url: fmt.Sprintf("%s/keys/", ts.URL),
token: tc.token,
}
svcCall := svc.On("RevokeToken", mock.Anything, tc.token).Return(tc.err)
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
repocall.Unset()
svcCall.Unset()
}
}
12 changes: 12 additions & 0 deletions auth/api/http/keys/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@ func (req keyReq) validate() error {
}
return nil
}

type revokeTokenReq struct {
token string
}

func (req revokeTokenReq) validate() error {
if req.token == "" {
return apiutil.ErrBearerToken
}

return nil
}
Loading
Loading