Fix dev-login role switching, distinct users, and user resolution races

Dev-login now updates the role when an existing user logs in as a
different role. Each dev role maps to a distinct email/name so
sessions don't collide. CognitoSub is deterministic (email-based)
to prevent mismatch between dev-login and CurrentUserService.
CurrentUserService catches DbUpdateException on concurrent user
creation and retries the lookup instead of crashing.
This commit is contained in:
Adam Moussa 2026-05-20 17:24:01 -04:00
parent f44caba906
commit f37c2aa3f0
3 changed files with 25 additions and 3 deletions

View file

@ -107,7 +107,7 @@ public class AuthController : ControllerBase
user = new User user = new User
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
CognitoSub = $"dev-{Guid.NewGuid():N}", CognitoSub = $"dev-{request.Email}",
Email = request.Email, Email = request.Email,
DisplayName = request.DisplayName ?? request.Email.Split('@')[0], DisplayName = request.DisplayName ?? request.Email.Split('@')[0],
Role = role, Role = role,
@ -118,6 +118,12 @@ public class AuthController : ControllerBase
_db.Users.Add(user); _db.Users.Add(user);
await _db.SaveChangesAsync(ct); await _db.SaveChangesAsync(ct);
} }
else if (user.Role != role)
{
user.Role = role;
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync(ct);
}
var claims = new List<Claim> var claims = new List<Claim>
{ {

View file

@ -80,7 +80,16 @@ public class CurrentUserService : ICurrentUserService
}; };
_db.Users.Add(_cachedUser); _db.Users.Add(_cachedUser);
await _db.SaveChangesAsync(); try
{
await _db.SaveChangesAsync();
}
catch (DbUpdateException)
{
_db.Entry(_cachedUser).State = EntityState.Detached;
_cachedUser = await _db.Users.FirstOrDefaultAsync(u => u.Email == email)
?? throw new UnauthorizedAccessException("Could not resolve current user");
}
} }
private User GetOrThrow() private User GetOrThrow()

View file

@ -39,10 +39,17 @@ export default function LoginPage() {
} }
}, [isAuthenticated, navigate]); }, [isAuthenticated, navigate]);
const devUsers: Record<string, { email: string; name: string }> = {
SysAdmin: { email: 'adam@seahavenind.com', name: 'Adam Moussa' },
Admin: { email: 'sarah@seahavenind.com', name: 'Sarah Chen' },
Dispatcher: { email: 'mike@seahavenind.com', name: 'Mike Torres' },
};
const handleDevLogin = async (role: string) => { const handleDevLogin = async (role: string) => {
setLoading(true); setLoading(true);
try { try {
const user = await authApi.devLogin('adam@seahavenind.com', 'Adam Moussa', role); const { email, name } = devUsers[role] ?? devUsers.SysAdmin;
const user = await authApi.devLogin(email, name, role);
dispatch(setUser(user)); dispatch(setUser(user));
navigate('/', { replace: true }); navigate('/', { replace: true });
} catch (err) { } catch (err) {