1
0
mirror of https://github.com/mainflux/mainflux.git synced 2025-04-24 13:48:49 +08:00

Fix issuing recovery key (#1007)

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
This commit is contained in:
Dušan Borovčanin 2020-01-16 18:43:06 +01:00 committed by Drasko DRASKOVIC
parent 8475e87fe7
commit c4fa27fd7e
6 changed files with 109 additions and 94 deletions

View File

@ -46,39 +46,67 @@ func startGRPCServer(svc authn.Service, port int) {
}
func TestIssue(t *testing.T) {
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
cases := map[string]struct {
token string
id string
kind uint32
err error
cases := []struct {
desc string
id string
kind uint32
err error
}{
"issue for user with valid token": {"", email, authn.UserKey, nil},
"issue for user that doesn't exist": {"", loginKey.Secret, 32, status.Error(codes.InvalidArgument, "received invalid token request")},
{
desc: "issue for user with valid token",
id: email,
kind: authn.UserKey,
err: nil,
},
{
desc: "issue recovery key",
id: email,
kind: authn.RecoveryKey,
err: nil,
},
{
desc: "issue API key",
id: userKey.Secret,
kind: authn.APIKey,
err: nil,
},
{
desc: "issue for invalid key type",
id: email,
kind: 32,
err: status.Error(codes.InvalidArgument, "received invalid token request"),
},
{
desc: "issue for user that exist",
id: "",
kind: authn.APIKey,
err: status.Error(codes.Unauthenticated, "unauthorized access"),
},
}
for desc, tc := range cases {
for _, tc := range cases {
_, err := client.Issue(context.Background(), &mainflux.IssueReq{Issuer: tc.id, Type: tc.kind})
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", desc, tc.err, err))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
userKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
recoveryKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing recovery key expected to succeed: %s", err))
apiKey, err := svc.Issue(context.Background(), userKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
assert.Nil(t, err, fmt.Sprintf("Issuing API key expected to succeed: %s", err))
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
@ -90,19 +118,19 @@ func TestIdentify(t *testing.T) {
err error
}{
{
desc: "identify user with reset token",
token: resetKey.Secret,
desc: "identify user with recovery token",
token: recoveryKey.Secret,
id: email,
err: nil,
},
{
desc: "identify user with user token",
token: userKey.Secret,
desc: "identify user with API token",
token: apiKey.Secret,
id: email,
err: nil,
},
{
desc: "identify user with invalid login token",
desc: "identify user with invalid user token",
token: "invalid",
id: "",
err: status.Error(codes.Unauthenticated, "unauthorized access"),

View File

@ -80,16 +80,16 @@ func toJSON(data interface{}) string {
func TestIssue(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
lk := issueRequest{Type: authn.UserKey}
uk := issueRequest{Type: authn.UserKey}
ak := issueRequest{Type: authn.APIKey, Duration: time.Hour}
rk := issueRequest{Type: authn.RecoveryKey}
uk := issueRequest{Type: authn.APIKey, Duration: time.Hour}
cases := []struct {
desc string
@ -99,48 +99,48 @@ func TestIssue(t *testing.T) {
status int
}{
{
desc: "issue login key",
req: toJSON(lk),
desc: "issue user key",
req: toJSON(uk),
ct: contentType,
token: "",
status: http.StatusCreated,
},
{
desc: "issue user key",
req: toJSON(uk),
desc: "issue API key",
req: toJSON(ak),
ct: contentType,
token: loginKey.Secret,
token: userKey.Secret,
status: http.StatusCreated,
},
{
desc: "issue reset key",
desc: "issue recovery key",
req: toJSON(rk),
ct: contentType,
token: loginKey.Secret,
token: userKey.Secret,
status: http.StatusBadRequest,
},
{
desc: "issue login key wrong content type",
req: toJSON(lk),
ct: "", token: loginKey.Secret,
desc: "issue user key wrong content type",
req: toJSON(uk),
ct: "", token: userKey.Secret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key wrong content type",
req: toJSON(rk),
ct: "",
token: loginKey.Secret,
token: userKey.Secret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key unauthorized",
req: toJSON(uk),
req: toJSON(ak),
ct: contentType,
token: "wrong",
status: http.StatusForbidden,
},
{
desc: "issue reset key with empty token",
desc: "issue recovery key with empty token",
req: toJSON(rk),
ct: contentType,
token: "",
@ -238,12 +238,12 @@ func TestRetrieve(t *testing.T) {
func TestRevoke(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
key := authn.Key{Type: authn.APIKey, IssuedAt: time.Now()}
k, err := svc.Issue(context.Background(), loginKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
k, err := svc.Issue(context.Background(), userKey.Secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
@ -258,13 +258,13 @@ func TestRevoke(t *testing.T) {
{
desc: "revoke an existing key",
id: k.ID,
token: loginKey.Secret,
token: userKey.Secret,
status: http.StatusNoContent,
},
{
desc: "revoke a non-existing key",
id: "non-existing",
token: loginKey.Secret,
token: userKey.Secret,
status: http.StatusNoContent,
},
{

View File

@ -10,9 +10,9 @@ import (
)
const (
loginDuration = 10 * time.Hour
resetDuration = 5 * time.Minute
issuerName = "mainflux.authn"
loginDuration = 10 * time.Hour
recoveryDuration = 5 * time.Minute
issuerName = "mainflux.authn"
)
var (
@ -75,9 +75,9 @@ func (svc service) Issue(ctx context.Context, issuer string, key Key) (Key, erro
case APIKey:
return svc.userKey(ctx, issuer, key)
case RecoveryKey:
return svc.resetKey(ctx, issuer, key)
return svc.tmpKey(issuer, recoveryDuration, key)
default:
return svc.loginKey(issuer, key)
return svc.tmpKey(issuer, loginDuration, key)
}
}
@ -127,22 +127,8 @@ func (svc service) Identify(ctx context.Context, token string) (string, error) {
}
}
func (svc service) loginKey(issuer string, key Key) (Key, error) {
func (svc service) tmpKey(issuer string, duration time.Duration, key Key) (Key, error) {
key.Secret = issuer
return svc.tempKey(loginDuration, key)
}
func (svc service) resetKey(ctx context.Context, issuer string, key Key) (Key, error) {
issuer, err := svc.login(issuer)
if err != nil {
return Key{}, err
}
key.Secret = issuer
return svc.tempKey(resetDuration, key)
}
func (svc service) tempKey(duration time.Duration, key Key) (Key, error) {
key.Issuer = issuerName
key.ExpiresAt = key.IssuedAt.Add(duration)
val, err := svc.tokenizer.Issue(key)

View File

@ -29,7 +29,7 @@ func newService() authn.Service {
func TestIssue(t *testing.T) {
svc := newService()
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
userKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
cases := []struct {
@ -39,7 +39,7 @@ func TestIssue(t *testing.T) {
err error
}{
{
desc: "issue login key",
desc: "issue user key",
key: authn.Key{
Type: authn.UserKey,
IssuedAt: time.Now(),
@ -48,7 +48,7 @@ func TestIssue(t *testing.T) {
err: nil,
},
{
desc: "issue login key no issue time",
desc: "issue user key no issue time",
key: authn.Key{
Type: authn.UserKey,
},
@ -56,16 +56,16 @@ func TestIssue(t *testing.T) {
err: authn.ErrInvalidKeyIssuedAt,
},
{
desc: "issue user key",
desc: "issue API key",
key: authn.Key{
Type: authn.APIKey,
IssuedAt: time.Now(),
},
issuer: loginKey.Secret,
issuer: userKey.Secret,
err: nil,
},
{
desc: "issue user key unauthorized",
desc: "issue API key unauthorized",
key: authn.Key{
Type: authn.APIKey,
IssuedAt: time.Now(),
@ -74,28 +74,28 @@ func TestIssue(t *testing.T) {
err: authn.ErrUnauthorizedAccess,
},
{
desc: "issue user key no issue time",
desc: "issue API key no issue time",
key: authn.Key{
Type: authn.APIKey,
},
issuer: loginKey.Secret,
issuer: userKey.Secret,
err: authn.ErrInvalidKeyIssuedAt,
},
{
desc: "issue reset key",
desc: "issue recovery key",
key: authn.Key{
Type: authn.RecoveryKey,
IssuedAt: time.Now(),
},
issuer: loginKey.Secret,
issuer: userKey.Secret,
err: nil,
},
{
desc: "issue reset key no issue time",
desc: "issue recovery key no issue time",
key: authn.Key{
Type: authn.RecoveryKey,
},
issuer: loginKey.Secret,
issuer: userKey.Secret,
err: authn.ErrInvalidKeyIssuedAt,
},
}
@ -213,7 +213,7 @@ func TestIdentify(t *testing.T) {
loginKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.UserKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
resetKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
recoveryKey, err := svc.Issue(context.Background(), email, authn.Key{Type: authn.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
userKey, err := svc.Issue(context.Background(), loginKey.Secret, authn.Key{Type: authn.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
@ -239,8 +239,8 @@ func TestIdentify(t *testing.T) {
err: nil,
},
{
desc: "identify reset key",
key: resetKey.Secret,
desc: "identify recovery key",
key: recoveryKey.Secret,
id: email,
err: nil,
},

View File

@ -233,6 +233,8 @@ func encodeError(_ context.Context, err error, w http.ResponseWriter) {
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(errorVal, users.ErrUserNotFound):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(errorVal, users.ErrRecoveryToken):
w.WriteHeader(http.StatusInternalServerError)
}
if errorVal.Msg() != "" {
json.NewEncoder(w).Encode(errorRes{Err: errorVal.Msg()})

View File

@ -24,27 +24,26 @@ var (
// when accessing a protected resource.
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
// ErrNotFound indicates a non-existent entity request
// ErrNotFound indicates a non-existent entity request.
ErrNotFound = errors.New("non-existent entity")
// ErrUserNotFound indicates a non-existent user request
// ErrUserNotFound indicates a non-existent user request.
ErrUserNotFound = errors.New("non-existent user")
// ErrScanMetadata indicates problem with metadata in db
// ErrScanMetadata indicates problem with metadata in db.
ErrScanMetadata = errors.New("Failed to scan metadata")
// ErrMissingEmail indicates missing email for password reset request
// ErrMissingEmail indicates missing email for password reset request.
ErrMissingEmail = errors.New("missing email for password reset")
// ErrMissingResetToken indicates malformed or missing reset token
// for reseting password
// for reseting password.
ErrMissingResetToken = errors.New("error missing reset token")
// ErrGeneratingResetToken indicates error in generating password recovery
// token
ErrGeneratingResetToken = errors.New("error missing reset token")
// ErrRecoveryToken indicates error in generating password recovery token.
ErrRecoveryToken = errors.New("error generating password recovery token")
// ErrGetToken indicates error in getting signed token
// ErrGetToken indicates error in getting signed token.
ErrGetToken = errors.New("Get signed token failed")
)
@ -60,10 +59,10 @@ type Service interface {
// identified by the non-nil error values in the response.
Login(context.Context, User) (string, errors.Error)
// Get authenticated user info for the given token
// Get authenticated user info for the given token.
UserInfo(ctx context.Context, token string) (User, errors.Error)
// UpdateUser updates the user metadata
// UpdateUser updates the user metadata.
UpdateUser(ctx context.Context, token string, user User) errors.Error
// GenerateResetToken email where mail will be sent.
@ -77,7 +76,7 @@ type Service interface {
// token can be authentication token or password reset token.
ResetPassword(_ context.Context, resetToken, password string) errors.Error
//SendPasswordReset sends reset password link to email
//SendPasswordReset sends reset password link to email.
SendPasswordReset(_ context.Context, host, email, token string) errors.Error
}
@ -163,7 +162,7 @@ func (svc usersService) GenerateResetToken(ctx context.Context, email, host stri
t, err := svc.issue(ctx, email, authn.RecoveryKey)
if err != nil {
return errors.Wrap(ErrGeneratingResetToken, err)
return errors.Wrap(ErrRecoveryToken, err)
}
return svc.SendPasswordReset(ctx, host, email, t)
}