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

Prevent deleting last passwordless device with no password #47592

Merged
merged 1 commit into from
Oct 16, 2024
Merged
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: 2 additions & 2 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3888,11 +3888,11 @@ func (a *Server) deleteMFADeviceSafely(ctx context.Context, user, deviceName str
// Check whether the device to delete is the last passwordless device,
// and whether deleting it would lockout the user from login.
//
// TODO(Joerger): the user may already be locked out from login if a password
// Note: the user may already be locked out from login if a password
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you said, it's a pretty weird edge case, but I think I'm OK with the if essentially saying "is last passkey && has no password".

// is not set and passwordless is disabled. Prevent them from deleting
// their last passkey to prevent them from being locked out further,
// in the case of passwordless being re-enabled.
if isPasskey(deviceToDelete) && remainingPasskeys == 0 && readOnlyAuthPref.GetAllowPasswordless() {
if isPasskey(deviceToDelete) && remainingPasskeys == 0 {
u, err := a.Services.GetUser(ctx, user, false /* withSecrets */)
if err != nil {
return nil, trace.Wrap(err)
Expand Down
44 changes: 23 additions & 21 deletions lib/auth/grpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,27 +464,6 @@ func TestDeletingLastPasswordlessDevice(t *testing.T) {
)
},
},
{
// TODO(Joerger): the user may already be locked out from login if a password
// is not set and passwordless is disabled. Prevent them from deleting
// their last passkey to prevent them from being locked out further,
// in the case of passwordless being re-enabled.
name: "OK other MFAs, but no password set, passwordless is off",
setup: func(t *testing.T, _ string, userClient *authclient.Client, pwdlessDev *TestDevice) {
// Register a non-passwordless device without adding a password.
_, err := RegisterTestDevice(ctx, userClient, "another-dev", proto.DeviceType_DEVICE_TYPE_TOTP, pwdlessDev, WithTestDeviceClock(clock))
require.NoError(t, err, "RegisterTestDevice")

authPref, err := authServer.GetAuthPreference(ctx)
require.NoError(t, err, "GetAuthPreference")

// Turn off passwordless authentication.
authPref.SetAllowPasswordless(false)
_, err = authServer.UpsertAuthPreference(ctx, authPref)
require.NoError(t, err, "UpsertAuthPreference")
},
checkErr: require.NoError,
},
{
name: "OK extra passwordless device",
setup: func(t *testing.T, username string, userClient *authclient.Client, pwdlessDev *TestDevice) {
Expand Down Expand Up @@ -560,6 +539,29 @@ func TestDeletingLastPasswordlessDevice(t *testing.T) {
)
},
},
{
name: "NOK other MFAs, but no password set, passwordless is off",
setup: func(t *testing.T, _ string, userClient *authclient.Client, pwdlessDev *TestDevice) {
// Register a non-passwordless device without adding a password.
_, err := RegisterTestDevice(ctx, userClient, "another-dev", proto.DeviceType_DEVICE_TYPE_TOTP, pwdlessDev, WithTestDeviceClock(clock))
require.NoError(t, err, "RegisterTestDevice")

authPref, err := authServer.GetAuthPreference(ctx)
require.NoError(t, err, "GetAuthPreference")

// Turn off passwordless authentication.
authPref.SetAllowPasswordless(false)
_, err = authServer.UpsertAuthPreference(ctx, authPref)
require.NoError(t, err, "UpsertAuthPreference")
},
checkErr: func(t require.TestingT, err error, _ ...any) {
require.ErrorContains(t,
err,
"cannot delete last passwordless credential for user",
"Unexpected error deleting last passwordless device",
)
},
},
}

for i, test := range tests {
Expand Down
Loading