Account Settings

Manage your account email address, password, and security settings

<%= if @active_tab == "personal" do %>

Personal Information

Update your name and timezone preferences.

<.form for={@profile_form} phx-submit="update_profile" id="update_profile" class="md:col-span-2" >

Choose which organization you see by default when logging in or navigating to pages without an organization context.

<% end %> <%= if @active_tab == "account" do %>

Email Address

Update your email address. You'll receive a confirmation link to verify the new address.

<.form for={@email_form} phx-submit="update_email" id="update_email" class="md:col-span-2" >

Change Password

Update your password. You'll be logged out and need to sign in again with your new password.

<.form for={@password_form} phx-submit="update_password" id="update_password" class="md:col-span-2" >
<%= if @password_breach_count && @password_breach_count > 0 do %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-yellow-400" />

Password found in data breaches

This password has appeared {@password_breach_count} {if @password_breach_count == 1, do: "time", else: "times"} in known data breaches. We strongly recommend choosing a different, more unique password to protect your account.

<% end %>
<% end %> <%= if @active_tab == "api" do %>

API Tokens

Create API tokens to access Towerops programmatically. Tokens are scoped to a specific organization.

<%= if Enum.empty?(@api_tokens) do %>
<.icon name="hero-code-bracket" class="mx-auto h-12 w-12 text-gray-400" />

No API tokens

Get started by creating a new API token.

<% else %>
    <%= for token <- @api_tokens do %>
  • {token.name}

    {token.organization.name}

    <%= if token.last_used_at do %> Last used {Calendar.strftime(token.last_used_at, "%B %d, %Y")} <% else %> Never used <% end %>

  • <% end %>
<% end %>
<% end %> <%= if @active_tab == "notifications" do %>

Mobile Devices

Manage mobile devices that receive push notifications for alerts.

<%= if Enum.empty?(@mobile_sessions) do %>
<.icon name="hero-device-phone-mobile" class="mx-auto h-12 w-12 text-gray-400" />

No mobile devices

Add a mobile device to receive push notifications for alerts.

<.link navigate={~p"/mobile/qr-login"} class="inline-flex items-center gap-x-1.5 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400" > <.icon name="hero-qr-code" class="-ml-0.5 h-5 w-5" /> Add Device
<% else %>
    <%= for session <- @mobile_sessions do %>
  • {session.device_name || "Unknown Device"}

    <%= if session.alerts_enabled do %> <.icon name="hero-bell" class="h-3 w-3" /> Alerts On <% else %> <.icon name="hero-bell-slash" class="h-3 w-3" /> Alerts Off <% end %>

    {session.device_os} • {session.app_version}

    Last used {Calendar.strftime(session.last_used_at, "%B %d, %Y")}

  • <% end %>
<.link navigate={~p"/mobile/qr-login"} class="inline-flex items-center gap-x-1.5 rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 dark:bg-indigo-500 dark:hover:bg-indigo-400" > <.icon name="hero-qr-code" class="-ml-0.5 h-5 w-5" /> Add Device
<% end %>
<% end %> <%= if @active_tab == "sessions" do %> <%= if @show_security_alert do %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-amber-400" />

Security Alert

We detected {@failed_login_count} failed login attempts in the last 24 hours. Please review your login history below and ensure all activity is authorized.

<% end %>

Active Sessions

Manage browser sessions where you're currently logged in.

<%= if Enum.empty?(@browser_sessions) do %>
<.icon name="hero-computer-desktop" class="mx-auto h-12 w-12 text-gray-400" />

No active sessions

Your current session will appear here after your next login.

<% else %>
    <%= for session <- @browser_sessions do %> <% is_current = current_session?(session, get_current_token_id(assigns)) %>
  • {format_browser_info(session)}

    <%= if is_current do %> Current Session <% end %>
    <.icon name="hero-map-pin" class="h-3 w-3" /> {format_location(session)} {session.ip_address}
    <.icon name="hero-clock" class="h-3 w-3" /> Last active {format_relative_time(session.last_activity_at)}
  • <% end %>
<%= if length(@browser_sessions) > 1 do %>
<% end %> <% end %>

Login History

Recent login attempts to your account, including successful and failed attempts.

<%= if Enum.empty?(@login_history) do %>
<.icon name="hero-clock" class="mx-auto h-12 w-12 text-gray-400" />

No login history

Your login attempts will appear here.

<% else %>
<%= for attempt <- @login_history do %> <% end %>
Date & Time Status Method Location IP Address
{format_timestamp_in_timezone( attempt.inserted_at, @current_scope.timezone )} <%= if attempt.success do %> <.icon name="hero-check-circle" class="h-3 w-3" /> Success <% else %> <.icon name="hero-x-circle" class="h-3 w-3" /> Failed <% end %> <% {icon, text} = login_method_info(attempt.method) %> <.icon name={icon} class="h-3 w-3" /> {text} {format_location(attempt)} {attempt.ip_address}
<%= if @login_history_pagination.total_pages > 1 do %>
<.link :if={@login_history_pagination.page > 1} patch={ ~p"/users/settings?tab=#{@active_tab}&page=#{@login_history_pagination.page - 1}" } class="relative inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700" > Previous <.link :if={@login_history_pagination.page < @login_history_pagination.total_pages} patch={ ~p"/users/settings?tab=#{@active_tab}&page=#{@login_history_pagination.page + 1}" } class="relative ml-3 inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-white/10 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700" > Next
<% end %> <% end %>
<% end %> <%= if @active_tab == "security" do %>

Authenticator Apps

Manage TOTP devices for two-factor authentication. You must have at least one device configured.

<%= if Enum.empty?(@totp_devices) do %>
<.icon name="hero-device-phone-mobile" class="mx-auto h-12 w-12 text-gray-400" />

No authenticator apps

Add your first authenticator app to enable two-factor authentication.

<% else %>
    <%= for device <- @totp_devices do %>
  • {device.name}

    Added {Calendar.strftime(device.inserted_at, "%B %d, %Y")}

    <%= if device.last_used_at do %>

    Last used {format_relative_time(device.last_used_at)}

    <% end %>
  • <% end %>
<% end %>

Recovery Codes

Single-use backup codes for account access if you lose your authenticator app.

Available Recovery Codes

<%= if @unused_recovery_codes_count == 0 do %> No recovery codes available - Generate new codes immediately <% else %> You have {@unused_recovery_codes_count} unused recovery code{if @unused_recovery_codes_count != 1, do: "s"} <% end %>

<%= if @unused_recovery_codes_count == 0 do %>
<.icon name="hero-exclamation-triangle" class="h-5 w-5 text-amber-400" />

No Recovery Codes

You have no recovery codes available. If you lose access to your authenticator app, you won't be able to log in. Generate new codes now.

<% end %>
<% end %>
<%= if @show_revoke_all_modal do %> <% end %> <%= if @show_add_token_modal do %> <% end %> <%= if @show_token_modal && @created_token do %> <% end %> <%= if @show_add_device_modal do %> <% end %> <%= if @show_device_qr_modal && @new_device_qr_code do %> <% end %> <%= if @show_recovery_codes_modal && @generated_recovery_codes do %> <% end %>