1
0
mirror of https://github.com/bitwarden/server synced 2026-01-05 10:03:23 +00:00

Merge branch 'main' into auth/pm-22975/client-version-validator

This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-11-21 15:08:45 -05:00
committed by GitHub
62 changed files with 484 additions and 742 deletions

View File

@@ -195,16 +195,35 @@ public class AccountsController : Controller
throw new BadRequestException(ModelState);
}
// Moved from API, If you modify this endpoint, please update API as well. Self hosted installs still use the API endpoints.
[HttpPost("prelogin")]
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
[Obsolete("Migrating to use a more descriptive endpoint that would support different types of prelogins. " +
"Use prelogin/password instead. This endpoint has no EOL at the time of writing.")]
public async Task<PasswordPreloginResponseModel> PostPrelogin([FromBody] PasswordPreloginRequestModel model)
{
// Same as PostPasswordPrelogin to maintain compatibility. Do not make changes in this function body,
// only make changes in MakePasswordPreloginCall
return await MakePasswordPreloginCall(model);
}
// There are two functions done this way because the open api docs that get generated in our build pipeline
// cannot handle two of the same post attributes on the same function call. That is why there is a
// PostPrelogin and the more appropriate PostPasswordPrelogin.
[HttpPost("prelogin/password")]
public async Task<PasswordPreloginResponseModel> PostPasswordPrelogin([FromBody] PasswordPreloginRequestModel model)
{
// Same as PostPrelogin to maintain backwards compatibility. Do not make changes in this function body,
// only make changes in MakePasswordPreloginCall
return await MakePasswordPreloginCall(model);
}
private async Task<PasswordPreloginResponseModel> MakePasswordPreloginCall(PasswordPreloginRequestModel model)
{
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
if (kdfInformation == null)
{
kdfInformation = GetDefaultKdf(model.Email);
}
return new PreloginResponseModel(kdfInformation);
return new PasswordPreloginResponseModel(kdfInformation, model.Email);
}
[HttpGet("webauthn/assertion-options")]
@@ -228,19 +247,17 @@ public class AccountsController : Controller
{
return _defaultKdfResults[0];
}
else
{
// Compute the HMAC hash of the email
var hmacMessage = Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant());
using var hmac = new System.Security.Cryptography.HMACSHA256(_defaultKdfHmacKey);
var hmacHash = hmac.ComputeHash(hmacMessage);
// Convert the hash to a number
var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant();
var hashFirst8Bytes = hashHex.Substring(0, 16);
var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber);
// Find the default KDF value for this hash number
var hashIndex = (int)(Math.Abs(hashNumber) % _defaultKdfResults.Count);
return _defaultKdfResults[hashIndex];
}
// Compute the HMAC hash of the email
var hmacMessage = Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant());
using var hmac = new System.Security.Cryptography.HMACSHA256(_defaultKdfHmacKey);
var hmacHash = hmac.ComputeHash(hmacMessage);
// Convert the hash to a number
var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant();
var hashFirst8Bytes = hashHex.Substring(0, 16);
var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber);
// Find the default KDF value for this hash number
var hashIndex = (int)(Math.Abs(hashNumber) % _defaultKdfResults.Count);
return _defaultKdfResults[hashIndex];
}
}

View File

@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations;
namespace Bit.Identity.Models.Request.Accounts;
public class PreloginRequestModel
public class PasswordPreloginRequestModel
{
[Required]
[EmailAddress]

View File

@@ -0,0 +1,38 @@
using Bit.Core.Enums;
using Bit.Core.KeyManagement.Models.Data;
using Bit.Core.Models.Data;
namespace Bit.Identity.Models.Response.Accounts;
public class PasswordPreloginResponseModel
{
public PasswordPreloginResponseModel(UserKdfInformation kdfInformation, string? salt = null)
{
// PM-28143 Cleanup
Kdf = kdfInformation.Kdf;
KdfIterations = kdfInformation.KdfIterations;
KdfMemory = kdfInformation.KdfMemory;
KdfParallelism = kdfInformation.KdfParallelism;
// End Cleanup
KdfSettings = new KdfSettings()
{
KdfType = kdfInformation.Kdf,
Iterations = kdfInformation.KdfIterations,
Memory = kdfInformation.KdfMemory,
Parallelism = kdfInformation.KdfParallelism,
};
Salt = salt;
}
// Old Data Types
public KdfType? Kdf { get; set; } // PM-28143 Remove with cleanup
public int? KdfIterations { get; set; } // PM-28143 Remove with cleanup
public int? KdfMemory { get; set; } // PM-28143 Remove with cleanup
public int? KdfParallelism { get; set; } // PM-28143 Remove with cleanup
// New Data Types
public KdfSettings? KdfSettings { get; set; } // PM-28143 With cleanup make this not nullish
public string? Salt { get; set; } // PM-28143 With cleanup make this not nullish. Not used yet,
// just the email from the request at this time.
}

View File

@@ -1,20 +0,0 @@
using Bit.Core.Enums;
using Bit.Core.Models.Data;
namespace Bit.Identity.Models.Response.Accounts;
public class PreloginResponseModel
{
public PreloginResponseModel(UserKdfInformation kdfInformation)
{
Kdf = kdfInformation.Kdf;
KdfIterations = kdfInformation.KdfIterations;
KdfMemory = kdfInformation.KdfMemory;
KdfParallelism = kdfInformation.KdfParallelism;
}
public KdfType Kdf { get; set; }
public int KdfIterations { get; set; }
public int? KdfMemory { get; set; }
public int? KdfParallelism { get; set; }
}

View File

@@ -1,8 +1,4 @@
// FIXME: Update this file to be null safe and then delete the line below
#nullable disable
using AspNetCoreRateLimit;
using Bit.Core.Utilities;
using Bit.Core.Utilities;
namespace Bit.Identity;
@@ -23,23 +19,7 @@ public class Program
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureLogging((hostingContext, logging) =>
logging.AddSerilog(hostingContext, (e, globalSettings) =>
{
var context = e.Properties["SourceContext"].ToString();
if (context.Contains(typeof(IpRateLimitMiddleware).FullName))
{
return e.Level >= globalSettings.MinLogLevel.IdentitySettings.IpRateLimit;
}
if (context.Contains("Duende.IdentityServer.Validation.TokenValidator") ||
context.Contains("Duende.IdentityServer.Validation.TokenRequestValidator"))
{
return e.Level >= globalSettings.MinLogLevel.IdentitySettings.IdentityToken;
}
return e.Level >= globalSettings.MinLogLevel.IdentitySettings.Default;
}));
});
})
.AddSerilogFileLogging();
}
}

View File

@@ -170,14 +170,11 @@ public class Startup
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
IHostApplicationLifetime appLifetime,
GlobalSettings globalSettings,
ILogger<Startup> logger)
{
IdentityModelEventSource.ShowPII = true;
app.UseSerilog(env, appLifetime, globalSettings);
// Add general security headers
app.UseMiddleware<SecurityHeadersMiddleware>();

View File

@@ -27,9 +27,6 @@
"events": {
"connectionString": "SECRET"
},
"sentry": {
"dsn": "SECRET"
},
"notificationHub": {
"connectionString": "SECRET",
"hubName": "SECRET"