diff -r 4d35bae7caf8 share/roundup/templates/classic/detectors/userauditor.py --- a/share/roundup/templates/classic/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/share/roundup/templates/classic/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -73,7 +73,11 @@ check_main = nodeid not in check_main # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address) diff -r 4d35bae7caf8 share/roundup/templates/devel/detectors/userauditor.py --- a/share/roundup/templates/devel/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/share/roundup/templates/devel/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -73,7 +73,11 @@ check_main = nodeid not in check_main # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address) diff -r 4d35bae7caf8 share/roundup/templates/jinja2/detectors/userauditor.py --- a/share/roundup/templates/jinja2/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/share/roundup/templates/jinja2/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -73,7 +73,11 @@ check_main = nodeid not in check_main # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address) diff -r 4d35bae7caf8 share/roundup/templates/minimal/detectors/userauditor.py --- a/share/roundup/templates/minimal/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/share/roundup/templates/minimal/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -73,7 +73,11 @@ check_main = nodeid not in check_main # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address) diff -r 4d35bae7caf8 share/roundup/templates/responsive/detectors/userauditor.py --- a/share/roundup/templates/responsive/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/share/roundup/templates/responsive/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -73,7 +73,11 @@ check_main = nodeid not in check_main # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address) diff -r 4d35bae7caf8 test/test_userauditor.py --- a/test/test_userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/test/test_userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -82,6 +82,10 @@ self.assertRaises(ValueError, self.db.user.set, uid, address='sp_ken@example.com') self.assertRaises(ValueError, self.db.user.set, uid, alternate_addresses='kenny@example.com') + # substrings of existing alternates should be allowed, but not exact matches + self.db.user.set(uid, alternate_addresses='p_ken@example.com') + self.assertRaises(ValueError, self.db.user.set, uid, alternate_addresses='sp_ken@example.com') + def testBadRoles(self): userid = self.db.user.lookup('kyle') self.assertRaises(ValueError, self.db.user.set, userid, roles='BadRole') diff -r 4d35bae7caf8 website/issues/detectors/userauditor.py --- a/website/issues/detectors/userauditor.py Sat Aug 29 12:53:43 2026 -0400 +++ b/website/issues/detectors/userauditor.py Mon Aug 31 10:29:42 2026 +0200 @@ -62,7 +62,11 @@ check_main = db.user.stringFind(address=address) # make sure none of the alts are owned by anyone other than us (x!=nodeid) - check_alts = [x for x in db.user.filter(None, {'alternate_addresses' : address}) if x != nodeid] + check_alts = [ + x for x in db.user.filter(None, {'alternate_addresses': address}) + if x != nodeid + and address in db.user.get(x, 'alternate_addresses').split('\n') + ] if check_main or check_alts: raise ValueError('Email address %s already in use' % address)