Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Infrastructure.Password;

/// <summary>
/// Resolves the effective password rule for a given client and validates passwords against it.
/// A client-level rule (stored on ClientConfig) takes precedence; when absent it falls back to
/// the global DCC configuration.
/// </summary>
public interface IPasswordRuleProvider
{
/// <summary>
/// Validate a password against the client's effective rule.
/// Returns the localized failure prompt, or null when the password is valid.
/// Designed to be called from a FluentValidation rule (see <see cref="PasswordRuleValidatorExtensions"/>)
/// so client-aware password validation stays inside the single, framework-invoked validation pipeline.
/// </summary>
Task<string?> GetFailureAsync(string? password, string? clientId);

/// <summary>
/// Generate a new password satisfying the global DCC password rule.
/// </summary>
Task<string> GenerateNewPasswordAsync();
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Infrastructure.Password;

/// <summary>
/// Wires <see cref="IPasswordRuleProvider"/> into a FluentValidation rule chain. The configured
/// rule (global DCC default, or a client override) is the single source of truth for what a valid
/// password looks like - no hardcoded required/length checks are layered on top of it here.
/// </summary>
public static class PasswordRuleValidatorExtensions
{
public static IRuleBuilderOptionsConditions<T, string?> PasswordRule<T>(
this IRuleBuilder<T, string?> ruleBuilder,
IPasswordRuleProvider passwordRuleProvider,
Func<T, string?> clientId)
{
return ruleBuilder.CustomAsync(async (password, context, cancellation) =>
{
var failure = await passwordRuleProvider.GetFailureAsync(password, clientId(context.InstanceToValidate));
if (failure is not null)
{
context.AddFailure(failure);
}
});
}
}
30 changes: 30 additions & 0 deletions src/Contracts/Masa.Auth.Contracts.Admin/Sso/ClientConfigDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Sso;

public class ClientConfigDto
{
/// <summary>
/// OIDC client_id
/// </summary>
public string ClientId { get; set; } = "";

/// <summary>
/// 密码规则(正则表达式);为空表示回退到全局 DCC 配置
/// </summary>
public ClientPasswordRuleDto PasswordRule { get; set; } = new();

/// <summary>
/// 密码验证失败提示(i18n key);为空表示回退到全局 DCC 配置
/// </summary>
/// <summary>
/// 短信场景 × 渠道 × 模板配置
/// </summary>
public List<ClientSmsTemplateDto> SmsTemplates { get; set; } = new();

/// <summary>
/// 邮件场景 × 渠道 × 模板配置
/// </summary>
public List<ClientEmailTemplateDto> EmailTemplates { get; set; } = new();
}
20 changes: 20 additions & 0 deletions src/Contracts/Masa.Auth.Contracts.Admin/Sso/ClientDetailDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,24 @@ public class ClientDetailDto : AddClientDto
#region Properties
public List<ClientPropertyDto> Properties { get; set; } = new();
#endregion

#region Password & Message Templates
/// <summary>
/// 密码规则(正则表达式);为空表示回退到全局 DCC 配置
/// </summary>
public ClientPasswordRuleDto PasswordRule { get; set; } = new();

/// <summary>
/// 密码验证失败提示(i18n key);为空表示回退到全局 DCC 配置
/// </summary>
/// <summary>
/// 短信场景 × 渠道 × 模板配置
/// </summary>
public List<ClientSmsTemplateDto> SmsTemplates { get; set; } = new();

/// <summary>
/// 邮件场景 × 渠道 × 模板配置
/// </summary>
public List<ClientEmailTemplateDto> EmailTemplates { get; set; } = new();
#endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Sso;

/// <summary>
/// Client 级邮件模板配置:一个发送场景对应一个渠道 + 模板
/// </summary>
public class ClientEmailTemplateDto
{
public SendEmailTypes Scene { get; set; }

/// <summary>
/// MC 渠道编码
/// </summary>
public string ChannelCode { get; set; } = "";

/// <summary>
/// MC 模板编码
/// </summary>
public string TemplateCode { get; set; } = "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Sso;

public class ClientPasswordRuleDto
{
public bool UseRegexPattern { get; set; }

public int MinLength { get; set; } = 6;

public int MaxLength { get; set; } = 20;

public bool RequireUppercase { get; set; }

public bool RequireLowercase { get; set; }

public bool RequireDigit { get; set; }

public bool RequireSpecialCharacter { get; set; }

public string RegexPattern { get; set; } = string.Empty;

public string? PasswordPrompt { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Contracts.Admin.Sso;

/// <summary>
/// Client 级短信模板配置:一个发送场景对应一个渠道 + 模板
/// </summary>
public class ClientSmsTemplateDto
{
public SendMsgCodeTypes Scene { get; set; }

/// <summary>
/// MC 渠道编码
/// </summary>
public string ChannelCode { get; set; } = "";

/// <summary>
/// MC 模板编码
/// </summary>
public string TemplateCode { get; set; } = "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,16 @@ namespace Masa.Auth.Contracts.Admin.Subjects.Validator;

public class PasswordValidator : MasaAbstractValidator<string?>
{
public PasswordValidator(PasswordHelper passwordHelper)
public PasswordValidator(IPasswordRuleProvider passwordRuleProvider)
{
RuleFor(password => password).Required().MaximumLength(50).Custom(passwordHelper.ValidatePassword);
RuleFor(password => password).Required()
.CustomAsync(async (password, context, cancellation) =>
{
var failure = await passwordRuleProvider.GetFailureAsync(password, null);
if (failure is not null)
{
context.AddFailure(failure);
}
});
}
}
46 changes: 46 additions & 0 deletions src/Domain/Masa.Auth.Domain/Sso/Aggregates/ClientConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) MASA Stack All rights reserved.
// Licensed under the Apache License. See LICENSE.txt in the project root for license information.

namespace Masa.Auth.Domain.Sso.Aggregates;

/// <summary>
/// Per-client business configuration: password rule and the message template
/// matrix (scene x channel x template). Keyed by the OIDC client_id (string).
/// A missing value means "fall back to the global DCC configuration".
/// </summary>
public class ClientConfig : FullAggregateRoot<int, Guid>
{
private List<ClientMessageTemplate> _messageTemplates = new();

public string ClientId { get; private set; } = "";

public string? PasswordRule { get; private set; }

public string? PasswordPrompt { get; private set; }

public ClientPasswordRuleConfig? PasswordRuleConfig { get; private set; }

public IReadOnlyCollection<ClientMessageTemplate> MessageTemplates => _messageTemplates;

private ClientConfig() { }

public ClientConfig(string clientId)
{
ClientId = clientId;
}

public void UpdatePasswordRule(string? passwordRule, string? passwordPrompt, ClientPasswordRuleConfig? passwordRuleConfig)
{
PasswordRule = passwordRule;
PasswordPrompt = passwordPrompt;
PasswordRuleConfig = passwordRuleConfig;
}

/// <summary>
/// Replace the whole message template matrix.
/// </summary>
public void SetMessageTemplates(IEnumerable<ClientMessageTemplate> messageTemplates)
{
_messageTemplates = messageTemplates.ToList();
}
}
Loading
Loading