Merge pull request #527 from lanedirt/525-prevent-email-address-collision-from-occuring

Prevent email address collision from occurring during identity generation
This commit is contained in:
Leendert de Borst
2025-01-13 14:09:54 +00:00
committed by GitHub
12 changed files with 795 additions and 25 deletions

View File

@@ -34,6 +34,7 @@
<ItemGroup>
<ProjectReference Include="..\Databases\AliasServerDb\AliasServerDb.csproj" />
<ProjectReference Include="..\Generators\AliasVault.Generators.Identity\AliasVault.Generators.Identity.csproj" />
<ProjectReference Include="..\Shared\AliasVault.Shared.Server\AliasVault.Shared.Server.csproj" />
<ProjectReference Include="..\Shared\AliasVault.Shared\AliasVault.Shared.csproj" />
<ProjectReference Include="..\Utilities\AliasVault.Auth\AliasVault.Auth.csproj" />

View File

@@ -0,0 +1,58 @@
//-----------------------------------------------------------------------
// <copyright file="IdentityController.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Api.Controllers;
using AliasServerDb;
using AliasVault.Api.Controllers.Abstracts;
using AliasVault.Api.Helpers;
using Asp.Versioning;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
/// <summary>
/// Controller for generating identities taking into account existing information on the AliasVault server.
/// </summary>
/// <param name="userManager">UserManager instance.</param>
/// <param name="dbContextFactory">DbContextFactory instance.</param>
[ApiVersion("1")]
public class IdentityController(UserManager<AliasVaultUser> userManager, IAliasServerDbContextFactory dbContextFactory) : AuthenticatedRequestController(userManager)
{
/// <summary>
/// Verify that provided email address is not already taken by another user.
/// </summary>
/// <param name="email">The full email address to check.</param>
/// <returns>True if the email address is already taken, false otherwise.</returns>
[HttpPost("CheckEmail/{email}")]
public async Task<IActionResult> CheckEmail(string email)
{
var user = await GetCurrentUserAsync();
if (user == null)
{
return Unauthorized();
}
bool isTaken = await EmailClaimExistsAsync(email);
return Ok(new { isTaken });
}
/// <summary>
/// Verify that provided email address is not already taken by another user.
/// </summary>
/// <param name="email">The email address to check.</param>
/// <returns>True if the email address is already taken, false otherwise.</returns>
private async Task<bool> EmailClaimExistsAsync(string email)
{
await using var context = await dbContextFactory.CreateDbContextAsync();
var sanitizedEmail = EmailHelper.SanitizeEmail(email);
var claimExists = await context.UserEmailClaims.FirstOrDefaultAsync(c => c.Address == sanitizedEmail);
return claimExists != null;
}
}

View File

@@ -386,7 +386,7 @@ public class VaultController(ILogger<VaultController> logger, IAliasServerDbCont
foreach (var email in newEmailAddresses)
{
// Sanitize email address.
var sanitizedEmail = email.Trim().ToLower();
var sanitizedEmail = EmailHelper.SanitizeEmail(email);
// If email address is invalid according to the EmailAddressAttribute, skip it.
if (!new EmailAddressAttribute().IsValid(sanitizedEmail))

View File

@@ -0,0 +1,24 @@
//-----------------------------------------------------------------------
// <copyright file="EmailHelper.cs" company="lanedirt">
// Copyright (c) lanedirt. All rights reserved.
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Api.Helpers;
/// <summary>
/// EmailHelper class which contains helper methods for email.
/// </summary>
public static class EmailHelper
{
/// <summary>
/// Sanitize email address by trimming and converting to lowercase.
/// </summary>
/// <param name="email">Email address to sanitize.</param>
/// <returns>Sanitized email address.</returns>
public static string SanitizeEmail(string email)
{
return email.Trim().ToLower();
}
}

View File

@@ -49,20 +49,43 @@ public sealed class CredentialService(HttpClient httpClient, DbService dbService
/// <returns>Task.</returns>
public async Task<Credential> GenerateRandomIdentity(Credential credential)
{
// Generate a random identity using the IIdentityGenerator implementation.
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(dbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
const int MaxAttempts = 5;
var attempts = 0;
bool isEmailTaken;
// Generate random values for the Identity properties
credential.Username = identity.NickName;
credential.Alias.FirstName = identity.FirstName;
credential.Alias.LastName = identity.LastName;
credential.Alias.NickName = identity.NickName;
credential.Alias.Gender = identity.Gender == Gender.Male ? "Male" : "Female";
credential.Alias.BirthDate = identity.BirthDate;
do
{
// Generate a random identity using the IIdentityGenerator implementation
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(dbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();
// Set the email
var emailDomain = GetDefaultEmailDomain();
credential.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
// Generate random values for the Identity properties
credential.Username = identity.NickName;
credential.Alias.FirstName = identity.FirstName;
credential.Alias.LastName = identity.LastName;
credential.Alias.NickName = identity.NickName;
credential.Alias.Gender = identity.Gender == Gender.Male ? "Male" : "Female";
credential.Alias.BirthDate = identity.BirthDate;
// Set the email
var emailDomain = GetDefaultEmailDomain();
credential.Alias.Email = $"{identity.EmailPrefix}@{emailDomain}";
// Check if email is already taken
try
{
var response = await httpClient.PostAsync($"v1/Identity/CheckEmail/{credential.Alias.Email}", null);
var result = await response.Content.ReadFromJsonAsync<Dictionary<string, bool>>();
isEmailTaken = result?["isTaken"] ?? false;
}
catch
{
// If the API call fails, assume email is not taken to allow operation to continue
isEmailTaken = false;
}
attempts++;
}
while (isEmailTaken && attempts < MaxAttempts);
// Generate password
credential.Passwords.First().Value = GenerateRandomPassword();

View File

@@ -151,3 +151,109 @@ Juliana
Charlie
Lucia
Stella
Adriana
Beatrice
Bianca
Calliope
Carmen
Celeste
Dakota
Diana
Esther
Florence
Francesca
Georgia
Harlow
Haven
Holly
Hope
India
Indie
Iris
Juniper
Kaia
Keira
Lara
Laura
Laurel
Luna
Magnolia
Maeve
Marina
Marlowe
Nina
Noelle
Octavia
Olive
Ophelia
Phoenix
Poppy
Primrose
Ramona
River
Rosalie
Rosemary
Sage
Salem
Selena
Sienna
Summer
Sylvie
Thea
Tessa
Wren
Winter
Willa
Ada
Aspen
Blair
Brynn
Cassidy
Cecilia
Daisy
Dawn
Daphne
Ember
Fiona
Flora
Freya
Gemma
Giselle
Harmony
Heidi
Imogen
Indie
Jessie
June
Kaia
Lena
Lola
Mabel
Maisie
Margot
Matilda
Mira
Morgan
Nell
Nadia
Odette
Opal
Pearl
Phoebe
Raven
Reese
Robin
Rowan
Ruth
Sabrina
Sasha
Sierra
Skye
Sloane
Talia
Thora
Vera
Willa
Winnie
Yara
Zara

View File

@@ -140,3 +140,108 @@ Levi
Alan
Jorge
Carson
Felix
Oliver
Theodore
Harrison
Maxwell
Sebastian
Xavier
Dominick
Lincoln
Elliott
Walter
Simon
Dean
Hugo
Malcolm
Leon
Oscar
Calvin
Raymond
Edgar
Franklin
Arthur
Lawrence
Dennis
Russell
Douglas
Leonard
Gregory
Harold
Frederick
Martin
Curtis
Stanley
Gilbert
Harvey
Francis
Eugene
Ralph
Roy
Albert
Bruce
Ronald
Keith
Craig
Roger
Randy
Gary
Dennis
Edwin
Don
Glen
Gordon
Howard
Earl
Leo
Lloyd
Milton
Norman
Roland
Vernon
Warren
Alfred
Bernard
Chester
Clarence
Clifford
Clyde
Dale
Dan
Darrell
Floyd
Herman
Jerome
Maurice
Neil
Ray
Rodney
Roland
Stuart
Wallace
Wayne
Wendell
Barry
Cecil
Claude
Daryl
Edmund
Everett
Ferdinand
Forrest
Gerald
Hugh
Irving
Leslie
Marvin
Morris
Nelson
Perry
Phillip
Roderick
Ross
Terrence
Wade
Winston
Zachariah

View File

@@ -165,3 +165,107 @@ Shaw
Snyder
Mason
Dixon
Blackwood
Shepherd
Frost
Hawkins
Pearson
Fleming
Dawson
Palmer
Nash
Barker
Thornton
Fitzgerald
Winters
Mckenzie
Chandler
Griffith
Cunningham
Doyle
Fletcher
Hicks
Walton
Briggs
Pearce
Nichols
Blake
Hodges
Benson
Marsh
Whitaker
Skinner
Robbins
Goodwin
Kirby
Savage
Hensley
Hancock
Pratt
Gallagher
Yates
Dennis
Swanson
Steele
Bauer
Holt
Barber
Schultz
Foley
Fowler
Wise
Malone
Cannon
Tate
Stark
Welch
Dyer
Booth
Payne
Shannon
Harmon
Woodward
Morse
Jacobson
Knowles
Blanchard
Dillon
Stokes
Buckley
Dickerson
Middleton
Sellers
Cobb
Stephenson
Roach
Moody
Beard
Mccarthy
Garner
Mcguire
Sloan
Ballard
Shields
Orr
Savage
Graves
Dempsey
Weeks
Mckay
Cooke
Riddle
Gates
Atkins
Farrell
Lowery
Huffman
Livingston
Davenport
Hendricks
Kerr
Pollard
Hoover
Wolfe
Bowman
Underwood
Frazier

View File

@@ -102,3 +102,110 @@ Juul
Lise
Myrthe
Veerle
Aafke
Alicia
Amira
Aniek
Annabel
Annelies
Anouk
Astrid
Babette
Bianca
Britt
Carlijn
Chantal
Claire
Dagmar
Danique
Daphne
Denise
Dominique
Doris
Eefje
Elena
Eline
Elisa
Elisabeth
Ellen
Esther
Eveline
Fabienne
Felice
Fleur
Frederique
Gwen
Hanna
Heleen
Helena
Ilona
Imke
Inge
Irene
Iris
Janna
Janneke
Jasmine
Jennifer
Jessica
Joelle
Judith
Julia
Karin
Karlijn
Kim
Kirsten
Kyra
Laura
Lena
Lianne
Liesbeth
Linda
Lisanne
Lisette
Louise
Maartje
Manon
Margot
Marieke
Marijke
Marlies
Marloes
Marthe
Melissa
Michelle
Nadine
Natalie
Nicole
Nina
Noortje
Paulien
Petra
Rachel
Renee
Robin
Rosa
Roxanne
Sabine
Sandra
Saskia
Silke
Simone
Suzanne
Sylvie
Tamara
Tanja
Tara
Thea
Thirza
Tina
Tineke
Ursula
Victoria
Wendy
Wilma
Xandra
Yasmin
Yvette
Yvonne
Zara

View File

@@ -99,3 +99,114 @@ Mijs
Mika
Felix
Merlijn
Alexander
Aron
Arthur
Axel
Bas
Bastiaan
Berend
Björn
Casper
Cees
Chris
Christian
Christiaan
Colin
Cornelis
Dani
Dennis
Dirk
Dominic
Eduard
Eelco
Erik
Erwin
Ezra
Faas
Filip
Florian
Frank
Frederik
Freek
Gerard
Gerrit
Giel
Gijs
Glenn
Govert
Harm
Harold
Hendrik
Henrik
Huub
Ian
Ivo
Jacob
Jake
Jan
Jarno
Jason
Jeffrey
Jeremy
Jim
Jimmy
Johan
Johannes
Jonas
Jonathan
Jos
Joshua
Justin
Kay
Kevin
Kjeld
Klaas
Lennard
Lennart
Leon
Lex
Liam
Loek
Lorenzo
Louis
Lowie
Maarten
Magnus
Maikel
Marc
Marcel
Marco
Martijn
Mathias
Matthijs
Maurits
Menno
Michiel
Nathan
Nico
Oscar
Pascal
Patrick
Paul
Peter
Philip
Pieter
Pim
Quincy
Remco
Rick
Rik
Robert
Rogier
Rowan
Ruud
Simon
Stefan
Steven
Thom
Victor
Vincent
Willem
Wouter
Yannick

View File

@@ -104,3 +104,104 @@ van Asselt
Timmermans
van Vliet
van Rijn
van Schaik
Bosman
Wolters
van Hout
Hermans
van Rooij
de Vos
van Donselaar
Evers
van den Brink
Verkerk
Groeneveld
van Duijn
Schuurman
Hoogendoorn
van Zanten
Koopman
Cornelissen
van Driel
Teunissen
Versteeg
van Deursen
Schipper
van Kempen
Bouwman
van der Valk
Nijhuis
van der Werf
van den Akker
Verhoef
Wessels
van der Poel
Driessen
van Oosten
Lambrechts
van der Vlist
Hoogeveen
van Gils
Rietveld
Barendrecht
van der Spek
Stam
van der Linde
Boersma
van Dijk
Schepers
van der Kolk
Roelofs
van der Velden
van den Burg
Westra
van der Steen
Pronk
van der Veer
Rozendaal
van den Bos
Konings
van der Wiel
Noordam
van der Laan
Schut
van der Vlugt
Witteveen
van der Zwan
Boogaard
van der Waal
Stolk
van der Windt
Rutten
van der Zanden
Spaans
van der Zwaan
Roos
van der Zijl
Schoenmaker
van Diepen
Romeijn
van Doesburg
Schippers
van Eck
Rijken
van Egmond
Schrama
van Eijk
Ruijter
van Engelen
Sanders
van Es
Schenk
van Essen
van Gaal
van Geenen
van Gent
van Gestel
van Gool
van Grinsven
van Gurp
van Haaften
van Haren
van Hattem
van Hees

View File

@@ -67,25 +67,55 @@ public class UsernameEmailGenerator
{
var parts = new List<string>();
// Use first initial + last name
if (_random.Next(2) == 0)
switch (_random.Next(4))
{
parts.Add(identity.FirstName.Substring(0, 1).ToLower() + identity.LastName.ToLower());
}
else
{
// Use full name
parts.Add((identity.FirstName + identity.LastName).ToLower());
case 0:
// First initial + last name
parts.Add(identity.FirstName.Substring(0, 1).ToLower() + identity.LastName.ToLower());
break;
case 1:
// Full name
parts.Add((identity.FirstName + identity.LastName).ToLower());
break;
case 2:
// First name + last initial
parts.Add(identity.FirstName.ToLower() + identity.LastName.Substring(0, 1).ToLower());
break;
case 3:
// First 3 chars of first name + last name
parts.Add(identity.FirstName.Substring(0, Math.Min(3, identity.FirstName.Length)).ToLower() + identity.LastName.ToLower());
break;
}
// Add birth year
if (_random.Next(2) == 0)
// Add birth year variations
if (_random.Next(3) != 0)
{
parts.Add(identity.BirthDate.Year.ToString().Substring(2));
switch (_random.Next(2))
{
case 0:
parts.Add(identity.BirthDate.Year.ToString().Substring(2));
break;
case 1:
parts.Add(identity.BirthDate.Year.ToString());
break;
}
}
else if (_random.Next(2) == 0)
{
// Add random numbers for more uniqueness
parts.Add(_random.Next(10, 999).ToString());
}
// Join parts and sanitize
// Join parts with random symbols, possibly multiple
var emailPrefix = string.Join(GetRandomSymbol(), parts);
// Add extra random symbol at random position
if (_random.Next(2) == 0)
{
int position = _random.Next(emailPrefix.Length);
emailPrefix = emailPrefix.Insert(position, GetRandomSymbol());
}
emailPrefix = SanitizeEmailPrefix(emailPrefix);
// Adjust length