Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent email address collision from occurring during identity generation #527

Merged
merged 3 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/AliasVault.Api/AliasVault.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
58 changes: 58 additions & 0 deletions src/AliasVault.Api/Controllers/IdentityController.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion src/AliasVault.Api/Controllers/VaultController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ private async Task UpdateUserEmailClaims(AliasServerDbContext context, string us
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))
Expand Down
24 changes: 24 additions & 0 deletions src/AliasVault.Api/Helpers/EmailHelper.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
51 changes: 37 additions & 14 deletions src/AliasVault.Client/Services/CredentialService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,43 @@ public static string GenerateRandomPassword()
/// <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();

// 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}";
const int MaxAttempts = 5;
var attempts = 0;
bool isEmailTaken;

do
{
// Generate a random identity using the IIdentityGenerator implementation
var identity = await IdentityGeneratorFactory.CreateIdentityGenerator(dbService.Settings.DefaultIdentityLanguage).GenerateRandomIdentityAsync();

// 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading
Loading