Commit dd84e73c authored by Tyler Cloke's avatar Tyler Cloke Committed by Alban Seurat

Add VerifyPassword to API

It takes in an email and plain text password to verify. If it fails to find a password stored for email, it returns not_found. If it finds the password hash stored but that hash doesn't match the password passed via the API, it returns verified = false, else it returns verified = true.
Co-authored-by: 's avatarAlban Seurat <alban.seurat@me.com>
parent 92920c86
This diff is collapsed.
...@@ -148,6 +148,16 @@ message RevokeRefreshResp { ...@@ -148,6 +148,16 @@ message RevokeRefreshResp {
bool not_found = 1; bool not_found = 1;
} }
message VerifyPasswordReq {
string email = 1;
string password = 2;
}
message VerifyPasswordResp {
bool verified = 1;
bool not_found = 2;
}
// Dex represents the dex gRPC service. // Dex represents the dex gRPC service.
service Dex { service Dex {
// CreateClient creates a client. // CreateClient creates a client.
...@@ -172,4 +182,6 @@ service Dex { ...@@ -172,4 +182,6 @@ service Dex {
// //
// Note that each user-client pair can have only one refresh token at a time. // Note that each user-client pair can have only one refresh token at a time.
rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {}; rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};
// VerifyPassword returns whether a password matches a hash for a specific email or not.
rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {};
} }
...@@ -48,7 +48,8 @@ Finally run the Dex client providing the CA certificate, client certificate and ...@@ -48,7 +48,8 @@ Finally run the Dex client providing the CA certificate, client certificate and
Running the gRPC client will cause the following API calls to be made to the server Running the gRPC client will cause the following API calls to be made to the server
1. CreatePassword 1. CreatePassword
2. ListPasswords 2. ListPasswords
3. DeletePassword 3. VerifyPassword
4. DeletePassword
## Cleaning up ## Cleaning up
......
...@@ -76,6 +76,39 @@ func createPassword(cli api.DexClient) error { ...@@ -76,6 +76,39 @@ func createPassword(cli api.DexClient) error {
log.Printf("%+v", pass) log.Printf("%+v", pass)
} }
// Verifying correct and incorrect passwords
log.Print("Verifying Password:\n")
verifyReq := &api.VerifyPasswordReq{
Email: "test@example.com",
Password: "test1",
}
verifyResp, err := cli.VerifyPassword(context.TODO(), verifyReq)
if err != nil {
return fmt.Errorf("failed to run VerifyPassword for correct password: %v", err)
}
if !verifyResp.Verified {
return fmt.Errorf("failed to verify correct password: %v", verifyResp)
}
log.Printf("properly verified correct password: %t\n", verifyResp.Verified)
badVerifyReq := &api.VerifyPasswordReq{
Email: "test@example.com",
Password: "wrong_password",
}
badVerifyResp, err := cli.VerifyPassword(context.TODO(), badVerifyReq)
if err != nil {
return fmt.Errorf("failed to run VerifyPassword for incorrect password: %v", err)
}
if badVerifyResp.Verified {
return fmt.Errorf("verify returned true for incorrect password: %v", badVerifyResp)
}
log.Printf("properly failed to verify incorrect password: %t\n", badVerifyResp.Verified)
log.Print("Listing Passwords:\n")
for _, pass := range resp.Passwords {
log.Printf("%+v", pass)
}
deleteReq := &api.DeletePasswordReq{ deleteReq := &api.DeletePasswordReq{
Email: p.Email, Email: p.Email,
} }
......
...@@ -254,6 +254,37 @@ func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*a ...@@ -254,6 +254,37 @@ func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*a
} }
func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) {
if req.Email == "" {
return nil, errors.New("no email supplied")
}
if req.Password == "" {
return nil, errors.New("no password to verify supplied")
}
password, err := d.s.GetPassword(req.Email)
if err != nil {
if err == storage.ErrNotFound {
return &api.VerifyPasswordResp{
NotFound: true,
}, nil
}
d.logger.Errorf("api: there was an error retrieving the password: %v", err)
return nil, fmt.Errorf("verify password: %v", err)
}
if err := bcrypt.CompareHashAndPassword(password.Hash, []byte(req.Password)); err != nil {
d.logger.Info("api: password check failed : %v", err)
return &api.VerifyPasswordResp{
Verified: false,
}, nil
}
return &api.VerifyPasswordResp{
Verified: true,
}, nil
}
func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) { func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) {
id := new(internal.IDTokenSubject) id := new(internal.IDTokenSubject)
if err := internal.Unmarshal(req.UserId, id); err != nil { if err := internal.Unmarshal(req.UserId, id); err != nil {
......
...@@ -69,8 +69,9 @@ func TestPassword(t *testing.T) { ...@@ -69,8 +69,9 @@ func TestPassword(t *testing.T) {
defer client.Close() defer client.Close()
ctx := context.Background() ctx := context.Background()
email := "test@example.com"
p := api.Password{ p := api.Password{
Email: "test@example.com", Email: email,
// bcrypt hash of the value "test1" with cost 10 // bcrypt hash of the value "test1" with cost 10
Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"), Hash: []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"),
Username: "test", Username: "test",
...@@ -93,8 +94,56 @@ func TestPassword(t *testing.T) { ...@@ -93,8 +94,56 @@ func TestPassword(t *testing.T) {
t.Fatalf("Created password %s twice", createReq.Password.Email) t.Fatalf("Created password %s twice", createReq.Password.Email)
} }
// Attempt to verify valid password and email
goodVerifyReq := &api.VerifyPasswordReq{
Email: email,
Password: "test1",
}
goodVerifyResp, err := client.VerifyPassword(ctx, goodVerifyReq)
if err != nil {
t.Fatalf("Unable to run verify password we expected to be valid for correct email: %v", err)
}
if !goodVerifyResp.Verified {
t.Fatalf("verify password failed for password expected to be valid for correct email. expected %t, found %t", true, goodVerifyResp.Verified)
}
if goodVerifyResp.NotFound {
t.Fatalf("verify password failed to return not found response. expected %t, found %t", false, goodVerifyResp.NotFound)
}
// Check not found response for valid password with wrong email
badEmailVerifyReq := &api.VerifyPasswordReq{
Email: "somewrongaddress@email.com",
Password: "test1",
}
badEmailVerifyResp, err := client.VerifyPassword(ctx, badEmailVerifyReq)
if err != nil {
t.Fatalf("Unable to run verify password for incorrect email: %v", err)
}
if badEmailVerifyResp.Verified {
t.Fatalf("verify password passed for password expected to be not found. expected %t, found %t", false, badEmailVerifyResp.Verified)
}
if !badEmailVerifyResp.NotFound {
t.Fatalf("expected not found response for verify password with bad email. expected %t, found %t", true, badEmailVerifyResp.NotFound)
}
// Check that wrong password fails
badPassVerifyReq := &api.VerifyPasswordReq{
Email: email,
Password: "wrong_password",
}
badPassVerifyResp, err := client.VerifyPassword(ctx, badPassVerifyReq)
if err != nil {
t.Fatalf("Unable to run verify password for password we expected to be invalid: %v", err)
}
if badPassVerifyResp.Verified {
t.Fatalf("verify password passed for password we expected to fail. expected %t, found %t", false, badPassVerifyResp.Verified)
}
if badPassVerifyResp.NotFound {
t.Fatalf("did not expect expected not found response for verify password with bad email. expected %t, found %t", false, badPassVerifyResp.NotFound)
}
updateReq := api.UpdatePasswordReq{ updateReq := api.UpdatePasswordReq{
Email: "test@example.com", Email: email,
NewUsername: "test1", NewUsername: "test1",
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment