Skip to content

Commit

Permalink
[identity] configurable no password authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
patatoid committed Nov 30, 2024
1 parent b69fcc7 commit bc93718
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
<section v-if="identityProvider.isPersisted">
<h3>Sessions</h3>
<div class="ui segment">
<div class="ui toggle checkbox">
<input type="checkbox" v-model="identityProvider.check_password">
<label>check password</label>
</div>
<router-link
:to="{ name: 'edit-session-template', params: { identityProviderId: identityProvider.id } }"
class="ui fluid blue button">Edit login template</router-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const assign = {
type: function ({ type }) { this.type = type },
backend: function ({ backend }) { this.backend = new Backend(backend) },
backend_id: function ({ backend_id }) { this.backend_id = backend_id },
check_password: function ({ check_password }) { this.check_password = check_password },
choose_session: function ({ choose_session }) { this.choose_session = choose_session },
totpable: function ({ totpable }) { this.totpable = totpable },
enforce_totp: function ({ enforce_totp }) { this.enforce_totp = enforce_totp },
Expand Down Expand Up @@ -82,6 +83,7 @@ class IdentityProvider {
id,
name,
backend_id,
check_password,
choose_session,
totpable,
enforce_totp,
Expand All @@ -97,6 +99,7 @@ class IdentityProvider {
id,
name,
backend_id,
check_password,
choose_session,
user_editable,
totpable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ defmodule BorutaAdminWeb.IdentityProviderView do
name: identity_provider.name,
backend: render_one(identity_provider.backend, BackendView, "backend.json", backend: identity_provider.backend),
backend_id: identity_provider.backend_id,
check_password: identity_provider.check_password,
choose_session: identity_provider.choose_session,
totpable: identity_provider.totpable,
enforce_totp: identity_provider.enforce_totp,
Expand Down
51 changes: 40 additions & 11 deletions apps/boruta_identity/lib/boruta_identity/accounts/sessions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ defmodule BorutaIdentity.Accounts.Sessions do
) ::
{:ok, user :: User.t()} | {:error, reason :: String.t()}

# NOTE duplicate of BorutaIdentity.Accounts.Registrations
# @callback register(
# backend :: BorutaIdentity.IdentityProviders.Backend.t(),
# registration_params :: registration_params()
# ) ::
# {:ok, user :: User.t()}
# | {:error, changeset :: Ecto.Changeset.t()}

@spec initialize_session(
context :: any(),
client_id :: String.t(),
Expand All @@ -95,17 +103,8 @@ defmodule BorutaIdentity.Accounts.Sessions do
module :: atom()
) :: callback_result :: any()
defwithclientidp create_session(context, client_id, authentication_params, module) do
client_impl = IdentityProvider.implementation(client_idp)

with {:ok, user} <-
apply(client_impl, :get_user, [client_idp.backend, authentication_params]),
{:ok, user} <-
apply(client_impl, :check_user_against, [
client_idp.backend,
user,
authentication_params
]),
%User{} = user <- apply(client_impl, :domain_user!, [user, client_idp.backend]),
with {:ok, user} <- get_user(authentication_params, client_idp),
{:ok, user} <- maybe_check_password(user, authentication_params, client_idp),
:ok <- ensure_user_confirmed(user, client_idp),
{:ok, user, session_token} <- create_user_session(user) do
module.user_authenticated(context, user, session_token)
Expand All @@ -124,6 +123,36 @@ defmodule BorutaIdentity.Accounts.Sessions do
end
end

def get_user(%{email: email}, %IdentityProvider{check_password: false} = client_idp) do
client_impl = IdentityProvider.implementation(client_idp)

apply(client_impl, :register, [client_idp.backend, %{
email: email,
password: SecureRandom.hex(),
metadata: %{"check_password" => %{"value" => false, "display" => [], "status" => "valid"}}
}])
end

def get_user(authentication_params, %IdentityProvider{check_password: true} = client_idp) do
client_impl = IdentityProvider.implementation(client_idp)

apply(client_impl, :get_user, [client_idp.backend, authentication_params])
end

def maybe_check_password(user, _authentication_params, %IdentityProvider{check_password: false}), do: {:ok, user}

def maybe_check_password(user, authentication_params, %IdentityProvider{backend: backend, check_password: true} = client_idp) do
client_impl = IdentityProvider.implementation(client_idp)

with {:ok, user} <- apply(client_impl, :check_user_against, [
backend,
user,
authentication_params
]) do
{:ok, apply(client_impl, :domain_user!, [user, client_idp.backend])}
end
end

@spec delete_session(
context :: any(),
client_id :: String.t(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ defmodule BorutaIdentity.IdentityProviders.IdentityProvider do
enforce_totp: boolean(),
confirmable: boolean(),
authenticable: boolean(),
check_password: boolean(),
reset_password: boolean(),
client_identity_providers:
list(ClientIdentityProvider.t()) | Ecto.Association.NotLoaded.t(),
Expand Down Expand Up @@ -112,6 +113,7 @@ defmodule BorutaIdentity.IdentityProviders.IdentityProvider do
field(:confirmable, :boolean, default: false)
field(:consentable, :boolean, default: false)
field(:authenticable, :boolean, default: true, virtual: true)
field(:check_password, :boolean, default: true)
field(:reset_password, :boolean, default: true, virtual: true)

has_many(:client_identity_providers, ClientIdentityProvider)
Expand Down Expand Up @@ -174,6 +176,7 @@ defmodule BorutaIdentity.IdentityProviders.IdentityProvider do
|> cast(attrs, [
:id,
:name,
:check_password,
:choose_session,
:totpable,
:enforce_totp,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule BorutaIdentity.Repo.Migrations.AddCheckPasswordToIdentityProviders do
use Ecto.Migration

def change do
alter table(:identity_providers) do
add :check_password, :boolean, null: false, default: true
end
end
end
33 changes: 32 additions & 1 deletion apps/boruta_identity/test/boruta_identity/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -543,13 +543,21 @@ defmodule BorutaIdentity.AccountsTest do
identity_provider: insert(:identity_provider, confirmable: true)
)

no_password_client_identity_provider =
BorutaIdentity.Factory.insert(
:client_identity_provider,
identity_provider: insert(:identity_provider, check_password: false)
)

client_identity_provider = BorutaIdentity.Factory.insert(:client_identity_provider)

{:ok,
backend: client_identity_provider.identity_provider.backend,
client_id: client_identity_provider.client_id,
confirmable_backend: confirmable_client_identity_provider.identity_provider.backend,
confirmable_client_id: confirmable_client_identity_provider.client_id}
confirmable_client_id: confirmable_client_identity_provider.client_id,
no_password_backend: no_password_client_identity_provider.identity_provider.backend,
no_password_client_id: no_password_client_identity_provider.client_id}
end

test "returns an error with nil client_id" do
Expand Down Expand Up @@ -719,6 +727,29 @@ defmodule BorutaIdentity.AccountsTest do
assert session_token
end

test "authenticates the user with no password", %{no_password_client_id: client_id, no_password_backend: backend} do
context = :context
username = "[email protected]"
authentication_params = %{email: username}

assert {:user_authenticated, ^context,
%User{
username: ^username,
backend: ^backend,
last_login_at: last_login_at
},
session_token} =
Accounts.create_session(
context,
client_id,
authentication_params,
DummySession
)

assert last_login_at
assert session_token
end

test "does not create multiple users accross multiple authentications", %{
client_id: client_id,
backend: backend
Expand Down

0 comments on commit bc93718

Please sign in to comment.