refactor and docs
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Moritz 2025-11-06 14:02:29 +01:00
parent 8e5524de57
commit bd79a9b9e1
Signed by: moritz
GPG key ID: 1020A035E5DD0824
13 changed files with 1321 additions and 174 deletions

View file

@ -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