This commit is contained in:
parent
8e5524de57
commit
bd79a9b9e1
13 changed files with 1321 additions and 174 deletions
|
|
@ -322,7 +322,7 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "user2@example.com"
|
||||
})
|
||||
|> Ash.Changeset.change_attribute(:oidc_id, "shared_oidc_333")
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "shared_oidc_333")
|
||||
|> Ash.create()
|
||||
|
||||
# Should fail due to unique constraint on oidc_id
|
||||
|
|
@ -335,4 +335,162 @@ defmodule MvWeb.OidcPasswordLinkingTest do
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "OIDC login with passwordless user - Requires Linking Flow" do
|
||||
test "user without password and without oidc_id triggers PasswordVerificationRequired" do
|
||||
# Create user without password (e.g., invited user)
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "invited@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# Verify user has no password and no oidc_id
|
||||
assert is_nil(existing_user.hashed_password)
|
||||
assert is_nil(existing_user.oidc_id)
|
||||
|
||||
# OIDC registration should trigger linking flow (not automatic)
|
||||
user_info = %{
|
||||
"sub" => "auto_link_oidc_123",
|
||||
"preferred_username" => "invited@example.com"
|
||||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
|
||||
# Should fail with PasswordVerificationRequired
|
||||
# The LinkOidcAccountLive will auto-link without password prompt
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
{:error, error} = result
|
||||
|
||||
assert Enum.any?(error.errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
end
|
||||
|
||||
test "user without password but WITH password later requires verification" do
|
||||
# Create user without password first
|
||||
{:ok, user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "added-password@example.com"
|
||||
})
|
||||
|> Ash.create()
|
||||
|
||||
# User sets password later (using admin action)
|
||||
{:ok, user_with_password} =
|
||||
user
|
||||
|> Ash.Changeset.for_update(:admin_set_password, %{
|
||||
password: "newpassword123"
|
||||
})
|
||||
|> Ash.update()
|
||||
|
||||
assert not is_nil(user_with_password.hashed_password)
|
||||
|
||||
# Now OIDC login should require password verification
|
||||
user_info = %{
|
||||
"sub" => "needs_verification",
|
||||
"preferred_username" => "added-password@example.com"
|
||||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
|
||||
# Should fail with PasswordVerificationRequired
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
{:error, error} = result
|
||||
|
||||
assert Enum.any?(error.errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "OIDC login with different oidc_id - Hard Error" do
|
||||
test "user with different oidc_id cannot be linked (hard error)" do
|
||||
# Create user with existing OIDC ID
|
||||
{:ok, existing_user} =
|
||||
Mv.Accounts.User
|
||||
|> Ash.Changeset.for_create(:create_user, %{
|
||||
email: "already-linked@example.com"
|
||||
})
|
||||
|> Ash.Changeset.force_change_attribute(:oidc_id, "original_oidc_999")
|
||||
|> Ash.create()
|
||||
|
||||
assert existing_user.oidc_id == "original_oidc_999"
|
||||
|
||||
# Try to register with same email but different OIDC ID
|
||||
user_info = %{
|
||||
"sub" => "different_oidc_888",
|
||||
"preferred_username" => "already-linked@example.com"
|
||||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
|
||||
# Should fail with hard error (not PasswordVerificationRequired)
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
{:error, error} = result
|
||||
|
||||
# Should NOT be PasswordVerificationRequired
|
||||
refute Enum.any?(error.errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
|
||||
# Should be a validation error about email already linked
|
||||
assert Enum.any?(error.errors, fn err ->
|
||||
case err do
|
||||
%Ash.Error.Changes.InvalidAttribute{message: msg} ->
|
||||
String.contains?(msg, "already linked to a different OIDC account")
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
test "cannot link different oidc_id even with password verification" do
|
||||
# Create user with password AND existing OIDC ID
|
||||
existing_user =
|
||||
create_test_user(%{
|
||||
email: "password-and-oidc@example.com",
|
||||
password: "mypassword123",
|
||||
oidc_id: "first_oidc_111"
|
||||
})
|
||||
|
||||
assert existing_user.oidc_id == "first_oidc_111"
|
||||
assert not is_nil(existing_user.hashed_password)
|
||||
|
||||
# Try to register with different OIDC ID
|
||||
user_info = %{
|
||||
"sub" => "second_oidc_222",
|
||||
"preferred_username" => "password-and-oidc@example.com"
|
||||
}
|
||||
|
||||
result =
|
||||
Mv.Accounts.create_register_with_rauthy(%{
|
||||
user_info: user_info,
|
||||
oauth_tokens: %{"access_token" => "test_token"}
|
||||
})
|
||||
|
||||
# Should fail - cannot link different OIDC ID
|
||||
assert {:error, %Ash.Error.Invalid{}} = result
|
||||
{:error, error} = result
|
||||
|
||||
# Should be a hard error, not password verification
|
||||
refute Enum.any?(error.errors, fn err ->
|
||||
match?(%Mv.Accounts.User.Errors.PasswordVerificationRequired{}, err)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue