mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-05 09:04:09 -04:00
Local user password change (embedded IdP) (#5132)
This commit is contained in:
@@ -400,7 +400,6 @@ func (m *EmbeddedIdPManager) CreateUserWithPassword(ctx context.Context, email,
|
||||
|
||||
// InviteUserByID resends an invitation to a user.
|
||||
func (m *EmbeddedIdPManager) InviteUserByID(ctx context.Context, userID string) error {
|
||||
// TODO: implement
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
@@ -432,6 +431,33 @@ func (m *EmbeddedIdPManager) DeleteUser(ctx context.Context, userID string) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUserPassword updates the password for a user in the embedded IdP.
|
||||
// It verifies that the current user is changing their own password and
|
||||
// validates the current password before updating to the new password.
|
||||
func (m *EmbeddedIdPManager) UpdateUserPassword(ctx context.Context, currentUserID, targetUserID string, oldPassword, newPassword string) error {
|
||||
// Verify the user is changing their own password
|
||||
if currentUserID != targetUserID {
|
||||
return fmt.Errorf("users can only change their own password")
|
||||
}
|
||||
|
||||
// Verify the new password is different from the old password
|
||||
if oldPassword == newPassword {
|
||||
return fmt.Errorf("new password must be different from current password")
|
||||
}
|
||||
|
||||
err := m.provider.UpdateUserPassword(ctx, targetUserID, oldPassword, newPassword)
|
||||
if err != nil {
|
||||
if m.appMetrics != nil {
|
||||
m.appMetrics.IDPMetrics().CountRequestError()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithContext(ctx).Debugf("updated password for user %s in embedded IdP", targetUserID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateConnector creates a new identity provider connector in Dex.
|
||||
// Returns the created connector config with the redirect URL populated.
|
||||
func (m *EmbeddedIdPManager) CreateConnector(ctx context.Context, cfg *dex.ConnectorConfig) (*dex.ConnectorConfig, error) {
|
||||
@@ -449,15 +475,8 @@ func (m *EmbeddedIdPManager) ListConnectors(ctx context.Context) ([]*dex.Connect
|
||||
}
|
||||
|
||||
// UpdateConnector updates an existing identity provider connector.
|
||||
// Field preservation for partial updates is handled by Provider.UpdateConnector.
|
||||
func (m *EmbeddedIdPManager) UpdateConnector(ctx context.Context, cfg *dex.ConnectorConfig) error {
|
||||
// Preserve existing secret if not provided in update
|
||||
if cfg.ClientSecret == "" {
|
||||
existing, err := m.provider.GetConnector(ctx, cfg.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get existing connector: %w", err)
|
||||
}
|
||||
cfg.ClientSecret = existing.ClientSecret
|
||||
}
|
||||
return m.provider.UpdateConnector(ctx, cfg)
|
||||
}
|
||||
|
||||
|
||||
@@ -248,6 +248,71 @@ func TestEmbeddedIdPManager_UserIDFormat_MatchesJWT(t *testing.T) {
|
||||
t.Logf(" Connector: %s", connectorID)
|
||||
}
|
||||
|
||||
func TestEmbeddedIdPManager_UpdateUserPassword(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tmpDir, err := os.MkdirTemp("", "embedded-idp-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
config := &EmbeddedIdPConfig{
|
||||
Enabled: true,
|
||||
Issuer: "http://localhost:5556/dex",
|
||||
Storage: EmbeddedStorageConfig{
|
||||
Type: "sqlite3",
|
||||
Config: EmbeddedStorageTypeConfig{
|
||||
File: filepath.Join(tmpDir, "dex.db"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
manager, err := NewEmbeddedIdPManager(ctx, config, nil)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = manager.Stop(ctx) }()
|
||||
|
||||
// Create a user with a known password
|
||||
email := "password-test@example.com"
|
||||
name := "Password Test User"
|
||||
initialPassword := "InitialPass123!"
|
||||
|
||||
userData, err := manager.CreateUserWithPassword(ctx, email, initialPassword, name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, userData)
|
||||
|
||||
userID := userData.ID
|
||||
|
||||
t.Run("successful password change", func(t *testing.T) {
|
||||
newPassword := "NewSecurePass456!"
|
||||
err := manager.UpdateUserPassword(ctx, userID, userID, initialPassword, newPassword)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the new password works by changing it again
|
||||
anotherPassword := "AnotherPass789!"
|
||||
err = manager.UpdateUserPassword(ctx, userID, userID, newPassword, anotherPassword)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("wrong old password", func(t *testing.T) {
|
||||
err := manager.UpdateUserPassword(ctx, userID, userID, "wrongpassword", "NewPass123!")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "current password is incorrect")
|
||||
})
|
||||
|
||||
t.Run("cannot change other user password", func(t *testing.T) {
|
||||
otherUserID := "other-user-id"
|
||||
err := manager.UpdateUserPassword(ctx, userID, otherUserID, "oldpass", "newpass")
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "users can only change their own password")
|
||||
})
|
||||
|
||||
t.Run("same password rejected", func(t *testing.T) {
|
||||
samePassword := "SamePass123!"
|
||||
err := manager.UpdateUserPassword(ctx, userID, userID, samePassword, samePassword)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "new password must be different")
|
||||
})
|
||||
}
|
||||
|
||||
func TestEmbeddedIdPManager_GetLocalKeysLocation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user