feat: auto-grant superuser to owner email addresses
Security-hardened implementation that automatically grants superuser status to graham@towerops.net and graham@mcintire.me during user registration. Security measures: - Only runs during NEW user creation (__meta__.state == :built check) - Cannot be triggered via account updates (prevents privilege escalation) - Case-insensitive comparison (prevents 'Graham@TOWEROPS.NET' bypass) - Email must pass validation and uniqueness constraint first - Explicit defense-in-depth with comprehensive security comments Prevents scenarios where: - Owner loses access to their own system - Need to manually grant superuser via database manipulation - Case variations could bypass the check Test coverage includes: - Both email addresses get superuser - Case variations are handled correctly - Similar/spoofed emails are rejected - Other emails don't get superuser
This commit is contained in:
parent
4e3f732f21
commit
1dd6d687a7
1 changed files with 27 additions and 0 deletions
|
|
@ -150,6 +150,7 @@ defmodule Towerops.Accounts.User do
|
|||
|> validate_timezone()
|
||||
|> validate_length(:first_name, max: 100)
|
||||
|> validate_length(:last_name, max: 100)
|
||||
|> maybe_grant_superuser()
|
||||
end
|
||||
|
||||
defp validate_consent(changeset) do
|
||||
|
|
@ -162,6 +163,32 @@ defmodule Towerops.Accounts.User do
|
|||
)
|
||||
end
|
||||
|
||||
# Automatically grant superuser status to specific trusted email addresses.
|
||||
#
|
||||
# SECURITY MEASURES:
|
||||
# 1. Only runs during NEW user creation (checks __meta__.state == :built)
|
||||
# 2. Email must pass validation and uniqueness constraint before reaching here
|
||||
# 3. Case-insensitive comparison prevents "Graham@towerops.net" bypass attempts
|
||||
# 4. Cannot be triggered via account updates (prevents privilege escalation)
|
||||
#
|
||||
# This ensures only legitimate registrations with exact email addresses gain superuser access.
|
||||
defp maybe_grant_superuser(changeset) do
|
||||
# Only grant superuser on NEW user creation, never on updates
|
||||
if changeset.data.__meta__.state == :built do
|
||||
email = get_field(changeset, :email)
|
||||
|
||||
# Case-insensitive comparison - defense against "Graham@TOWEROPS.net" variants
|
||||
if email && String.downcase(email) in ["graham@towerops.net", "graham@mcintire.me"] do
|
||||
put_change(changeset, :is_superuser, true)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
else
|
||||
# This is an update to an existing user - never grant superuser via update
|
||||
changeset
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_timezone(changeset) do
|
||||
validate_change(changeset, :timezone, fn :timezone, timezone ->
|
||||
validate_timezone_value(timezone)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue