Skip to content

2676 prevent higher access users from changing email to personal#3302

Merged
entrotech merged 48 commits into
developfrom
2676-prevent-higher-access-users-from-changing-email-to-personal
Jul 21, 2026
Merged

2676 prevent higher access users from changing email to personal#3302
entrotech merged 48 commits into
developfrom
2676-prevent-higher-access-users-from-changing-email-to-personal

Conversation

@arshiamasih

@arshiamasih arshiamasih commented Jun 13, 2026

Copy link
Copy Markdown
Member

What changes did you make?

The original PR issue was to limit admin users from updating their email to personal email address. However there were a few other issues in account updates that were coupled closely enough to be handled in this PR.

  • adds a domain whitelist to restrict admin/security admin users from updating email to non approved domains
  • fixes errors to handle case for existing emails failure
  • adds explicit error handling for email verification confirmation errors
  • adds integration testing for updateAccount to check for standard user versus admin user personal email updates

Why did you make the changes (we will use this info to test)?

  • To reduce the chance that an LA City employee will retain access to admin or security permissions after leaving the city.

Screenshots of Proposed Changes Of The Website (if any, please do not screen shot code changes)

Visuals before changes are applied

Admin attempt to change to personal email - SUCCEEDS

change in PR: blocks update requests to non-approved email domains

image

image

Security attempt to change to personal email - SUCCEEDS

change in PR: blocks update requests to non-approved email domains

image

image

develop-security-peronal-success.mov

Attempt to change to another existing email - BLOCKED with a catch all error message

change in PR: the error was caught in a generic catch all error message instead of the explicit "existing email" as the back end response code did not match the front end error handling

image

SMTP / TDM Google mailer failure - vague error message:

image

Visuals after changes are applied

Security Admin change email actions:

Security Admin attempt to change to personal email - BLOCKED with explicit error message

image

Database NO update on personal email change attempt (DBeaver):
https://github.com/user-attachments/assets/bee5c88c-2288-4dcc-b169-2a10978f03b4

Security Admin attempt to change to existing email - BLOCKED with correct explicit error message

image

Database NO update on existing email change attempt (DBeaver):
https://github.com/user-attachments/assets/055bc5fa-5a2d-4dac-8c3c-40a3c979d52c

Security success on allowed email domain - SUCCESS confirmation:

image

image

Database success update on approved email domain (DBeaver):
image

Successful login with changed email (confirming no breaking changes to login flow):

Screen.Recording.2026-07-09.at.11.36.37.AM.mov

Admin change email actions:

Admin attempt to change to personal email - BLOCKED with explicit error message

image

Admin attempt to change to existing email - BLOCKED with correct explicit error message

image

Admin success on allowed email domain - SUCCESS confirmation:

image

image

Database update (DBeaver):
image

Non-admin change personal email action - NOT BLOCKED:

image

image

SMTP / TDM Google mailer failure - added explicit error message

image

@arshiamasih
arshiamasih marked this pull request as draft June 13, 2026 00:26
@arshiamasih
arshiamasih marked this pull request as ready for review June 14, 2026 20:28
@arshiamasih
arshiamasih requested a review from entrotech June 14, 2026 21:42
@arshiamasih arshiamasih added role: back-end Node/Express Development Task level: medium labels Jun 14, 2026
@arshiamasih
arshiamasih marked this pull request as draft June 21, 2026 20:17
@arshiamasih
arshiamasih marked this pull request as ready for review June 30, 2026 19:02

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a placeholder config file until we move domain lists to .env file.

Another option could be to move these lists to the database - persisting in DB seems a bit over engineered for the currents needs imo, but I am open to considering if there are preferences towards it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to put the list of domains in the .env file, so they can be altered as necessary by the devops people - who will be able to change runtime environment variable settings without having to edit the database or code. I'll do this as a small change to your PR, and update the .env contents settings in the 1Passaword vault(s).

Comment on lines +135 to +139
const user = await selectById(model.id);
if (!user) {
const error = new Error("User record not found.");
error.code = "USER_NOT_FOUND";
throw error;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the user sessions is accessible from the middleware layer so perhaps this is redundant but also seems more secure.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to check to make sure the user object from the middleware has the same structure as the user object from 'selectById', but it probably does., so it should not make a difference where you get the user info. The middleware gets it from the seure JWT header in the HTTP request so it better be secure, or we have bigger problems. I'm ok with what you did here.

Comment on lines +107 to +119

const validateUniqueEmail = async (email, currentUserId) => {
const trimmedEmail = email.toLowerCase().trim();
const existingEmailCheck = await selectByEmail(trimmedEmail);

if (existingEmailCheck && existingEmailCheck.id !== currentUserId) {
const error = new Error(
`The email ${email} is already in use by another account.`
);
error.code = "ERR_DUPLICATE_EMAIL";
throw error;
}
};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Login_Update stored procedure also checks for email uniqueness by looking at the login table, but imo we should be validating reqs upfront before we attempt a DB update- the stored procedure then acts as a secondary safeguard check. LMK what you think.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good.

@entrotech entrotech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to make a few small changes to the PR before merging it, so the domain list comes from the .env file as you suggested, but it seems to be working well and gives good error messages when they try to enter a duplicate email or use a restricted domain.

We will need to create another issue to prevent someone who has security admin rights from granting roles to a user from the security admin page if their user name is not associated with an allowed domain.

Comment on lines +107 to +119

const validateUniqueEmail = async (email, currentUserId) => {
const trimmedEmail = email.toLowerCase().trim();
const existingEmailCheck = await selectByEmail(trimmedEmail);

if (existingEmailCheck && existingEmailCheck.id !== currentUserId) {
const error = new Error(
`The email ${email} is already in use by another account.`
);
error.code = "ERR_DUPLICATE_EMAIL";
throw error;
}
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good.

Comment on lines +107 to +119

const validateUniqueEmail = async (email, currentUserId) => {
const trimmedEmail = email.toLowerCase().trim();
const existingEmailCheck = await selectByEmail(trimmedEmail);

if (existingEmailCheck && existingEmailCheck.id !== currentUserId) {
const error = new Error(
`The email ${email} is already in use by another account.`
);
error.code = "ERR_DUPLICATE_EMAIL";
throw error;
}
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good.

Comment on lines +135 to +139
const user = await selectById(model.id);
if (!user) {
const error = new Error("User record not found.");
error.code = "USER_NOT_FOUND";
throw error;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have to check to make sure the user object from the middleware has the same structure as the user object from 'selectById', but it probably does., so it should not make a difference where you get the user info. The middleware gets it from the seure JWT header in the HTTP request so it better be secure, or we have bigger problems. I'm ok with what you did here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to put the list of domains in the .env file, so they can be altered as necessary by the devops people - who will be able to change runtime environment variable settings without having to edit the database or code. I'll do this as a small change to your PR, and update the .env contents settings in the 1Passaword vault(s).

@entrotech
entrotech merged commit fd01290 into develop Jul 21, 2026
7 checks passed
@entrotech
entrotech deleted the 2676-prevent-higher-access-users-from-changing-email-to-personal branch July 21, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

level: medium role: back-end Node/Express Development Task

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev: Limit Users with Higher Level Access from Changing Emails to Personal Email Accounts

2 participants