ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Bitwarden Server Passkey Directory Report:基于 2FA Directory 的通行密钥支持目录查询实现

Bitwarden Server Passkey Directory Report:基于 2FA Directory 的通行密钥支持目录查询实现 Bitwarden Server Passkey Directory Report基于 2FA Directory 的通行密钥支持目录查询实现【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server导读Passkey Directory Report 是 Bitwarden server 中为组织管理员提供的一份支持通行密钥Passkey的域名目录其数据来源于公开的 2FA Directory 为骨架结合仓库源码查询实现、DI 注册、API 控制器、领域模型与响应模型完整讲解该功能的数据流、缓存策略、响应结构与端点契约并给出可直接复现的接口调用方式与源码级实现解析。功能概述Passkey Directory Report 提供一份支持通行密钥的域名清单数据源为 2FA Directory API。这份数据在 Bitwarden 客户端中驱动一份报告帮助组织管理员了解其成员的哪些凭据cipher可以被升级为通行密钥Passkey。简单来说服务端只负责拉取外部目录数据、做归一化与缓存、以稳定接口输出真正的凭据匹配发生在客户端客户端拿到目录条目后将其与组织成员的登录项 URL 逐一比对识别出支持 Passkey 登录或支持 Passkey 作为 MFA 的站点并附上官方设置文档链接引导用户完成升级。与客户端的分工服务端与客户端的分工由本仓库文档明确说明服务端本仓库从 2FA Directory API 拉取全量域名目录解析为统一结构缓存 24 小时并通过GET /reports/passkey-directory提供给客户端客户端将目录条目与用户凭据匹配渲染报告界面客户端侧实现细节见 clients 仓库的apps/web/src/app/dirt/reports/pages/README.md本仓库不包含该实现。Feature Flag 门控该功能由PasskeyDirectoryReport特性开关Feature Flag控制。在源码中该 Flag 定义于 src/Core/Constants.cspublic const string PasskeyDirectoryReport inno-passkey-directory-report;即特性开关的实际配置名为inno-passkey-directory-report。只有该 Flag 开启时对应的 API 端点才可用详见下文API 端点一节。整体架构与数据流数据流图关联文档给出的端到端数据流如下2FA Directory API -- GetPasskeyDirectoryQuery (cached 24h) -- ReportsController -- Client整个链路分为三个环节外部数据源2FA Directory v1 APIhttps://passkeys-api.2fa.directory/v1/all.json提供一个 JSON 字典描述各域名对 Passkey / MFA 的支持情况。该 URL 在查询实现中以常量固化见 GetPasskeyDirectoryQuery.csprivate const string _passkeyDirectoryUrl https://passkeys-api.2fa.directory/v1/all.json;查询层GetPasskeyDirectoryQuery负责拉取并解析外部数据通过 FusionCache 缓存 24 小时避免每次请求都打外部 APIAPI 端点ReportsController暴露GET /reports/passkey-directory返回缓存的目录条目集合。关键文件文件作用GetPasskeyDirectoryQuery.cs核心查询——拉取、解析并缓存 2FA Directory 数据Interfaces/IGetPasskeyDirectoryQuery.cs查询接口定义ReportingServiceCollectionExtensions.cs查询、HTTP 客户端与缓存的 DI 注册Models/Data/PasskeyDirectoryEntry.cs目录条目的领域模型src/Api/Dirt/Controllers/ReportsController.cs暴露端点的 API 控制器src/Api/Dirt/Models/Response/PasskeyDirectoryResponseModel.csAPI 响应模型说明以上为关联文档中的关键文件清单其中指向本仓库内部的路径已统一转换为以仓库根目录为起点的相对路径便于直接跳转阅读。核心查询实现GetPasskeyDirectoryQueryGetPasskeyDirectoryQuery是实现整个功能的中枢类位于 src/Core/Dirt/Reports/ReportFeatures/GetPasskeyDirectoryQuery.cs。它通过主构造函数注入三样依赖public class GetPasskeyDirectoryQuery( IHttpClientFactory httpClientFactory, [FromKeyedServices(GetPasskeyDirectoryQuery.CacheName)] IFusionCache cache, ILoggerGetPasskeyDirectoryQuery logger) : IGetPasskeyDirectoryQueryIHttpClientFactory用于创建命名 HTTP 客户端PasskeyDirectoryHttpClientIFusionCache以键控服务Keyed Service方式注入名为PasskeyDirectory的缓存实例ILogger记录拉取与解析过程的日志。接口定义接口 IGetPasskeyDirectoryQuery.cs 只声明了一个方法其 XML 注释准确概括了语义public interface IGetPasskeyDirectoryQuery { TaskIEnumerablePasskeyDirectoryEntry GetPasskeyDirectoryAsync(); }注释特别强调这些条目代表的域名可能支持 Passkey用于登录或 MFA需要客户端与 cipher 匹配后给出文档链接引导用户使用 Passkey——即目录数据是候选清单而非已确认升级的清单。缓存优先的读取路径查询入口GetPasskeyDirectoryAsync()采用缓存优先策略通过 FusionCache 的GetOrSetAsync实现private static readonly TimeSpan _cacheDuration TimeSpan.FromDays(1); private const string _cacheKey passkey-directory; public async TaskIEnumerablePasskeyDirectoryEntry GetPasskeyDirectoryAsync() { var entries await cache.GetOrSetAsync( key: _cacheKey, factory: async _ await FetchPasskeyDirectoryAsync(), options: new FusionCacheEntryOptions(duration: _cacheDuration) ); return entries; }核心参数缓存键passkey-directory缓存时长TimeSpan.FromDays(1)即 24 小时回源行为缓存未命中时执行FetchPasskeyDirectoryAsync()拉取外部数据并写入缓存命中的请求直接返回缓存结果完全不打外部 API。外部数据拉取与解析FetchPasskeyDirectoryAsync()是真正的回源逻辑实现细节值得逐段拆解private async TaskListPasskeyDirectoryEntry FetchPasskeyDirectoryAsync() { logger.LogInformation(Constants.BypassFiltersEventId, Fetching passkey directory from external API); var response await _httpClient.GetAsync(_passkeyDirectoryUrl); response.EnsureSuccessStatusCode(); await using var stream await response.Content.ReadAsStreamAsync(); var directory await JsonSerializer.DeserializeAsyncDictionarystring, JsonElement(stream); if (directory is null) { return []; } var entries new ListPasskeyDirectoryEntry(); foreach (var (domain, serviceData) in directory) { var hasPasswordless serviceData.TryGetProperty(passwordless, out var passwordlessElement) passwordlessElement.ValueKind JsonValueKind.String; var hasMfa serviceData.TryGetProperty(mfa, out var mfaElement) mfaElement.ValueKind JsonValueKind.String; if (!hasPasswordless !hasMfa) { continue; } var instructions serviceData.TryGetProperty(documentation, out var docElement) docElement.ValueKind JsonValueKind.String ? docElement.GetString() ?? string.Empty : string.Empty; entries.Add(new PasskeyDirectoryEntry { DomainName domain, Passwordless hasPasswordless, Mfa hasMfa, Instructions instructions }); } logger.LogInformation(Constants.BypassFiltersEventId, Fetched {Count} passkey directory entries from external API, entries.Count); return entries; }从源码可以提炼出以下实现事实网络健壮性使用EnsureSuccessStatusCode()外部 API 返回非 2xx 时直接抛出异常交由上层异常处理链路BypassFiltersEventId日志事件记录数据结构假设外部all.json是一个以域名为键、服务信息对象为值的字典因此反序列化为Dictionarystring, JsonElement并用JsonElement做防御式属性探测支持标记判定passwordless与mfa字段只有当其ValueKind JsonValueKind.String时才认为该域名支持对应能力。这是一个值得注意的容错设计——外部数据中该字段存在但为空非字符串时不会被误判为支持过滤策略passwordless与mfa均不支持的域名会被continue跳过不进入结果集从而精简目录体积文档链接兜底documentation字段缺失或非字符串时Instructions回落为空字符串保证响应模型字段永远有值空目录兜底反序列化结果为null如空 body时返回空列表[]不会导致下游空引用。领域模型每个目录条目对应领域模型 PasskeyDirectoryEntry.cspublic class PasskeyDirectoryEntry { public string DomainName { get; set; } string.Empty; public bool Passwordless { get; set; } public bool Mfa { get; set; } public string Instructions { get; set; } string.Empty; }四个属性与外部数据字段一一对应DomainName域名如github.com、Passwordless是否支持无密码 Passkey 登录、Mfa是否支持将 Passkey 用作 MFA 手段、Instructions官方设置文档 URL。缓存策略与 DI 注册缓存细节关联文档明确列出的缓存参数如下ProviderFusionCache键控服务名PasskeyDirectoryDuration24 小时Keypasskey-directory。这些参数在源码中均有一一对应的常量GetPasskeyDirectoryQuery.CacheName PasskeyDirectory、_cacheDuration TimeSpan.FromDays(1)、_cacheKey passkey-directory。DI 注册ReportingServiceCollectionExtensions缓存、HTTP 客户端与查询服务统一在 ReportingServiceCollectionExtensions.cs 的AddReportingServices()中注册与该功能直接相关的三行是services.AddExtendedCache(GetPasskeyDirectoryQuery.CacheName, (GlobalSettings)globalSettings); services.AddHttpClient(GetPasskeyDirectoryQuery.HttpClientName); // ... services.AddScopedIGetPasskeyDirectoryQuery, GetPasskeyDirectoryQuery();AddExtendedCache(...)注册名为PasskeyDirectory的 FusionCache 键控缓存实例供[FromKeyedServices]注入AddHttpClient(GetPasskeyDirectoryQuery.HttpClientName)注册名为PasskeyDirectoryHttpClient的命名 HttpClient默认超时、无自定义 handler符合该场景轻量只读拉取的需求AddScopedIGetPasskeyDirectoryQuery, GetPasskeyDirectoryQuery()查询以 Scoped 生命周期注册——由于真正的数据已在缓存层兜底Scoped 生命周期不会放大外部 API 压力。GetPasskeyDirectoryQuery中通过httpClientFactory.CreateClient(HttpClientName)获取该命名客户端与注册处严格对应。API 端点契约端点定义GET /reports/passkey-directory端点实现在 src/Api/Dirt/Controllers/ReportsController.cs/// summary /// Gets the list of domains that support passkeys from the 2FA Directory /// /summary /// returnsList of domains with passkey support details/returns [HttpGet(passkey-directory)] [RequireFeature(FeatureFlagKeys.PasskeyDirectoryReport)] public async TaskIEnumerablePasskeyDirectoryResponseModel GetPasskeyDirectoryAsync() { var entries await _getPasskeyDirectoryQuery.GetPasskeyDirectoryAsync(); return entries.Select(e new PasskeyDirectoryResponseModel { DomainName e.DomainName, Passwordless e.Passwordless, Mfa e.Mfa, Instructions e.Instructions }); }端点要素要素值HTTP 方法/路径GET /reports/passkey-directory认证标准 Bitwarden 认证控制器级[Authorize(Application)]见 ReportsController.cs特性开关[RequireFeature(FeatureFlagKeys.PasskeyDirectoryReport)]对应配置名inno-passkey-directory-reportConstants.cs返回类型IEnumerablePasskeyDirectoryResponseModel两点实现细节值得注意端点无需组织上下文与同控制器的member-access/{orgId}、password-health-report-applications/{orgId}等端点不同passkey-directory不接收任何路由或查询参数——目录是全局数据对所有已认证用户一致因此也不走AuthorizeAsync(orgId)的Access Reports 权限 套餐能力双重校验鉴权与开关并行生效[Authorize(Application)]保证请求必须携带合法应用令牌[RequireFeature(...)]保证特性开关开启二者叠加后才可访问开关未开启时端点直接返回 404/不可用。响应结构每个响应数组元素包含以下字段字段类型说明domainNamestring域名例如github.compasswordlessbool该域名是否支持无密码 Passkey 登录mfabool该域名是否支持将 Passkey 用作 MFA 手段instructionsstring官方设置文档 URL无文档时为空字符串响应模型 PasskeyDirectoryResponseModel.cs 与领域模型结构完全同构控制器中逐字段映射Passwordless等属性名经 JSON 序列化后输出为小驼峰的passwordless/mfa/instructions/domainName。若响应数组为空如外部 API 暂不可用且无缓存客户端应将其视为当前没有可升级候选而非错误。一个典型的响应示例[ { domainName: github.com, passwordless: true, mfa: true, instructions: https://docs.github.com/... }, { domainName: example.com, passwordless: false, mfa: true, instructions: } ]其中instructions为空字符串表示该站点虽支持 Passkey但外部目录未提供设置文档链接。从源码看该功能的工程要点外部依赖隔离全部外部访问收敛在GetPasskeyDirectoryQuery一个类内控制器只面向本地接口编程替换数据源或调整解析逻辑不影响 API 层缓存兜底外部抖动24 小时 FusionCache 使绝大多数请求命中缓存即使 2FA Directory 短暂不可用已缓存的目录仍可正常服务首次回源失败则表现为空目录防御式解析JsonElement.ValueKind探测与空值兜底让服务对外部数据格式变化有较强容忍度避免因单个字段格式异常导致整个解析失败开关与鉴权分层特性开关RequireFeature控制功能灰度应用级认证控制访问主体二者职责清晰、可独立演进。总结Passkey Directory Report 是 Bitwarden server 中一个典型的外部数据源 缓存 REST 输出微功能GetPasskeyDirectoryQuery每 24 小时从 2FA Directory 拉取一次全量域名目录过滤掉完全不支持 Passkey 的站点后写入 FusionCacheReportsController在inno-passkey-directory-report特性开关开启时通过GET /reports/passkey-directory将条目输出给客户端客户端再将目录与成员凭据匹配为组织管理员呈现哪些凭据可升级为通行密钥的报告并附上官方文档链接。整套实现链路短、边界清晰是理解 Bitwarden server 中外部数据集成 报告服务模式的绝佳样例。【免费下载链接】serverBitwarden infrastructure/backend (API, database, Docker, etc).项目地址: https://gitcode.com/GitHub_Trending/ser/server创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表