using Microsoft.AspNetCore.Authentication;

namespace Dpz.Core.Auth.Controllers;

public class ShellController(
    IUserPasskeySessionService passkeySessionService,
    IAccountSessionService accountSessionService
) : Controller
{
    [HttpGet("~/sign-in")]
    public IActionResult SignIn(string? returnUrl = null, string? msg = null)
    {
        if (User.Identity?.IsAuthenticated == true)
        {
            if (!string.IsNullOrWhiteSpace(returnUrl) && Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }

            return RedirectToAction("Index", "Home");
        }

        return this.ShellView(
            new AuthShellViewModel<EmptyAuthShellPayload>
            {
                EntryName = "public",
                PageName = "sign-in",
                ReturnUrl = returnUrl,
                Message = msg,
            }
        );
    }

    [Authorize, Route("~/sign-out")]
    public async Task<IActionResult> Logout(string? returnUrl)
    {
        var account = User.NameIdentifier;
        var sessionId = User.SessionId;
        if (!string.IsNullOrWhiteSpace(account) && !string.IsNullOrWhiteSpace(sessionId))
        {
            await accountSessionService.RevokeAsync(account, sessionId);
        }
        passkeySessionService.ClearCurrentCredentialId();
        await HttpContext.SignOutAsync();
        return string.IsNullOrWhiteSpace(returnUrl) || !Url.IsLocalUrl(returnUrl)
            ? Redirect("/")
            : Redirect(returnUrl);
    }

    [Authorize, HttpGet, Route("change-password.html")]
    public IActionResult ChangePassword()
    {
        return UserShell("change-password");
    }

    [Authorize, HttpGet, Route("passkeys.html")]
    public IActionResult Passkeys()
    {
        return UserShell("passkeys");
    }

    [Authorize, HttpGet, Route("security-onboarding.html")]
    public IActionResult SecurityOnboarding(string? returnUrl = null)
    {
        returnUrl =
            string.IsNullOrWhiteSpace(returnUrl) || !Url.IsLocalUrl(returnUrl)
                ? Url.Action("Index", "Home") ?? "/"
                : returnUrl;

        return UserShell("security-onboarding", returnUrl);
    }

    [HttpGet("auth-status/{reason}")]
    public IActionResult AuthStatus(string reason)
    {
        return this.ShellView(
            new AuthShellViewModel<EmptyAuthShellPayload>
            {
                EntryName = "public",
                PageName = "auth-status",
                Message = reason,
            }
        );
    }

    private IActionResult UserShell(string pageName, string? returnUrl = null)
    {
        return this.ShellView(
            new AuthShellViewModel<EmptyAuthShellPayload>
            {
                EntryName = "identity",
                PageName = pageName,
                ReturnUrl = returnUrl,
                User = new VmUserInfo
                {
                    Id = User.NameIdentifier ?? "",
                    Name = User.Identity?.Name ?? "",
                    Avatar = "",
                    Sex = Sex.Other,
                    Permissions = null,
                    Enable = true,
                    Key = "",
                },
            }
        );
    }
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码说明

这是一个 ASP.NET Core MVC 控制器,名为 ShellController,主要负责处理用户身份认证相关的页面路由和逻辑。

主要功能模块

1. 构造函数依赖注入

public class ShellController(
    IUserPasskeySessionService passkeySessionService,
    IAccountSessionService accountSessionService
) : Controller

使用了 C# 12 的主构造函数语法,注入两个服务:

  • IUserPasskeySessionService: 管理 Passkey(无密码认证)会话
  • IAccountSessionService: 管理用户账户会话

2. 登录页面 (SignIn)

[HttpGet("~/sign-in")]
public IActionResult SignIn(string? returnUrl = null, string? msg = null)
  • 检查用户是否已认证
  • 已认证用户:重定向到 returnUrl 或首页
  • 未认证用户:返回登录页面视图,传递参数包括入口名称、页面名称、返回URL和消息

3. 登出功能 (Logout)

[Authorize, Route("~/sign-out")]
public async Task<IActionResult> Logout(string? returnUrl)
  • 需要授权访问([Authorize]
  • 执行操作:
    • 撤销当前账户的会话(通过 accountSessionService
    • 清除 Passkey 凭证ID
    • 执行 ASP.NET Core 的登出流程
    • 重定向到指定URL或根路径

4. 用户设置页面

修改密码

[Authorize, HttpGet, Route("change-password.html")]
public IActionResult ChangePassword()

Passkeys 管理

[Authorize, HttpGet, Route("passkeys.html")]
public IActionResult Passkeys()

安全向导

[Authorize, HttpGet, Route("security-onboarding.html")]
public IActionResult SecurityOnboarding(string? returnUrl = null)
  • 提供安全设置引导页面
  • 验证 returnUrl 的安全性(必须是本地URL)

5. 认证状态页面 (AuthStatus)

[HttpGet("auth-status/{reason}")]
public IActionResult AuthStatus(string reason)

显示认证状态信息,接受一个 reason 参数说明状态原因。

6. 辅助方法 (UserShell)

private IActionResult UserShell(string pageName, string? returnUrl = null)
  • 私有辅助方法,用于生成用户相关页面的统一视图
  • 构建包含用户信息的视图模型:
    • 用户ID、名称
    • 头像、性别
    • 权限、启用状态等

安全特性

  1. URL 验证: 多处使用 Url.IsLocalUrl() 防止开放重定向攻击
  2. 授权控制: 敏感操作使用 [Authorize] 特性保护
  3. 会话管理: 登出时主动撤销服务端会话
  4. Passkey 支持: 集成了现代无密码认证方案

架构模式

  • 使用依赖注入实现松耦合
  • 统一的视图模型 (AuthShellViewModel) 处理不同页面
  • 遵循 RESTful 路由约定
  • 清晰的职责分离(控制器仅负责路由和视图渲染)
评论加载中...