Browse Source

Merge pull request #1238 from colinin/dev

chore: Merge branch dev to main
pull/1239/head
yx lin 8 months ago
committed by GitHub
parent
commit
54a7614b6a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .github/workflows/build.yml
  2. 2
      .github/workflows/publish.yml
  3. 2
      .github/workflows/release.yml
  4. 104
      Directory.Packages.props
  5. 7
      apps/vben5/apps/app-antd/.env.development
  6. 15
      apps/vben5/apps/app-antd/.env.production
  7. 1
      apps/vben5/apps/app-antd/package.json
  8. 21
      apps/vben5/apps/app-antd/src/adapter/request/index.ts
  9. 53
      apps/vben5/apps/app-antd/src/auth/authService.ts
  10. 8
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  11. 5
      apps/vben5/apps/app-antd/src/locales/langs/en-US/page.json
  12. 8
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  13. 5
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/page.json
  14. 11
      apps/vben5/apps/app-antd/src/router/routes/core.ts
  15. 69
      apps/vben5/apps/app-antd/src/store/auth.ts
  16. 42
      apps/vben5/apps/app-antd/src/views/_core/authentication/code-login.vue
  17. 144
      apps/vben5/apps/app-antd/src/views/_core/authentication/forget-password.vue
  18. 54
      apps/vben5/apps/app-antd/src/views/_core/authentication/login.vue
  19. 120
      apps/vben5/apps/app-antd/src/views/_core/authentication/should-change-password.vue
  20. 26
      apps/vben5/apps/app-antd/src/views/_core/authentication/third-party-login.vue
  21. 15
      apps/vben5/apps/app-antd/src/views/_core/fallback/login-callback.vue
  22. 1
      apps/vben5/packages/@abp/account/src/api/index.ts
  23. 28
      apps/vben5/packages/@abp/account/src/api/useAccountApi.ts
  24. 46
      apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts
  25. 2
      apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts
  26. 35
      apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts
  27. 12
      apps/vben5/packages/@abp/account/src/types/account.ts
  28. 14
      apps/vben5/packages/@abp/account/src/types/token.ts
  29. 4
      apps/vben5/packages/effects/hooks/src/use-app-config.ts
  30. 4
      apps/vben5/packages/types/global.d.ts
  31. 1
      apps/vben5/pnpm-workspace.yaml
  32. 14
      aspnet-core/LINGYUN.MicroService.All.sln
  33. 14
      aspnet-core/LINGYUN.MicroService.SingleProject.sln
  34. 1
      aspnet-core/framework/security/LINGYUN.Abp.Claims.Mapping/LINGYUN.Abp.Claims.Mapping.csproj
  35. 131
      aspnet-core/framework/security/LINGYUN.Abp.Claims.Mapping/Microsoft/IdentityModel/Tokens/TokenWildcardIssuerValidator.cs
  36. 12
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Features/WeChatOfficialFeatureDefinitionProvider.cs
  37. 2
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Features/WeChatOfficialFeatures.cs
  38. 24
      aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.SettingManagement/LINGYUN/Abp/WeChat/SettingManagement/WeChatSettingAppService.cs
  39. 62
      aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs
  40. 2
      aspnet-core/migrations/LY.MicroService.WebhooksManagement.EntityFrameworkCore/LY.MicroService.WebhooksManagement.EntityFrameworkCore.csproj
  41. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml
  42. 30
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd
  43. 26
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj
  44. 28
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs
  45. 52
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs
  46. 46
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs
  47. 8
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs
  48. 36
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json
  49. 36
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json
  50. 77
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs
  51. 30
      aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs
  52. 188
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs
  53. 68
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/TwoFactorSupportedLoginModel.cs
  54. 105
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs
  55. 81
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs
  56. 29
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs
  57. 29
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs
  58. 10
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs
  59. 68
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs
  60. 18
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs
  61. 29
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs
  62. 29
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs
  63. 34
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs
  64. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml
  65. 30
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd
  66. 44
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj
  67. 32
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs
  68. 14
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml
  69. 13
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs
  70. 14
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml
  71. 14
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml
  72. 14
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml
  73. 13
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml
  74. 12
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json
  75. BIN
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/bilibili_logo_18x18.png
  76. BIN
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/qq_logo_15x18.png
  77. BIN
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/wecom_logo_77x18.png
  78. 60
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictLoginModel.cs
  79. 64
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/TwoFactorSupportedLoginModel.cs
  80. 6
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountAuthenticationTypes.cs
  81. 32
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs
  82. 18
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/AccountBundles.cs
  83. 17
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/ChangePasswordScriptContributor.cs
  84. 14
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/UserLoginLinkStyleContributor.cs
  85. 10
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs
  86. 5
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj
  87. 11
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs
  88. 8
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Modules/Account/Components/Toolbar/UserLoginLink/Default.cshtml
  89. 4
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Modules/_ViewImports.cshtml
  90. 46
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml
  91. 174
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs
  92. 21
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.js
  93. 105
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml
  94. 387
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs
  95. 54
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml
  96. 291
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs
  97. 2
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ProfileManagement/ProfileManagementPageContributor.cs
  98. 3
      aspnet-core/modules/account/LINGYUN.Abp.Account.Web/wwwroot/styles/user-login-link/fix-style.css
  99. 5
      aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs
  100. 6
      aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Application.Contracts/LINGYUN/Abp/OpenIddict/AbpOpenIddictApplicationContractsModule.cs

2
.github/workflows/build.yml

@ -7,7 +7,7 @@ on:
- "**.csproj"
env:
DOTNET_VERSION: "9.0.101"
DOTNET_VERSION: "9.0.301"
jobs:
build:

2
.github/workflows/publish.yml

@ -4,7 +4,7 @@ on:
pull_request:
branches: [ main ]
env:
DOTNET_VERSION: "9.0.101"
DOTNET_VERSION: "9.0.301"
jobs:
publish:

2
.github/workflows/release.yml

@ -14,4 +14,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
automatic_release_tag: "9.1.3"
automatic_release_tag: "9.2.0"

104
Directory.Packages.props

@ -1,18 +1,18 @@
<Project>
<PropertyGroup>
<DotNetCoreCAPPackageVersion>8.3.2</DotNetCoreCAPPackageVersion>
<DotNetCoreCAPPackageVersion>8.3.5</DotNetCoreCAPPackageVersion>
<ElsaPackageVersion>2.15.1</ElsaPackageVersion>
<ElsaNextPackageVersion>3.3.5</ElsaNextPackageVersion>
<VoloAbpPackageVersion>9.1.3</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.1.3</LINGYUNAbpPackageVersion>
<MicrosoftExtensionsPackageVersion>9.0.0</MicrosoftExtensionsPackageVersion>
<MicrosoftAspNetCorePackageVersion>9.0.0</MicrosoftAspNetCorePackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>9.0.0</MicrosoftEntityFrameworkCorePackageVersion>
<VoloAbpPackageVersion>9.2.0</VoloAbpPackageVersion>
<LINGYUNAbpPackageVersion>9.2.0</LINGYUNAbpPackageVersion>
<MicrosoftExtensionsPackageVersion>9.0.4</MicrosoftExtensionsPackageVersion>
<MicrosoftAspNetCorePackageVersion>9.0.4</MicrosoftAspNetCorePackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>9.0.4</MicrosoftEntityFrameworkCorePackageVersion>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<!-- Abp Framework -->
<ItemGroup>
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="4.1.3" />
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.LeptonXLite" Version="4.2.0" />
<PackageVersion Include="Volo.Abp.Core" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Account.Application" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Account.Application.Contracts" Version="$(VoloAbpPackageVersion)" />
@ -33,6 +33,7 @@
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.NewtonsoftJson" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.Serilog" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.SignalR" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.AspNetCore.TestBase" Version="$(VoloAbpPackageVersion)" />
@ -72,6 +73,7 @@
<PackageVersion Include="Volo.Abp.FeatureManagement.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.FeatureManagement.HttpApi" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.FeatureManagement.Web" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Emailing" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EventBus" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.EventBus.Abstractions" Version="$(VoloAbpPackageVersion)" />
@ -89,13 +91,14 @@
<PackageVersion Include="Volo.Abp.Identity.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Identity.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Identity.HttpApi" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Identity.HttpApi.Client" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Identity.Web" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.Application" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.Application.Contracts" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.Domain" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.IdentityServer.HttpApi" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Identity.HttpApi.Client" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Imaging.Abstractions" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Imaging.ImageSharp" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Json" Version="$(VoloAbpPackageVersion)" />
@ -121,6 +124,7 @@
<PackageVersion Include="Volo.Abp.PermissionManagement.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.PermissionManagement.HttpApi" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.PermissionManagement.Web" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Quartz" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Security" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Settings" Version="$(VoloAbpPackageVersion)" />
@ -130,6 +134,7 @@
<PackageVersion Include="Volo.Abp.SettingManagement.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.SettingManagement.HttpApi" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.SettingManagement.Web" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Swashbuckle" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Sms" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.TestBase" Version="$(VoloAbpPackageVersion)" />
@ -142,6 +147,7 @@
<PackageVersion Include="Volo.Abp.Users.Domain.Shared" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.Validation" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.VirtualFileSystem" Version="$(VoloAbpPackageVersion)" />
<PackageVersion Include="Volo.Abp.VirtualFileExplorer.Web" Version="$(VoloAbpPackageVersion)" />
</ItemGroup>
<!-- .NET -->
<ItemGroup>
@ -150,7 +156,8 @@
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Protocols.Json" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="9.0.4" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(MicrosoftEntityFrameworkCorePackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftEntityFrameworkCorePackageVersion)" />
@ -220,35 +227,35 @@
</ItemGroup>
<!-- Serilog -->
<ItemGroup>
<PackageVersion Include="Serilog" Version="4.0.2" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageVersion Include="Serilog.Enrichers.Environment" Version="2.3.0" />
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Enrichers.Environment" Version="3.0.1" />
<PackageVersion Include="Serilog.Enrichers.Assembly" Version="2.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="2.0.2" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageVersion Include="Serilog.Enrichers.Process" Version="3.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.1" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Elasticsearch" Version="9.0.3" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
<!-- Test -->
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageVersion Include="Moq.AutoMock" Version="3.0.0" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Moq.AutoMock" Version="3.5.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
<PackageVersion Include="Shouldly" Version="4.3.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
</ItemGroup>
<!-- Fody -->
<ItemGroup>
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.0" />
<PackageVersion Include="Fody" Version="6.9.2" />
</ItemGroup>
<!-- DataBase -->
<ItemGroup>
@ -256,25 +263,34 @@
<PackageVersion Include="Oracle.ManagedDataAccess.Core" Version="23.6.1" />
<PackageVersion Include="Npgsql" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.1.6" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.2" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.4" />
</ItemGroup>
<!-- Other -->
<ItemGroup>
<PackageVersion Include="AspNet.Security.OAuth.Bilibili" Version="9.4.0" />
<PackageVersion Include="AspNet.Security.OAuth.GitHub" Version="9.4.0" />
<PackageVersion Include="AspNet.Security.OAuth.QQ" Version="9.4.0" />
<PackageVersion Include="AspNet.Security.OAuth.Weixin" Version="9.4.0" />
<PackageVersion Include="AspNet.Security.OAuth.WorkWeixin" Version="9.4.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Twitter" Version="$(MicrosoftAspNetCorePackageVersion)" />
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.9.0" />
<PackageVersion Include="aliyun-net-sdk-core" Version="1.5.10" />
<PackageVersion Include="Aliyun.OSS.SDK.NetCore" Version="2.14.1" />
<PackageVersion Include="AgileConfig.Client" Version="1.6.9" />
<PackageVersion Include="Elastic.Apm.NetCoreAll" Version="1.31.0" />
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.5.0" />
<PackageVersion Include="Dapr.Client" Version="1.14.0" />
<PackageVersion Include="Dapr.Actors" Version="1.14.0" />
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.14.0" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.7" />
<PackageVersion Include="Dapr.Client" Version="1.15.4" />
<PackageVersion Include="Dapr.Actors" Version="1.15.4" />
<PackageVersion Include="Dapr.Actors.AspNetCore" Version="1.15.4" />
<PackageVersion Include="DistributedLock.Core" Version="1.0.8" />
<PackageVersion Include="DistributedLock.Redis" Version="1.0.3" />
<PackageVersion Include="Hangfire.MySqlStorage" Version="2.0.3" />
<PackageVersion Include="HangFire.SqlServer" Version="1.8.17" />
<PackageVersion Include="HangFire.SqlServer" Version="1.8.18" />
<PackageVersion Include="IdentityModel" Version="7.0.0" />
<PackageVersion Include="IP2Region.Net" Version="2.0.2" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.2.0" />
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageVersion Include="Magicodes.IE.Excel" Version="2.7.5.2" />
<PackageVersion Include="Markdig" Version="0.34.0" />
<PackageVersion Include="MiniExcel" Version="1.34.2" />
@ -282,8 +298,8 @@
<PackageVersion Include="NEST" Version="7.17.5" />
<PackageVersion Include="NRules" Version="0.9.2" />
<PackageVersion Include="Ocelot.Provider.Polly" Version="20.0.0" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.0.0" />
<PackageVersion Include="OpenIddict.Server.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenIddict.Validation.DataProtection" Version="6.2.1" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.11.2" />
<PackageVersion Include="OpenTelemetry.Exporter.Console" Version="1.11.2" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.2" />
@ -292,19 +308,19 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.11.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-beta.11" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.11.1" />
<PackageVersion Include="Polly" Version="8.4.2" />
<PackageVersion Include="Polly" Version="8.5.2" />
<PackageVersion Include="QRCoder" Version="1.5.1" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.13.0" />
<PackageVersion Include="Quartz.Serialization.Json" Version="3.14.0" />
<PackageVersion Include="RulesEngine" Version="5.0.5" />
<PackageVersion Include="Scriban" Version="5.10.0" />
<PackageVersion Include="Scriban" Version="6.2.1" />
<PackageVersion Include="Senparc.Weixin.MP" Version="16.18.9" />
<PackageVersion Include="SkyApm.Agent.Hosting" Version="2.2.0" />
<PackageVersion Include="SkyApm.Diagnostics.AspNetCore" Version="2.2.0" />
<PackageVersion Include="SkyAPM.Diagnostics.CAP" Version="2.2.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.16" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.8.1" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.8" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="2.1.6" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.31" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="Tencent.QCloud.Cos.Sdk" Version="5.4.37" />
<PackageVersion Include="TencentCloudSDK" Version="3.0.712" />

7
apps/vben5/apps/app-antd/.env.development

@ -15,6 +15,13 @@ VITE_DEVTOOLS=false
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 是否仅允许OIDC登录
VITE_GLOB_ONLY_OIDC=false
# 认证服务器
VITE_GLOB_AUTHORITY="http://127.0.0.1:30001"
# 授权范围
VITE_GLOB_AUDIENCE="openid email address phone profile offline_access lingyun-abp-application"
# 客户端Id
VITE_GLOB_CLIENT_ID=vue-admin-client
# 客户端密钥【生产环境请勿设置此值,建议启用仅允许OIDC登录,将使用授权码类型登录】
VITE_GLOB_CLIENT_SECRET=1q2w3e*

15
apps/vben5/apps/app-antd/.env.production

@ -10,10 +10,23 @@ VITE_COMPRESS=none
VITE_PWA=false
# vue-router 的模式
VITE_ROUTER_HISTORY=hash
# oauth2.0协议要求回调必须是完整的url
# VITE_ROUTER_HISTORY=hash
# 是否注入全局loading
VITE_INJECT_APP_LOADING=true
# 打包后是否生成dist.zip
VITE_ARCHIVER=true
# 是否仅允许OIDC登录
VITE_GLOB_ONLY_OIDC=false
# 认证服务器
VITE_GLOB_AUTHORITY="http://127.0.0.1:30001"
# 授权范围
VITE_GLOB_AUDIENCE="openid email address phone profile offline_access lingyun-abp-application"
# 客户端Id
VITE_GLOB_CLIENT_ID=vue-oauth-client

1
apps/vben5/apps/app-antd/package.json

@ -64,6 +64,7 @@
"@vueuse/core": "catalog:",
"ant-design-vue": "catalog:",
"dayjs": "catalog:",
"oidc-client-ts": "catalog:",
"pinia": "catalog:",
"vue": "catalog:",
"vue-router": "catalog:"

21
apps/vben5/apps/app-antd/src/adapter/request/index.ts

@ -5,7 +5,7 @@ import {
} from '@vben/request';
import { useAccessStore } from '@vben/stores';
import { useOAuthError, useTokenApi } from '@abp/account';
import { useOAuthError } from '@abp/account';
import { useAbpStore } from '@abp/core';
import { requestClient, useWrapperResult } from '@abp/request';
import { message } from 'ant-design-vue';
@ -13,7 +13,6 @@ import { message } from 'ant-design-vue';
import { useAuthStore } from '#/store';
export function initRequestClient() {
const { refreshTokenApi } = useTokenApi();
/**
*
*/
@ -36,19 +35,11 @@ export function initRequestClient() {
* token逻辑
*/
async function doRefreshToken() {
const accessStore = useAccessStore();
if (accessStore.refreshToken) {
try {
const { accessToken, tokenType, refreshToken } = await refreshTokenApi({
refreshToken: accessStore.refreshToken,
});
const newToken = `${tokenType} ${accessToken}`;
accessStore.setAccessToken(newToken);
accessStore.setRefreshToken(refreshToken);
return newToken;
} catch {
console.warn('The refresh token has expired or is unavailable.');
}
const authStore = useAuthStore();
try {
return await authStore.refreshSession();
} catch {
console.warn('The refresh token has expired or is unavailable.');
}
return '';
}

53
apps/vben5/apps/app-antd/src/auth/authService.ts

@ -0,0 +1,53 @@
import { useAppConfig } from '@vben/hooks';
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';
const { authority, audience, clientId, clientSecret, disablePKCE } =
useAppConfig(import.meta.env, import.meta.env.PROD);
const userManager = new UserManager({
authority,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: `${window.location.origin}/signin-callback`,
response_type: 'code',
scope: audience,
post_logout_redirect_uri: `${window.location.origin}/`,
silent_redirect_uri: `${window.location.origin}/silent-renew.html`,
automaticSilentRenew: true,
loadUserInfo: true,
userStore: new WebStorageStateStore({ store: window.localStorage }),
disablePKCE,
});
export default {
async login() {
return userManager.signinRedirect();
},
async logout() {
return userManager.signoutRedirect();
},
async refreshToken() {
return userManager.signinSilent();
},
async getAccessToken() {
const user = await userManager.getUser();
return user?.access_token;
},
async isAuthenticated() {
const user = await userManager.getUser();
return !!user && !user.expired;
},
async handleCallback() {
return userManager.signinRedirectCallback();
},
async getUser() {
return userManager.getUser();
},
};

8
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -11,6 +11,14 @@
},
"qrcodeLogin": {
"scaned": "Please confirm login on your phone."
},
"errors": {
"accountLockedByInvalidLoginAttempts": "The user account has been locked out due to invalid login attempts. Please wait a while and try again.",
"accountInactive": "You are not allowed to login! Your account is inactive.",
"invalidUserNameOrPassword": "Invalid username or password!",
"tokenHasExpired": "The token is no longer valid!",
"requiresTwoFactor": "Identity verification is required. Please select a verification method!",
"shouldChangePassword": "Your password has expired. Please change it and login!"
}
},
"manage": {

5
apps/vben5/apps/app-antd/src/locales/langs/en-US/page.json

@ -4,7 +4,10 @@
"register": "Register",
"codeLogin": "Code Login",
"qrcodeLogin": "Qr Code Login",
"forgetPassword": "Forget Password"
"forgetPassword": "Forget Password",
"oidcLogin": "Open Connect",
"oidcLoginMessage": "Please click \"OK\" to jump to the Certification center for login.",
"processingLogin": "Processing Login..."
},
"dashboard": {
"title": "Dashboard",

8
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -11,6 +11,14 @@
},
"qrcodeLogin": {
"scaned": "请在手机上确认登录."
},
"errors": {
"accountLockedByInvalidLoginAttempts": "由于尝试登录无效,用户帐户被锁定.请稍候再试!",
"accountInactive": "您不能登录,您的帐户是无效的!",
"invalidUserNameOrPassword": "用户名或密码错误!",
"tokenHasExpired": "您的请求会话已过期,请重新登录!",
"requiresTwoFactor": "需要验证身份,请选择一种验证方式!",
"shouldChangePassword": "您的密码已过期,请修改密码后登录!"
}
},
"manage": {

5
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/page.json

@ -4,7 +4,10 @@
"register": "注册",
"codeLogin": "验证码登录",
"qrcodeLogin": "二维码登录",
"forgetPassword": "忘记密码"
"forgetPassword": "忘记密码",
"oidcLogin": "认证中心登录",
"oidcLoginMessage": "请点击确定跳转认证中心登录",
"processingLogin": "登录成功,正在跳转中..."
},
"dashboard": {
"title": "概览",

11
apps/vben5/apps/app-antd/src/router/routes/core.ts

@ -21,6 +21,17 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
/** 基本路由,这些路由是必须存在的 */
const coreRoutes: RouteRecordRaw[] = [
{
component: () => import('#/views/_core/fallback/login-callback.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
title: 'Processing login',
},
name: 'OidcFallback',
path: '/signin-callback',
},
/**
*
* 使BasicLayout

69
apps/vben5/apps/app-antd/src/store/auth.ts

@ -9,6 +9,7 @@ import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
import {
usePhoneLoginApi,
useProfileApi,
useQrCodeLoginApi,
useTokenApi,
@ -19,12 +20,14 @@ import { notification } from 'ant-design-vue';
import { defineStore } from 'pinia';
import { useAbpConfigApi } from '#/api/core/useAbpConfigApi';
import authService from '#/auth/authService';
import { $t } from '#/locales';
export const useAuthStore = defineStore('auth', () => {
const { publish } = useEventBus();
const { loginApi } = useTokenApi();
const { loginApi, refreshTokenApi } = useTokenApi();
const { loginApi: qrcodeLoginApi } = useQrCodeLoginApi();
const { loginApi: phoneLoginApi } = usePhoneLoginApi();
const { getUserInfoApi } = useUserInfoApi();
const { getConfigApi } = useAbpConfigApi();
const { getPictureApi } = useProfileApi();
@ -35,6 +38,46 @@ export const useAuthStore = defineStore('auth', () => {
const loginLoading = ref(false);
async function refreshSession() {
if (await authService.getAccessToken()) {
const user = await authService.refreshToken();
const newToken = `${user?.token_type} ${user?.access_token}`;
accessStore.setAccessToken(newToken);
if (user?.refresh_token) {
accessStore.setRefreshToken(user.refresh_token);
}
return newToken;
} else {
const { accessToken, tokenType, refreshToken } = await refreshTokenApi({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
refreshToken: accessStore.refreshToken!,
});
const newToken = `${tokenType} ${accessToken}`;
accessStore.setAccessToken(newToken);
accessStore.setRefreshToken(refreshToken);
return newToken;
}
}
async function oidcLogin() {
await authService.login();
}
async function oidcCallback() {
try {
const user = await authService.handleCallback();
return await _loginSuccess({
accessToken: user.access_token,
tokenType: user.token_type,
refreshToken: user.refresh_token ?? '',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expiresIn: user.expires_in!,
});
} finally {
loginLoading.value = false;
}
}
async function qrcodeLogin(
key: string,
tenantId?: string,
@ -49,6 +92,20 @@ export const useAuthStore = defineStore('auth', () => {
}
}
async function phoneLogin(
phoneNumber: string,
code: string,
onSuccess?: () => Promise<void> | void,
) {
try {
loginLoading.value = true;
const result = await phoneLoginApi({ phoneNumber, code });
return await _loginSuccess(result, onSuccess);
} finally {
loginLoading.value = false;
}
}
/**
*
* Asynchronously handle the login process
@ -69,7 +126,10 @@ export const useAuthStore = defineStore('auth', () => {
async function logout(redirect: boolean = true) {
try {
// await logoutApi();
if (await authService.getAccessToken()) {
accessStore.setAccessToken(null);
await authService.logout();
}
} catch {
// 不做任何处理
}
@ -126,7 +186,6 @@ export const useAuthStore = defineStore('auth', () => {
) {
// 异步处理用户登录操作并获取 accessToken
let userInfo: null | UserInfo = null;
loginLoading.value = true;
const { accessToken, tokenType, refreshToken } = loginResult;
// 如果成功获取到 accessToken
if (accessToken) {
@ -172,9 +231,13 @@ export const useAuthStore = defineStore('auth', () => {
return {
$reset,
authLogin,
phoneLogin,
qrcodeLogin,
oidcLogin,
oidcCallback,
fetchUserInfo,
loginLoading,
logout,
refreshSession,
};
});

42
apps/vben5/apps/app-antd/src/views/_core/authentication/code-login.vue

@ -1,29 +1,42 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { ExtendedFormApi, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed, ref, useTemplateRef } from 'vue';
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAccountApi } from '@abp/account';
import { isPhone } from '@abp/core';
import { useAuthStore } from '#/store/auth';
interface CodeLoginExpose {
getFormApi(): ExtendedFormApi;
}
defineOptions({ name: 'CodeLogin' });
const authStore = useAuthStore();
const { sendPhoneSigninCodeApi } = useAccountApi();
const loading = ref(false);
const codeLogin = useTemplateRef<CodeLoginExpose>('codeLogin');
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenInput',
component: 'Input',
componentProps: {
placeholder: $t('authentication.mobile'),
placeholder: $t('AbpAccount.DisplayName:PhoneNumber'),
},
fieldName: 'phoneNumber',
label: $t('authentication.mobile'),
label: $t('AbpAccount.DisplayName:PhoneNumber'),
rules: z
.string()
.min(1, { message: $t('authentication.mobileTip') })
.refine((v) => /^\d{11}$/.test(v), {
.refine((v) => isPhone(v), {
message: $t('authentication.mobileErrortip'),
}),
},
@ -37,6 +50,7 @@ const formSchema = computed((): VbenFormSchema[] => {
: $t('authentication.sendCode');
return text;
},
handleSendCode: onSendCode,
placeholder: $t('authentication.code'),
},
fieldName: 'code',
@ -45,19 +59,31 @@ const formSchema = computed((): VbenFormSchema[] => {
},
];
});
async function onSendCode() {
const formApi = codeLogin.value?.getFormApi();
const input = await formApi?.getValues();
await sendPhoneSigninCodeApi({
phoneNumber: input!.phoneNumber,
});
}
/**
* 异步处理登录操作
* Asynchronously handle the login process
* @param values 登录表单数据
*/
async function handleLogin(values: Recordable<any>) {
// eslint-disable-next-line no-console
console.log(values);
try {
loading.value = true;
await authStore.phoneLogin(values.phoneNumber, values.code);
} finally {
loading.value = false;
}
}
</script>
<template>
<AuthenticationCodeLogin
ref="codeLogin"
:form-schema="formSchema"
:loading="loading"
@submit="handleLogin"

144
apps/vben5/apps/app-antd/src/views/_core/authentication/forget-password.vue

@ -1,42 +1,164 @@
<script lang="ts" setup>
import type { VbenFormSchema } from '@vben/common-ui';
import type { ExtendedFormApi, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
import { computed, ref } from 'vue';
import { computed, ref, useTemplateRef } from 'vue';
import { useRouter } from 'vue-router';
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { useAccountApi } from '@abp/account';
import { isPhone } from '@abp/core';
import { usePasswordValidator } from '@abp/identity';
import { message } from 'ant-design-vue';
interface FormModel {
currentPassword: string;
newPassword: string;
newPasswordConfirm: string;
}
interface ForgetPasswordExpose {
getFormApi(): ExtendedFormApi;
}
defineOptions({ name: 'ForgetPassword' });
const router = useRouter();
const { validate } = usePasswordValidator();
const { resetPasswordApi, sendPhoneResetPasswordCodeApi } = useAccountApi();
const loading = ref(false);
const forgetPassword = useTemplateRef<ForgetPasswordExpose>('forgetPassword');
const formSchema = computed((): VbenFormSchema[] => {
return [
{
component: 'VbenInput',
component: 'Input',
componentProps: {
placeholder: $t('AbpAccount.DisplayName:PhoneNumber'),
},
fieldName: 'phoneNumber',
label: $t('AbpAccount.DisplayName:PhoneNumber'),
rules: z
.string()
.min(1, { message: $t('authentication.mobileTip') })
.refine((v) => isPhone(v), {
message: $t('authentication.mobileErrortip'),
}),
},
{
component: 'VbenPinInput',
componentProps: {
createText: (countdown: number) => {
const text =
countdown > 0
? $t('authentication.sendText', [countdown])
: $t('authentication.sendCode');
return text;
},
handleSendCode: onSendCode,
placeholder: $t('authentication.code'),
},
fieldName: 'code',
label: $t('authentication.code'),
rules: z.string().min(1, { message: $t('authentication.codeTip') }),
},
{
component: 'InputPassword',
componentProps: {
placeholder: 'example@example.com',
placeholder: $t('AbpAccount.DisplayName:NewPassword'),
},
fieldName: 'email',
label: $t('authentication.email'),
fieldName: 'newPassword',
label: $t('AbpAccount.DisplayName:NewPassword'),
rules: z
.string()
.min(1, { message: $t('authentication.emailTip') })
.email($t('authentication.emailValidErrorTip')),
.superRefine(async (newPassword, ctx) => {
try {
await validate(newPassword);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: String(error),
});
}
})
.refine(
async (newPassword) => {
const formApi = forgetPassword.value?.getFormApi();
const input = (await formApi?.getValues()) as FormModel;
return input.currentPassword !== newPassword;
},
{
message: $t('AbpAccount.NewPasswordSameAsOld'),
},
)
.refine(
async (newPassword) => {
const formApi = forgetPassword.value?.getFormApi();
const input = (await formApi?.getValues()) as FormModel;
return input.newPasswordConfirm === newPassword;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
{
component: 'InputPassword',
componentProps: {
placeholder: $t('AbpAccount.DisplayName:NewPasswordConfirm'),
},
fieldName: 'newPasswordConfirm',
label: $t('AbpAccount.DisplayName:NewPasswordConfirm'),
rules: z.string().refine(
async (newPasswordConfirm) => {
const formApi = forgetPassword.value?.getFormApi();
const input = (await formApi?.getValues()) as FormModel;
return input.newPassword === newPasswordConfirm;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
];
});
function handleSubmit(value: Recordable<any>) {
// eslint-disable-next-line no-console
console.log('reset email:', value);
async function onSendCode() {
const formApi = forgetPassword.value?.getFormApi();
const input = await formApi?.getValues();
await sendPhoneResetPasswordCodeApi({
phoneNumber: input!.phoneNumber,
});
}
async function handleSubmit(values: Recordable<any>) {
loading.value = true;
try {
await resetPasswordApi({
code: values.code,
phoneNumber: values.phoneNumber,
newPassword: values.newPassword,
});
message.success($t('AbpAccount.YourPasswordIsSuccessfullyReset'));
router.push('/auth/login');
} finally {
loading.value = false;
}
}
</script>
<template>
<AuthenticationForgetPassword
ref="forgetPassword"
:form-schema="formSchema"
:submit-button-text="$t('AbpAccount.ResetPassword')"
:loading="loading"
@submit="handleSubmit"
/>

54
apps/vben5/apps/app-antd/src/views/_core/authentication/login.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { TwoFactorError } from '@abp/account';
import type { ShouldChangePasswordError, TwoFactorError } from '@abp/account';
import type { ExtendedFormApi, VbenFormSchema } from '@vben/common-ui';
import type { Recordable } from '@vben/types';
@ -7,13 +7,16 @@ import type { Recordable } from '@vben/types';
import { computed, nextTick, onMounted, useTemplateRef } from 'vue';
import { AuthenticationLogin, useVbenModal, z } from '@vben/common-ui';
import { useAppConfig } from '@vben/hooks';
import { $t } from '@vben/locales';
import { useAbpStore } from '@abp/core';
import { useAbpStore, useSettings } from '@abp/core';
import { Modal } from 'ant-design-vue';
import { useAbpConfigApi } from '#/api/core/useAbpConfigApi';
import { useAuthStore } from '#/store';
import ShouldChangePassword from './should-change-password.vue';
import ThirdPartyLogin from './third-party-login.vue';
import TwoFactorLogin from './two-factor-login.vue';
@ -23,14 +26,21 @@ interface LoginInstance {
defineOptions({ name: 'Login' });
const { onlyOidc } = useAppConfig(import.meta.env, import.meta.env.PROD);
const abpStore = useAbpStore();
const authStore = useAuthStore();
const { isTrue } = useSettings();
const { getConfigApi } = useAbpConfigApi();
const login = useTemplateRef<LoginInstance>('login');
const formSchema = computed((): VbenFormSchema[] => {
if (onlyOidc) {
return [];
}
let schemas: VbenFormSchema[] = [
{
component: 'Input',
@ -68,7 +78,28 @@ const formSchema = computed((): VbenFormSchema[] => {
const [TwoFactorModal, twoFactorModalApi] = useVbenModal({
connectedComponent: TwoFactorLogin,
});
const [ShouldChangePasswordModal, changePasswordModalApi] = useVbenModal({
connectedComponent: ShouldChangePassword,
});
async function onInit() {
if (onlyOidc === true) {
setTimeout(() => {
Modal.confirm({
centered: true,
title: $t('page.auth.oidcLogin'),
content: $t('page.auth.oidcLoginMessage'),
maskClosable: false,
closable: false,
cancelButtonProps: {
disabled: true,
},
async onOk() {
await authStore.oidcLogin();
},
});
}, 300);
return;
}
const abpConfig = await getConfigApi();
abpStore.setApplication(abpConfig);
nextTick(() => {
@ -77,10 +108,15 @@ async function onInit() {
});
}
async function onLogin(params: Recordable<any>) {
if (onlyOidc === true) {
await authStore.oidcLogin();
return;
}
try {
await authStore.authLogin(params);
} catch (error) {
onTwoFactorError(params, error);
onShouldChangePasswordError(params, error);
}
}
function onTwoFactorError(params: Recordable<any>, error: any) {
@ -93,16 +129,27 @@ function onTwoFactorError(params: Recordable<any>, error: any) {
twoFactorModalApi.open();
}
}
function onShouldChangePasswordError(params: Recordable<any>, error: any) {
const scpError = error as ShouldChangePasswordError;
if (scpError.changePasswordToken) {
changePasswordModalApi.setData({
...scpError,
...params,
});
changePasswordModalApi.open();
}
}
onMounted(onInit);
</script>
<template>
<div>
<div v-if="!onlyOidc">
<AuthenticationLogin
ref="login"
:form-schema="formSchema"
:loading="authStore.loginLoading"
:show-register="isTrue('Abp.Account.IsSelfRegistrationEnabled')"
@submit="onLogin"
>
<!-- 第三方登录 -->
@ -111,5 +158,6 @@ onMounted(onInit);
</template>
</AuthenticationLogin>
<TwoFactorModal />
<ShouldChangePasswordModal />
</div>
</template>

120
apps/vben5/apps/app-antd/src/views/_core/authentication/should-change-password.vue

@ -0,0 +1,120 @@
<script setup lang="ts">
import { useVbenForm, useVbenModal, z } from '@vben/common-ui';
import { $t } from '@vben/locales';
import { usePasswordValidator } from '@abp/identity';
import { useAuthStore } from '#/store/auth';
interface FormModel {
currentPassword: string;
newPassword: string;
newPasswordConfirm: string;
}
interface ModalState {
changePasswordToken: string;
password: string;
username: string;
userId: string;
}
const authStore = useAuthStore();
const { validate } = usePasswordValidator();
const [Form, formApi] = useVbenForm({
schema: [
{
component: 'InputPassword',
fieldName: 'password',
label: $t('AbpAccount.DisplayName:CurrentPassword'),
rules: 'required',
},
{
component: 'InputPassword',
fieldName: 'newPassword',
label: $t('AbpAccount.DisplayName:NewPassword'),
rules: z
.string()
.superRefine(async (newPassword, ctx) => {
try {
await validate(newPassword);
} catch (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: String(error),
});
}
})
.refine(
async (newPassword) => {
const input = (await formApi.getValues()) as FormModel;
return input.currentPassword !== newPassword;
},
{
message: $t('AbpAccount.NewPasswordSameAsOld'),
},
)
.refine(
async (newPassword) => {
const input = (await formApi.getValues()) as FormModel;
return input.newPasswordConfirm === newPassword;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
{
component: 'InputPassword',
fieldName: 'newPasswordConfirm',
label: $t('AbpAccount.DisplayName:NewPasswordConfirm'),
rules: z.string().refine(
async (newPasswordConfirm) => {
const input = (await formApi.getValues()) as FormModel;
return input.newPassword === newPasswordConfirm;
},
{
message: $t(
'AbpIdentity.Volo_Abp_Identity:PasswordConfirmationFailed',
),
},
),
},
],
showDefaultActions: false,
handleSubmit: onSubmit,
});
const [Modal, modalApi] = useVbenModal({
draggable: true,
closeOnClickModal: false,
closeOnPressEscape: false,
async onConfirm() {
await formApi.validateAndSubmitForm();
},
});
async function onSubmit(values: Record<string, any>) {
modalApi.setState({ submitting: true });
try {
const state = modalApi.getData<ModalState>();
await authStore.authLogin({
username: state.username,
password: state.password,
NewPassword: values.newPassword,
ChangePasswordToken: state.changePasswordToken,
});
} finally {
modalApi.setState({ submitting: false });
}
}
</script>
<template>
<Modal :title="$t('AbpAccount.ResetMyPassword')">
<Form />
</Modal>
</template>
<style scoped></style>

26
apps/vben5/apps/app-antd/src/views/_core/authentication/third-party-login.vue

@ -1,12 +1,17 @@
<script setup lang="ts">
import { MdiGithub, MdiGoogle, MdiQqchat, MdiWechat } from '@vben/icons';
import { $t } from '@vben/locales';
import { Button } from 'ant-design-vue';
import { VbenIconButton } from '@vben-core/shadcn-ui';
import { useAuthStore } from '#/store/auth';
defineOptions({
name: 'ThirdPartyLogin',
});
const authStore = useAuthStore();
async function login() {
await authStore.oidcLogin();
}
</script>
<template>
@ -20,18 +25,9 @@ defineOptions({
</div>
<div class="mt-4 flex flex-wrap justify-center">
<VbenIconButton class="mb-3">
<MdiWechat />
</VbenIconButton>
<VbenIconButton class="mb-3">
<MdiQqchat />
</VbenIconButton>
<VbenIconButton class="mb-3">
<MdiGithub />
</VbenIconButton>
<VbenIconButton class="mb-3">
<MdiGoogle />
</VbenIconButton>
<Button block type="primary" ghost @click="login">
{{ $t('page.auth.oidcLogin') }}
</Button>
</div>
</div>
</template>

15
apps/vben5/apps/app-antd/src/views/_core/fallback/login-callback.vue

@ -0,0 +1,15 @@
<script lang="ts" setup>
import { onMounted } from 'vue';
import { useAuthStore } from '#/store/auth';
const authStore = useAuthStore();
onMounted(async () => {
await authStore.oidcCallback();
});
</script>
<template>
<div>{{ $t('page.auth.processingLogin') }}</div>
</template>

1
apps/vben5/packages/@abp/account/src/api/index.ts

@ -1,5 +1,6 @@
export { useAccountApi } from './useAccountApi';
export { useMySessionApi } from './useMySessionApi';
export { usePhoneLoginApi } from './usePhoneLoginApi';
export { useProfileApi } from './useProfileApi';
export { useQrCodeLoginApi } from './useQrCodeLoginApi';
export { useTokenApi } from './useTokenApi';

28
apps/vben5/packages/@abp/account/src/api/useAccountApi.ts

@ -2,7 +2,9 @@ import type { ListResultDto } from '@abp/core';
import type {
GetTwoFactorProvidersInput,
PhoneResetPasswordDto,
SendEmailSigninCodeDto,
SendPhoneResetPasswordCodeDto,
SendPhoneSigninCodeDto,
TwoFactorProvider,
} from '../types/account';
@ -54,10 +56,36 @@ export function useAccountApi() {
});
}
/**
*
* @param input
*/
function sendPhoneResetPasswordCodeApi(
input: SendPhoneResetPasswordCodeDto,
): Promise<void> {
return request('/api/account/phone/send-password-reset-code', {
data: input,
method: 'POST',
});
}
/**
*
* @param input
*/
function resetPasswordApi(input: PhoneResetPasswordDto): Promise<void> {
return request('/api/account/phone/reset-password', {
data: input,
method: 'PUT',
});
}
return {
cancel,
getTwoFactorProvidersApi,
resetPasswordApi,
sendEmailSigninCodeApi,
sendPhoneResetPasswordCodeApi,
sendPhoneSigninCodeApi,
};
}

46
apps/vben5/packages/@abp/account/src/api/usePhoneLoginApi.ts

@ -0,0 +1,46 @@
import type { OAuthTokenResult, PhoneNumberTokenRequest } from '../types/token';
import { useAppConfig } from '@vben/hooks';
import { useRequest } from '@abp/request';
export function usePhoneLoginApi() {
const { cancel, request } = useRequest();
/**
*
* @param input
* @returns token
*/
async function loginApi(input: PhoneNumberTokenRequest) {
const { audience, clientId, clientSecret } = useAppConfig(
import.meta.env,
import.meta.env.PROD,
);
const result = await request<OAuthTokenResult>('/connect/token', {
data: {
client_id: clientId,
client_secret: clientSecret,
grant_type: 'phone_verify',
phone_number: input.phoneNumber,
phone_verify_code: input.code,
scope: audience,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
});
return {
accessToken: result.access_token,
expiresIn: result.expires_in,
refreshToken: result.refresh_token,
tokenType: result.token_type,
};
}
return {
cancel,
loginApi,
};
}

2
apps/vben5/packages/@abp/account/src/api/useQrCodeLoginApi.ts

@ -34,7 +34,7 @@ export function useQrCodeLoginApi() {
/**
*
* @param key Key
* @param input
* @returns token
*/
async function loginApi(input: QrCodeTokenRequest) {

35
apps/vben5/packages/@abp/account/src/hooks/useOAuthError.ts

@ -1,3 +1,5 @@
import { $t } from '@vben/locales';
interface OAuthError {
error: string;
error_description?: string;
@ -6,8 +8,37 @@ interface OAuthError {
export function useOAuthError() {
function formatError(error: OAuthError) {
// TODO: 解决oauth消息国际化.
return error.error_description;
switch (error.error_description) {
// 用户名或密码无效
case 'Invalid username or password!': {
return $t('abp.oauth.errors.invalidUserNameOrPassword');
}
// 需要更改密码
case 'PeriodicallyChangePassword':
case 'ShouldChangePasswordOnNextLogin': {
return $t('abp.oauth.errors.shouldChangePassword');
}
// 需要二次认证
case 'RequiresTwoFactor': {
return $t('abp.oauth.errors.requiresTwoFactor');
}
// Token已失效
case 'The token is no longer valid.': {
return $t('abp.oauth.errors.tokenHasExpired');
}
// 用户尝试登录次数太多,用户被锁定
case 'The user account has been locked out due to invalid login attempts. Please wait a while and try again.': {
return $t('abp.oauth.errors.accountLockedByInvalidLoginAttempts');
}
// 用户未启用
case 'You are not allowed to login! Your account is inactive.': {
return $t('abp.oauth.errors.accountInactive');
}
// 其他不常用的错误信息返回原始字符串
default: {
return error.error_description;
}
}
}
return {

12
apps/vben5/packages/@abp/account/src/types/account.ts

@ -12,11 +12,23 @@ interface SendPhoneSigninCodeDto {
phoneNumber: string;
}
interface SendPhoneResetPasswordCodeDto {
phoneNumber: string;
}
interface PhoneResetPasswordDto {
code: string;
newPassword: string;
phoneNumber: string;
}
type TwoFactorProvider = NameValue<string>;
export type {
GetTwoFactorProvidersInput,
PhoneResetPasswordDto,
SendEmailSigninCodeDto,
SendPhoneResetPasswordCodeDto,
SendPhoneSigninCodeDto,
TwoFactorProvider,
};

14
apps/vben5/packages/@abp/account/src/types/token.ts

@ -14,6 +14,13 @@ interface PasswordTokenRequest extends TokenRequest {
/** 用户名 */
userName: string;
}
/** 手机号授权请求数据模型 */
interface PhoneNumberTokenRequest {
/** 验证码 */
code: string;
/** 手机号 */
phoneNumber: string;
}
/** 扫码登录授权请求数据模型 */
interface QrCodeTokenRequest {
/** 二维码Key */
@ -69,13 +76,20 @@ interface TwoFactorError extends OAuthError {
userId: string;
}
interface ShouldChangePasswordError extends OAuthError {
changePasswordToken: string;
userId: string;
}
export type {
OAuthError,
OAuthTokenRefreshModel,
OAuthTokenResult,
PasswordTokenRequest,
PasswordTokenRequestModel,
PhoneNumberTokenRequest,
QrCodeTokenRequest,
ShouldChangePasswordError,
TokenRequest,
TokenResult,
TwoFactorError,

4
apps/vben5/packages/effects/hooks/src/use-app-config.ts

@ -21,6 +21,8 @@ export function useAppConfig(
VITE_GLOB_AUDIENCE,
VITE_GLOB_CLIENT_ID,
VITE_GLOB_CLIENT_SECRET,
VITE_GLOB_ONLY_OIDC,
VITE_GLOB_DISABLE_PKCE,
VITE_GLOB_UI_FRAMEWORK,
} = config;
@ -30,6 +32,8 @@ export function useAppConfig(
audience: VITE_GLOB_AUDIENCE,
clientId: VITE_GLOB_CLIENT_ID,
clientSecret: VITE_GLOB_CLIENT_SECRET,
onlyOidc: VITE_GLOB_ONLY_OIDC === 'true',
disablePKCE: VITE_GLOB_DISABLE_PKCE === 'true',
uiFramework: VITE_GLOB_UI_FRAMEWORK,
};
}

4
apps/vben5/packages/types/global.d.ts

@ -13,6 +13,8 @@ export interface VbenAdminProAppConfigRaw {
VITE_GLOB_CLIENT_SECRET: string;
VITE_GLOB_AUTHORITY: string;
VITE_GLOB_AUDIENCE?: string;
VITE_GLOB_ONLY_OIDC?: string;
VITE_GLOB_DISABLE_PKCE?: string;
VITE_GLOB_UI_FRAMEWORK: string;
}
@ -22,6 +24,8 @@ export interface ApplicationConfig {
audience?: string;
clientId: string;
clientSecret: string;
onlyOidc?: boolean;
disablePKCE?: boolean;
uiFramework: string;
}

1
apps/vben5/pnpm-workspace.yaml

@ -134,6 +134,7 @@ catalog:
naive-ui: ^2.41.0
nitropack: ^2.10.4
nprogress: ^0.2.0
oidc-client-ts: ^3.2.1
ora: ^8.2.0
pinia: ^2.3.1
pinia-plugin-persistedstate: ^4.2.0

14
aspnet-core/LINGYUN.MicroService.All.sln

@ -839,6 +839,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Saas.DbChecker"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.UI.Navigation.VueVbenAdmin5", "modules\platform\LINGYUN.Abp.UI.Navigation.VueVbenAdmin5\LINGYUN.Abp.UI.Navigation.VueVbenAdmin5.csproj", "{4105FC8B-E8C6-DBD7-FFEA-EA5AB09C7D08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.OAuth", "modules\account\LINGYUN.Abp.Account.Web.OAuth\LINGYUN.Abp.Account.Web.OAuth.csproj", "{DD11D070-F39E-1C41-1843-AE3ADBE501EF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.OAuth", "modules\account\LINGYUN.Abp.Account.OAuth\LINGYUN.Abp.Account.OAuth.csproj", "{001D0817-1EED-4C04-821E-F815F148EC90}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -2173,6 +2177,14 @@ Global
{4105FC8B-E8C6-DBD7-FFEA-EA5AB09C7D08}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4105FC8B-E8C6-DBD7-FFEA-EA5AB09C7D08}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4105FC8B-E8C6-DBD7-FFEA-EA5AB09C7D08}.Release|Any CPU.Build.0 = Release|Any CPU
{DD11D070-F39E-1C41-1843-AE3ADBE501EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DD11D070-F39E-1C41-1843-AE3ADBE501EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD11D070-F39E-1C41-1843-AE3ADBE501EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD11D070-F39E-1C41-1843-AE3ADBE501EF}.Release|Any CPU.Build.0 = Release|Any CPU
{001D0817-1EED-4C04-821E-F815F148EC90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{001D0817-1EED-4C04-821E-F815F148EC90}.Debug|Any CPU.Build.0 = Debug|Any CPU
{001D0817-1EED-4C04-821E-F815F148EC90}.Release|Any CPU.ActiveCfg = Release|Any CPU
{001D0817-1EED-4C04-821E-F815F148EC90}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2580,6 +2592,8 @@ Global
{D63FEED3-2342-7E96-4121-B0C19CF1EA81} = {6A3FF105-7E8B-4B4D-A736-1C2BAF86FDAA}
{DED16774-635C-D781-4D5B-D1FA56EECF10} = {D01D859E-4B72-478A-BABD-90F0981652D5}
{4105FC8B-E8C6-DBD7-FFEA-EA5AB09C7D08} = {F4923692-D343-4318-AECA-96F580B1A563}
{DD11D070-F39E-1C41-1843-AE3ADBE501EF} = {9E72FEB9-A626-4312-892B-CDD043879758}
{001D0817-1EED-4C04-821E-F815F148EC90} = {9E72FEB9-A626-4312-892B-CDD043879758}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C95FDF91-16F2-4A8B-A4BE-0E62D1B66718}

14
aspnet-core/LINGYUN.MicroService.SingleProject.sln

@ -688,6 +688,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Quartz.SqlInsta
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.UI.Navigation.VueVbenAdmin5", "modules\platform\LINGYUN.Abp.UI.Navigation.VueVbenAdmin5\LINGYUN.Abp.UI.Navigation.VueVbenAdmin5.csproj", "{113167DA-602A-4EBE-9357-D83C090DBA3F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.Web.OAuth", "modules\account\LINGYUN.Abp.Account.Web.OAuth\LINGYUN.Abp.Account.Web.OAuth.csproj", "{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LINGYUN.Abp.Account.OAuth", "modules\account\LINGYUN.Abp.Account.OAuth\LINGYUN.Abp.Account.OAuth.csproj", "{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1830,6 +1834,14 @@ Global
{113167DA-602A-4EBE-9357-D83C090DBA3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{113167DA-602A-4EBE-9357-D83C090DBA3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{113167DA-602A-4EBE-9357-D83C090DBA3F}.Release|Any CPU.Build.0 = Release|Any CPU
{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7}.Release|Any CPU.Build.0 = Release|Any CPU
{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2165,6 +2177,8 @@ Global
{B8D0DCBD-8D03-E450-0258-D0660A356BF0} = {22295ECE-A973-43F0-A6F2-982F7DB02F4E}
{A8586A5A-A213-4C66-819C-E692187B1F39} = {91EE5D5B-B6DF-43F1-BC09-1A982719A34B}
{113167DA-602A-4EBE-9357-D83C090DBA3F} = {F3449D35-8671-4BF6-8D1B-EFBB8AFD61DD}
{2379F502-BBBD-4BF2-91F7-D0E5C61E91B7} = {4F837B81-EA7D-472A-8482-3D5A730DF810}
{2E4C437A-989D-68D9-C5FB-1AE085B2CBC8} = {4F837B81-EA7D-472A-8482-3D5A730DF810}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {711A43C0-A2F8-4E5C-9B9F-F2551E4B3FF1}

1
aspnet-core/framework/security/LINGYUN.Abp.Claims.Mapping/LINGYUN.Abp.Claims.Mapping.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Security" />
<PackageReference Include="IdentityModel" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" />
</ItemGroup>
</Project>

131
aspnet-core/framework/security/LINGYUN.Abp.Claims.Mapping/Microsoft/IdentityModel/Tokens/TokenWildcardIssuerValidator.cs

@ -0,0 +1,131 @@
using Microsoft.IdentityModel.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Volo.Abp.Text.Formatting;
namespace Microsoft.IdentityModel.Tokens;
/// <summary>
/// Copy from: https://github.com/maliming/Owl.TokenWildcardIssuerValidator
/// https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Tokens/Validators.cs#L207
/// </summary>
public static class TokenWildcardIssuerValidator
{
private const string IDX10204 = "IDX10204: Unable to validate issuer. validationParameters.ValidIssuer is null or whitespace AND validationParameters.ValidIssuers is null.";
private const string IDX10205 = "IDX10205: Issuer validation failed. Issuer: '{0}'. Did not match: validationParameters.ValidIssuer: '{1}' or validationParameters.ValidIssuers: '{2}'.";
private const string IDX10211 = "IDX10211: Unable to validate issuer. The 'issuer' parameter is null or whitespace";
private const string IDX10235 = "IDX10235: ValidateIssuer property on ValidationParameters is set to false. Exiting without validating the issuer.";
private const string IDX10236 = "IDX10236: Issuer Validated.Issuer: '{0}'";
public static readonly IssuerValidator IssuerValidator = (issuer, token, validationParameters) =>
{
if (validationParameters == null)
{
throw LogHelper.LogArgumentNullException(nameof(validationParameters));
}
if (!validationParameters.ValidateIssuer)
{
LogHelper.LogInformation(IDX10235);
return issuer;
}
if (string.IsNullOrWhiteSpace(issuer))
{
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(IDX10211)
{
InvalidIssuer = issuer
});
}
// Throw if all possible places to validate against are null or empty
if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) &&
validationParameters.ValidIssuers == null)
{
throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidIssuerException(IDX10204)
{
InvalidIssuer = issuer
});
}
if (string.Equals(validationParameters.ValidIssuer, issuer, StringComparison.Ordinal))
{
LogHelper.LogInformation(IDX10236, issuer);
return issuer;
}
if (!string.IsNullOrWhiteSpace(validationParameters.ValidIssuer))
{
var extractResult = FormattedStringValueExtracter.Extract(issuer, validationParameters.ValidIssuer, ignoreCase: true);
if (extractResult.IsMatch &&
extractResult.Matches.Aggregate(validationParameters.ValidIssuer, (current, nameValue) => current.Replace($"{{{nameValue.Name}}}", nameValue.Value))
.IndexOf(issuer, StringComparison.OrdinalIgnoreCase) >= 0)
{
return issuer;
}
}
if (null != validationParameters.ValidIssuers)
{
foreach (var str in validationParameters.ValidIssuers)
{
if (string.IsNullOrEmpty(str))
{
LogHelper.LogInformation(IDX10235);
continue;
}
if (string.Equals(str, issuer, StringComparison.Ordinal))
{
LogHelper.LogInformation(IDX10236, issuer);
return issuer;
}
var extractResult = FormattedStringValueExtracter.Extract(issuer, str, ignoreCase: true);
if (extractResult.IsMatch &&
extractResult.Matches.Aggregate(str, (current, nameValue) => current.Replace($"{{{nameValue.Name}}}", nameValue.Value))
.IndexOf(issuer, StringComparison.OrdinalIgnoreCase) >= 0)
{
return issuer;
}
}
}
throw LogHelper.LogExceptionMessage(
new SecurityTokenInvalidIssuerException(LogHelper.FormatInvariant(IDX10205, issuer,
(validationParameters.ValidIssuer ?? "null"),
SerializeAsSingleCommaDelimitedString(validationParameters.ValidIssuers)))
{
InvalidIssuer = issuer
});
};
private static string SerializeAsSingleCommaDelimitedString(IEnumerable<string> strings)
{
if (strings == null)
{
return Utility.Null;
}
var sb = new StringBuilder();
var first = true;
foreach (var str in strings)
{
if (first)
{
sb.AppendFormat(CultureInfo.InvariantCulture, "{0}", str ?? Utility.Null);
first = false;
}
else
{
sb.AppendFormat(CultureInfo.InvariantCulture, ", {0}", str ?? Utility.Null);
}
}
return first ? Utility.Empty : sb.ToString();
}
}

12
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Features/WeChatOfficialFeatureDefinitionProvider.cs

@ -19,12 +19,12 @@ public class WeChatOfficialFeatureDefinitionProvider : FeatureDefinitionProvider
description: L("Features:WeChat.Official.EnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
officialEnableFeature.CreateChild(
name: WeChatOfficialFeatures.EnableAuthorization,
defaultValue: true.ToString(),
displayName: L("Features:WeChat.Official.EnableAuthorization"),
description: L("Features:WeChat.Official.EnableAuthorizationDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
//officialEnableFeature.CreateChild(
// name: WeChatOfficialFeatures.EnableAuthorization,
// defaultValue: true.ToString(),
// displayName: L("Features:WeChat.Official.EnableAuthorization"),
// description: L("Features:WeChat.Official.EnableAuthorizationDesc"),
// valueType: new ToggleStringValueType(new BooleanValueValidator()));
}
protected LocalizableString L(string name)

2
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.Official/LINGYUN/Abp/WeChat/Official/Features/WeChatOfficialFeatures.cs

@ -8,5 +8,5 @@ public static class WeChatOfficialFeatures
public const string Enable = GroupName + ".Enable";
public const string EnableAuthorization = GroupName + ".EnableAuthorization";
//public const string EnableAuthorization = GroupName + ".EnableAuthorization";
}

24
aspnet-core/framework/wechat/LINGYUN.Abp.WeChat.SettingManagement/LINGYUN/Abp/WeChat/SettingManagement/WeChatSettingAppService.cs

@ -161,6 +161,30 @@ public class WeChatSettingAppService : ApplicationService, IWeChatSettingAppServ
await SettingManager.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId, providerName, providerKey),
ValueType.String,
providerName);
workConnectionSetting.AddDetail(
await SettingDefinitionManager.GetAsync(WeChatWorkSettingNames.Connection.AgentId),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId, providerName, providerKey),
ValueType.String,
providerName);
workConnectionSetting.AddDetail(
await SettingDefinitionManager.GetAsync(WeChatWorkSettingNames.Connection.Secret),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(WeChatWorkSettingNames.Connection.Secret, providerName, providerKey),
ValueType.String,
providerName);
workConnectionSetting.AddDetail(
await SettingDefinitionManager.GetAsync(WeChatWorkSettingNames.Connection.Token),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(WeChatWorkSettingNames.Connection.Token, providerName, providerKey),
ValueType.String,
providerName);
workConnectionSetting.AddDetail(
await SettingDefinitionManager.GetAsync(WeChatWorkSettingNames.Connection.EncodingAESKey),
StringLocalizerFactory,
await SettingManager.GetOrNullAsync(WeChatWorkSettingNames.Connection.EncodingAESKey, providerName, providerKey),
ValueType.String,
providerName);
settingGroups.AddGroup(wechatWorkSettingGroup);
#endregion

62
aspnet-core/migrations/LY.MicroService.Applications.Single.EntityFrameworkCore/DataSeeder/ClientDataSeederContributor.cs

@ -204,6 +204,68 @@ public class ClientDataSeederContributor : IDataSeedContributor, ITransientDepen
await _permissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, internalServiceClientId, internalServicePermissions);
}
}
var oauthClientId = configurationSection["OAuthClient:ClientId"];
if (!oauthClientId.IsNullOrWhiteSpace())
{
var oauthClientRootUrl = configurationSection["OAuthClient:RootUrl"].EnsureEndsWith('/');
if (await _applicationRepository.FindByClientIdAsync(oauthClientId) == null)
{
await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor
{
ClientId = oauthClientId,
ClientSecret = null,
ApplicationType = OpenIddictConstants.ApplicationTypes.Web,
ConsentType = OpenIddictConstants.ConsentTypes.Implicit,
DisplayName = "OAuth Client",
PostLogoutRedirectUris =
{
new Uri(oauthClientRootUrl + "signout-callback"),
new Uri(oauthClientRootUrl)
},
RedirectUris =
{
new Uri(oauthClientRootUrl + "/signin-callback"),
new Uri(oauthClientRootUrl)
},
Permissions =
{
OpenIddictConstants.Permissions.Endpoints.Authorization,
OpenIddictConstants.Permissions.Endpoints.Token,
OpenIddictConstants.Permissions.Endpoints.DeviceAuthorization,
OpenIddictConstants.Permissions.Endpoints.Introspection,
OpenIddictConstants.Permissions.Endpoints.Revocation,
OpenIddictConstants.Permissions.Endpoints.EndSession,
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken,
OpenIddictConstants.Permissions.ResponseTypes.Code,
OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken,
OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken,
OpenIddictConstants.Permissions.ResponseTypes.CodeToken,
OpenIddictConstants.Permissions.ResponseTypes.IdToken,
OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken,
OpenIddictConstants.Permissions.ResponseTypes.None,
OpenIddictConstants.Permissions.ResponseTypes.Token,
OpenIddictConstants.Permissions.Scopes.Roles,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Prefixes.Scope + scope
}
});
var oauthClientPermissions = new string[1]
{
"AbpIdentity.UserLookup"
};
await _permissionDataSeeder.SeedAsync(ClientPermissionValueProvider.ProviderName, oauthClientId, oauthClientPermissions);
}
}
}
#endregion

2
aspnet-core/migrations/LY.MicroService.WebhooksManagement.EntityFrameworkCore/LY.MicroService.WebhooksManagement.EntityFrameworkCore.csproj

@ -18,7 +18,7 @@
<PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" />
<PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" />
<PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" VersionOverride="9.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
</ItemGroup>
<ItemGroup>

3
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

26
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<AssemblyName>LINGYUN.Abp.Account.OAuth</AssemblyName>
<PackageId>LINGYUN.Abp.Account.OAuth</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<None Remove="LINGYUN\Abp\Account\OAuth\Localization\Resources\*.json" />
<EmbeddedResource Include="LINGYUN\Abp\Account\OAuth\Localization\Resources\*.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Volo.Abp.Features" />
<PackageReference Include="Volo.Abp.Settings" />
</ItemGroup>
</Project>

28
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs

@ -0,0 +1,28 @@
using LINGYUN.Abp.Account.OAuth.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.Settings;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Account.OAuth;
[DependsOn(typeof(AbpFeaturesModule))]
[DependsOn(typeof(AbpSettingsModule))]
public class AbpAccountOAuthModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAccountOAuthModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<AccountOAuthResource>()
.AddVirtualJson("/LINGYUN/Abp/Account/OAuth/Localization/Resources");
});
}
}

52
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs

@ -0,0 +1,52 @@
using LINGYUN.Abp.Account.OAuth.Localization;
using Volo.Abp.Features;
using Volo.Abp.Localization;
using Volo.Abp.Validation.StringValues;
namespace LINGYUN.Abp.Account.OAuth.Features;
public class AccountOAuthFeatureDefinitionProvider : FeatureDefinitionProvider
{
public override void Define(IFeatureDefinitionContext context)
{
var group = context.AddGroup(
name: AccountOAuthFeatureNames.GroupName,
displayName: L("Features:ExternalOAuthLogin"));
group.AddFeature(
name: AccountOAuthFeatureNames.GitHub.Enable,
defaultValue: "false",
displayName: L("Features:GithubOAuthEnable"),
description: L("Features:GithubOAuthEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
group.AddFeature(
name: AccountOAuthFeatureNames.QQ.Enable,
defaultValue: "false",
displayName: L("Features:QQOAuthEnable"),
description: L("Features:QQOAuthEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
group.AddFeature(
name: AccountOAuthFeatureNames.WeChat.Enable,
defaultValue: "false",
displayName: L("Features:WeChatOAuthEnable"),
description: L("Features:WeChatOAuthEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
group.AddFeature(
name: AccountOAuthFeatureNames.WeCom.Enable,
defaultValue: "false",
displayName: L("Features:WeComOAuthEnable"),
description: L("Features:WeComOAuthEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
group.AddFeature(
name: AccountOAuthFeatureNames.Bilibili.Enable,
defaultValue: "false",
displayName: L("Features:BilibiliOAuthEnable"),
description: L("Features:BilibiliOAuthEnableDesc"),
valueType: new ToggleStringValueType(new BooleanValueValidator()));
}
private static LocalizableString L(string name)
{
return LocalizableString.Create<AccountOAuthResource>(name);
}
}

46
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs

@ -0,0 +1,46 @@
namespace LINGYUN.Abp.Account.OAuth.Features;
public static class AccountOAuthFeatureNames
{
public const string GroupName = "Abp.Account.OAuth";
public static class GitHub
{
public const string Prefix = GroupName + ".GitHub";
/// <summary>
/// 启用Github认证登录
/// </summary>
public const string Enable = Prefix + ".Enable";
}
public static class QQ
{
public const string Prefix = GroupName + ".QQ";
/// <summary>
/// 启用QQ认证登录
/// </summary>
public const string Enable = Prefix + ".Enable";
}
public static class WeChat
{
public const string Prefix = GroupName + ".WeChat";
/// <summary>
/// 启用微信认证登录
/// </summary>
public const string Enable = Prefix + ".Enable";
}
public static class WeCom
{
public const string Prefix = GroupName + ".WeCom";
/// <summary>
/// 启用企业微信认证登录
/// </summary>
public const string Enable = Prefix + ".Enable";
}
public static class Bilibili
{
public const string Prefix = GroupName + ".Bilibili";
/// <summary>
/// 启用Bilibili认证登录
/// </summary>
public const string Enable = Prefix + ".Enable";
}
}

8
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs

@ -0,0 +1,8 @@
using Volo.Abp.Localization;
namespace LINGYUN.Abp.Account.OAuth.Localization;
[LocalizationResourceName("AbpAccountOAuth")]
public class AccountOAuthResource
{
}

36
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json

@ -0,0 +1,36 @@
{
"culture": "en",
"texts": {
"Permission:ExternalOAuthLogin": "External Oauth Login",
"Features:ExternalOAuthLogin": "External Oauth Login",
"Features:GithubOAuthEnable": "GitHub",
"Features:GithubOAuthEnableDesc": "Enable to enable the application to support login via a GitHub account.",
"Features:QQOAuthEnable": "QQ",
"Features:QQOAuthEnableDesc": "Enable to enable the application to support login via QQ account.",
"Features:WeChatOAuthEnable": "WeChat",
"Features:WeChatOAuthEnableDesc": "Enable to enable the application to support login via the wechat official account.",
"Features:WeComOAuthEnable": "WeCom",
"Features:WeComOAuthEnableDesc": "Enable to enable the application to support login via wecom.",
"Features:BilibiliOAuthEnable": "Bilibili",
"Features:BilibiliOAuthEnableDesc": "Enable to allow the application to support login via a Bilibili account.",
"Settings:ExternalOAuthLogin": "External Oauth Login",
"Settings:GitHubAuth": "GitHub",
"Settings:GitHubClientId": "Client Id",
"Settings:GitHubClientIdDesc": "The client ID received from GitHub during registration. for details: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps",
"Settings:GitHubClientSecret": "Client Secret",
"Settings:GitHubClientSecretDesc": "The client key of the OAuth application that you received from GitHub. for details: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps",
"Settings:BilibiliAuth": "Bilibili",
"Settings:BilibiliClientId": "Client Id",
"Settings:BilibiliClientIdDesc": "Client Id, for details: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB",
"Settings:BilibiliClientSecret": "Client Secret",
"Settings:BilibiliClientSecretDesc": "Client Secret, for details: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB",
"OAuth:Microsoft": "Microsoft",
"OAuth:Twitter": "Twitter",
"OAuth:GitHub": "GitHub",
"OAuth:Google": "Google",
"OAuth:QQ": "QQ",
"OAuth:Weixin": "WeChat",
"OAuth:WorkWeixin": "WeCom",
"OAuth:Bilibili": "Bilibili"
}
}

36
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json

@ -0,0 +1,36 @@
{
"culture": "zh-Hans",
"texts": {
"Permission:ExternalOAuthLogin": "外部登录",
"Features:ExternalOAuthLogin": "外部登录",
"Features:GithubOAuthEnable": "GitHub认证",
"Features:GithubOAuthEnableDesc": "启用以使应用程序支持通过GitHub账号登录.",
"Features:QQOAuthEnable": "QQ认证",
"Features:QQOAuthEnableDesc": "启用以使应用程序支持通过QQ账号登录.",
"Features:WeChatOAuthEnable": "微信认证",
"Features:WeChatOAuthEnableDesc": "启用以使应用程序支持通过微信公众号登录.",
"Features:WeComOAuthEnable": "企业微信认证",
"Features:WeComOAuthEnableDesc": "启用以使应用程序支持通过企业微信登录.",
"Features:BilibiliOAuthEnable": "Bilibili认证",
"Features:BilibiliOAuthEnableDesc": "启用以使应用程序支持通过Bilibili账号登录.",
"Settings:ExternalOAuthLogin": "外部登录",
"Settings:GitHubAuth": "GitHub登录",
"Settings:GitHubClientId": "Client Id",
"Settings:GitHubClientIdDesc": "注册时从 GitHub 收到的客户端 ID.详见: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps",
"Settings:GitHubClientSecret": "Client Secret",
"Settings:GitHubClientSecretDesc": "您从 GitHub 收到的 OAuth 应用程序的客户端密钥.详见: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps",
"Settings:BilibiliAuth": "Bilibili登录",
"Settings:BilibiliClientId": "Client Id",
"Settings:BilibiliClientIdDesc": "应用Id, 详见: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB",
"Settings:BilibiliClientSecret": "Client Secret",
"Settings:BilibiliClientSecretDesc": "应用密钥, 详见: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB",
"OAuth:Microsoft": "Microsoft",
"OAuth:Twitter": "Twitter",
"OAuth:GitHub": "GitHub",
"OAuth:Google": "Google",
"OAuth:QQ": "QQ",
"OAuth:Weixin": "微信",
"OAuth:WorkWeixin": "企业微信",
"OAuth:Bilibili": "Bilibili"
}
}

77
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs

@ -0,0 +1,77 @@
using LINGYUN.Abp.Account.OAuth.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.OAuth.Settings;
public class AccountOAuthSettingDefinitionProvider : SettingDefinitionProvider
{
public override void Define(ISettingDefinitionContext context)
{
context.Add(GetGitHubSettings());
context.Add(GetBilibiliSettings());
}
private SettingDefinition[] GetGitHubSettings()
{
return new SettingDefinition[]
{
new SettingDefinition(
AccountOAuthSettingNames.GitHub.ClientId,
displayName: L("Settings:GitHubClientId"),
description: L("Settings:GitHubClientIdDesc"),
isVisibleToClients: false,
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
AccountOAuthSettingNames.GitHub.ClientSecret,
displayName: L("Settings:GitHubClientSecret"),
description: L("Settings:GitHubClientSecretDesc"),
isVisibleToClients: false,
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
};
}
private SettingDefinition[] GetBilibiliSettings()
{
return new SettingDefinition[]
{
new SettingDefinition(
AccountOAuthSettingNames.Bilibili.ClientId,
displayName: L("Settings:BilibiliClientId"),
description: L("Settings:BilibiliClientIdDesc"),
isVisibleToClients: false,
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
new SettingDefinition(
AccountOAuthSettingNames.Bilibili.ClientSecret,
displayName: L("Settings:BilibiliClientSecret"),
description: L("Settings:BilibiliClientSecretDesc"),
isVisibleToClients: false,
isEncrypted: true)
.WithProviders(
DefaultValueSettingValueProvider.ProviderName,
ConfigurationSettingValueProvider.ProviderName,
GlobalSettingValueProvider.ProviderName,
TenantSettingValueProvider.ProviderName),
};
}
protected ILocalizableString L(string name)
{
return LocalizableString.Create<AccountOAuthResource>(name);
}
}

30
aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs

@ -0,0 +1,30 @@
namespace LINGYUN.Abp.Account.OAuth.Settings;
public static class AccountOAuthSettingNames
{
public const string GroupName = "Abp.Account.OAuth";
public static class GitHub
{
public const string Prefix = GroupName + ".GitHub";
/// <summary>
/// ClientId
/// </summary>
public const string ClientId = Prefix + ".ClientId";
/// <summary>
/// ClientSecret
/// </summary>
public const string ClientSecret = Prefix + ".ClientSecret";
}
public static class Bilibili
{
public const string Prefix = GroupName + ".Bilibili";
/// <summary>
/// ClientId
/// </summary>
public const string ClientId = Prefix + ".ClientId";
/// <summary>
/// ClientSecret
/// </summary>
public const string ClientSecret = Prefix + ".ClientSecret";
}
}

188
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/IdentityServerLoginModel.cs

@ -0,0 +1,188 @@
using IdentityServer4.Events;
using IdentityServer4.Models;
using IdentityServer4.Services;
using IdentityServer4.Stores;
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.IdentityServer.AspNetIdentity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Settings;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
{
/// <summary>
/// 重写登录模型,实现双因素登录
/// </summary>
[Dependency(ReplaceServices = true)]
[ExposeServices(
typeof(LINGYUN.Abp.Account.Web.Pages.Account.LoginModel),
typeof(IdentityServerLoginModel))]
public class IdentityServerLoginModel : LINGYUN.Abp.Account.Web.Pages.Account.LoginModel
{
protected IIdentityServerInteractionService Interaction { get; }
protected IEventService IdentityServerEvents { get; }
protected IClientStore ClientStore { get; }
public IdentityServerLoginModel(
IExternalProviderService externalProviderService,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IOptions<IdentityOptions> identityOptions,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache,
IIdentityServerInteractionService interaction,
IEventService identityServerEvents,
IClientStore clientStore)
: base(externalProviderService, schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache)
{
Interaction = interaction;
ClientStore = clientStore;
IdentityServerEvents = identityServerEvents;
}
public override async Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);
if (context != null)
{
// TODO: Find a proper cancel way.
// ShowCancelButton = true;
LoginInput.UserNameOrEmailAddress = context.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = context.Parameters[TenantResolverConsts.DefaultTenantKey];
if (!string.IsNullOrEmpty(tenant))
{
CurrentTenant.Change(Guid.Parse(tenant));
Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant);
}
}
if (context?.IdP != null)
{
LoginInput.UserNameOrEmailAddress = context.LoginHint;
ExternalProviders = new[] { new ExternalLoginProviderModel { AuthenticationScheme = context.IdP } };
return Page();
}
var providers = await GetExternalProviders();
ExternalProviders = providers.ToList();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
if (context?.Client?.ClientId != null)
{
var client = await ClientStore.FindEnabledClientByIdAsync(context?.Client?.ClientId);
if (client != null)
{
EnableLocalLogin = client.EnableLocalLogin;
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
{
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
}
}
}
if (IsExternalLoginOnly)
{
return await base.OnPostExternalLogin(providers.First().AuthenticationScheme);
}
return Page();
}
public override async Task<IActionResult> OnPostAsync(string action)
{
var context = await Interaction.GetAuthorizationContextAsync(ReturnUrl);
if (action == "Cancel")
{
if (context == null)
{
return Redirect("~/");
}
await Interaction.GrantConsentAsync(context, new ConsentResponse()
{
Error = AuthorizationError.AccessDenied
});
return Redirect(ReturnUrl);
}
await CheckLocalLoginAsync();
ValidateModel();
await IdentityOptions.SetAsync();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
await ReplaceEmailToUsernameOfInputIfNeeds();
var result = await SignInManager.PasswordSignInAsync(
LoginInput.UserNameOrEmailAddress,
LoginInput.Password,
LoginInput.RememberMe,
true
);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = result.ToIdentitySecurityLogAction(),
UserName = LoginInput.UserNameOrEmailAddress,
ClientId = context?.Client?.ClientId
});
if (result.RequiresTwoFactor)
{
return await TwoFactorLoginResultAsync();
}
if (result.IsLockedOut)
{
return await HandleUserLockedOut();
}
if (result.IsNotAllowed)
{
return await HandleUserNotAllowed();
}
if (!result.Succeeded)
{
return await HandleUserNameOrPasswordInvalid();
}
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
var user = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
Debug.Assert(user != null, nameof(user) + " != null");
await IdentityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
}
}

68
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.IdentityServer/Pages/Account/TwoFactorSupportedLoginModel.cs

@ -1,68 +0,0 @@
using IdentityServer4.Services;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.IdentityServer.Pages.Account
{
/// <summary>
/// 重写登录模型,实现双因素登录
/// </summary>
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LoginModel), typeof(IdentityServerSupportedLoginModel))]
public class TwoFactorSupportedLoginModel : IdentityServerSupportedLoginModel
{
public TwoFactorSupportedLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IOptions<IdentityOptions> identityOptions,
IIdentityServerInteractionService interaction,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache,
IClientStore clientStore,
IEventService identityServerEvents)
: base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache, interaction, clientStore, identityServerEvents)
{
}
protected async override Task<List<ExternalProviderModel>> GetExternalProviders()
{
var providers = await base.GetExternalProviders();
foreach (var provider in providers)
{
var localizedDisplayName = L[provider.DisplayName];
if (localizedDisplayName.ResourceNotFound)
{
localizedDisplayName = L["AuthenticationScheme:" + provider.DisplayName];
}
if (!localizedDisplayName.ResourceNotFound)
{
provider.DisplayName = localizedDisplayName.Value;
}
}
return providers;
}
protected override Task<IActionResult> TwoFactorLoginResultAsync()
{
// 重定向双因素认证页面
return Task.FromResult<IActionResult>(RedirectToPage("SendCode", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
}));
}
}
}

105
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs

@ -0,0 +1,105 @@
using AspNet.Security.OAuth.Bilibili;
using AspNet.Security.OAuth.GitHub;
using AspNet.Security.OAuth.QQ;
using AspNet.Security.OAuth.Weixin;
using AspNet.Security.OAuth.WorkWeixin;
using LINGYUN.Abp.Account.OAuth;
using LINGYUN.Abp.Account.OAuth.Localization;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.Bilibili;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.GitHub;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.QQ;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeChat;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeCom;
using LINGYUN.Abp.Account.Web.OAuth.Microsoft.Extensions.DependencyInjection;
using LINGYUN.Abp.Tencent.QQ;
using LINGYUN.Abp.WeChat.Official;
using LINGYUN.Abp.WeChat.Work;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Account.Localization;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
namespace LINGYUN.Abp.Account.Web.OAuth;
[DependsOn(typeof(AbpAccountWebModule))]
[DependsOn(typeof(AbpAccountOAuthModule))]
[DependsOn(typeof(AbpTencentQQModule))]
[DependsOn(typeof(AbpWeChatOfficialModule))]
[DependsOn(typeof(AbpWeChatWorkModule))]
public class AbpAccountWebOAuthModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
context.Services.PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options =>
{
options.AddAssemblyResource(typeof(AccountResource), typeof(AbpAccountWebOAuthModule).Assembly);
});
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAccountWebOAuthModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpVirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<AbpAccountWebOAuthModule>("LINGYUN.Abp.Account.Web.OAuth");
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<AccountResource>()
.AddBaseTypes(typeof(AccountOAuthResource));
});
context.Services
.AddAuthentication()
.AddGitHub(options =>
{
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
options.Scope.Add("user:email");
}).UseSettingProvider<
GitHubAuthenticationOptions,
GitHubAuthenticationHandler,
GitHubAuthHandlerOptionsProvider>()
.AddQQ(options =>
{
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
}).UseSettingProvider<
QQAuthenticationOptions,
QQAuthenticationHandler,
QQAuthHandlerOptionsProvider>()
.AddWeixin(options =>
{
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
}).UseSettingProvider<
WeixinAuthenticationOptions,
WeixinAuthenticationHandler,
WeChatAuthHandlerOptionsProvider>()
.AddWorkWeixin(options =>
{
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
}).UseSettingProvider<
WorkWeixinAuthenticationOptions,
WorkWeixinAuthenticationHandler,
WeComAuthHandlerOptionsProvider>()
.AddBilibili(options =>
{
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
}).UseSettingProvider<
BilibiliAuthenticationOptions,
BilibiliAuthenticationHandler,
BilibiliAuthHandlerOptionsProvider>();
}
}

81
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs

@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders;
public class AccountAuthenticationRequestHandler<TOptions, THandler> : IAuthenticationRequestHandler
where TOptions : RemoteAuthenticationOptions, new()
where THandler : RemoteAuthenticationHandler<TOptions>
{
protected THandler InnerHandler { get; }
protected IOAuthHandlerOptionsProvider<TOptions> OptionsProvider { get; }
public AccountAuthenticationRequestHandler(
THandler innerHandler,
IOAuthHandlerOptionsProvider<TOptions> optionsProvider)
{
InnerHandler = innerHandler;
OptionsProvider = optionsProvider;
}
public virtual async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
await InnerHandler.InitializeAsync(scheme, context);
}
public virtual async Task<AuthenticateResult> AuthenticateAsync()
{
return await InnerHandler.AuthenticateAsync();
}
public virtual async Task ChallengeAsync(AuthenticationProperties? properties)
{
await InitializeOptionsAsync();
await InnerHandler.ChallengeAsync(properties);
}
public virtual async Task ForbidAsync(AuthenticationProperties? properties)
{
await InnerHandler.ForbidAsync(properties);
}
public async Task SignOutAsync(AuthenticationProperties properties)
{
if (!(InnerHandler is IAuthenticationSignOutHandler signOutHandler))
{
throw new InvalidOperationException($"The authentication handler registered for scheme '{InnerHandler.Scheme}' is '{InnerHandler.GetType().Name}' which cannot be used for SignOutAsync");
}
await InitializeOptionsAsync();
await signOutHandler.SignOutAsync(properties);
}
public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
{
if (!(InnerHandler is IAuthenticationSignInHandler signInHandler))
{
throw new InvalidOperationException($"The authentication handler registered for scheme '{InnerHandler.Scheme}' is '{InnerHandler.GetType().Name}' which cannot be used for SignInAsync");
}
await InitializeOptionsAsync();
await signInHandler.SignInAsync(user, properties);
}
public virtual async Task<bool> HandleRequestAsync()
{
if (await InnerHandler.ShouldHandleRequestAsync())
{
await InitializeOptionsAsync();
}
return await InnerHandler.HandleRequestAsync();
}
protected async virtual Task InitializeOptionsAsync()
{
await OptionsProvider.SetOptionsAsync(InnerHandler.Options);
}
}

29
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs

@ -0,0 +1,29 @@
using AspNet.Security.OAuth.Bilibili;
using LINGYUN.Abp.Account.OAuth.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.Bilibili;
public class BilibiliAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider<BilibiliAuthenticationOptions>
{
public BilibiliAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider)
{
}
public async override Task SetOptionsAsync(BilibiliAuthenticationOptions options)
{
var clientId = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientId);
var clientSecret = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientSecret);
if (!clientId.IsNullOrWhiteSpace())
{
options.ClientId = clientId;
}
if (!clientSecret.IsNullOrWhiteSpace())
{
options.ClientSecret = clientSecret;
}
}
}

29
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs

@ -0,0 +1,29 @@
using AspNet.Security.OAuth.GitHub;
using LINGYUN.Abp.Account.OAuth.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.GitHub;
public class GitHubAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider<GitHubAuthenticationOptions>
{
public GitHubAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider)
{
}
public async override Task SetOptionsAsync(GitHubAuthenticationOptions options)
{
var clientId = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientId);
var clientSecret = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientSecret);
if (!clientId.IsNullOrWhiteSpace())
{
options.ClientId = clientId;
}
if (!clientSecret.IsNullOrWhiteSpace())
{
options.ClientSecret = clientSecret;
}
}
}

10
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authentication;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders;
public interface IOAuthHandlerOptionsProvider<TOptions>
where TOptions : RemoteAuthenticationOptions, new()
{
Task SetOptionsAsync(TOptions options);
}

68
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs

@ -0,0 +1,68 @@
using AspNet.Security.OAuth.Bilibili;
using AspNet.Security.OAuth.GitHub;
using AspNet.Security.OAuth.QQ;
using AspNet.Security.OAuth.Weixin;
using AspNet.Security.OAuth.WorkWeixin;
using LINGYUN.Abp.Account.OAuth.Features;
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using LINGYUN.Abp.Account.Web.OAuth.Pages.Account.Components.ExternalProviders;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Localization;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Account.Localization;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders;
public class OAuthExternalProviderService : IExternalProviderService, ITransientDependency
{
private static readonly Dictionary<string, string> _providerFeaturesMap = new Dictionary<string, string>
{
[GitHubAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.GitHub.Enable,
[QQAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.QQ.Enable,
[WeixinAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.WeChat.Enable,
[WorkWeixinAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.WeCom.Enable,
[BilibiliAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.Bilibili.Enable
};
private readonly IFeatureChecker _featureChecker;
private readonly IStringLocalizer<AccountResource> _stringLocalizer;
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
public OAuthExternalProviderService(
IFeatureChecker featureChecker,
IStringLocalizer<AccountResource> stringLocalizer,
IAuthenticationSchemeProvider authenticationSchemeProvider)
{
_featureChecker = featureChecker;
_stringLocalizer = stringLocalizer;
_authenticationSchemeProvider = authenticationSchemeProvider;
}
public async virtual Task<List<ExternalLoginProviderModel>> GetAllAsync()
{
var models = new List<ExternalLoginProviderModel>();
var schemas = await _authenticationSchemeProvider.GetAllSchemesAsync();
foreach (var schema in schemas)
{
if (_providerFeaturesMap.TryGetValue(schema.Name, out var schemaFeature))
{
if (await _featureChecker.IsEnabledAsync(schemaFeature))
{
models.Add(new ExternalLoginProviderModel
{
Name = schema.Name,
AuthenticationScheme = schema.Name,
DisplayName = _stringLocalizer[$"OAuth:{schema.Name}"],
ComponentType = typeof(ExternalProviderViewComponent),
});
}
}
}
return models;
}
}

18
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs

@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Authentication;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders;
public abstract class OAuthHandlerOptionsProvider<TOptions> : IOAuthHandlerOptionsProvider<TOptions>, ITransientDependency
where TOptions : RemoteAuthenticationOptions, new()
{
protected ISettingProvider SettingProvider { get; }
public OAuthHandlerOptionsProvider(ISettingProvider settingProvider)
{
SettingProvider = settingProvider;
}
public abstract Task SetOptionsAsync(TOptions options);
}

29
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs

@ -0,0 +1,29 @@
using AspNet.Security.OAuth.QQ;
using LINGYUN.Abp.Tencent.QQ.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.QQ;
public class QQAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider<QQAuthenticationOptions>
{
public QQAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider)
{
}
public async override Task SetOptionsAsync(QQAuthenticationOptions options)
{
var clientId = await SettingProvider.GetOrNullAsync(TencentQQSettingNames.QQConnect.AppId);
var clientSecret = await SettingProvider.GetOrNullAsync(TencentQQSettingNames.QQConnect.AppKey);
if (!clientId.IsNullOrWhiteSpace())
{
options.ClientId = clientId;
}
if (!clientSecret.IsNullOrWhiteSpace())
{
options.ClientSecret = clientSecret;
}
}
}

29
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs

@ -0,0 +1,29 @@
using AspNet.Security.OAuth.Weixin;
using LINGYUN.Abp.WeChat.Official.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeChat;
public class WeChatAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider<WeixinAuthenticationOptions>
{
public WeChatAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider)
{
}
public async override Task SetOptionsAsync(WeixinAuthenticationOptions options)
{
var clientId = await SettingProvider.GetOrNullAsync(WeChatOfficialSettingNames.AppId);
var clientSecret = await SettingProvider.GetOrNullAsync(WeChatOfficialSettingNames.AppSecret);
if (!clientId.IsNullOrWhiteSpace())
{
options.ClientId = clientId;
}
if (!clientSecret.IsNullOrWhiteSpace())
{
options.ClientSecret = clientSecret;
}
}
}

34
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs

@ -0,0 +1,34 @@
using AspNet.Security.OAuth.WorkWeixin;
using LINGYUN.Abp.WeChat.Work.Settings;
using System;
using System.Threading.Tasks;
using Volo.Abp.Settings;
namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeCom;
public class WeComAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider<WorkWeixinAuthenticationOptions>
{
public WeComAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider)
{
}
public async override Task SetOptionsAsync(WorkWeixinAuthenticationOptions options)
{
var clientId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId);
var clientSecret = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.Secret);
var agentId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId);
if (!clientId.IsNullOrWhiteSpace())
{
options.ClientId = clientId;
}
if (!clientSecret.IsNullOrWhiteSpace())
{
options.ClientSecret = clientSecret;
}
if (!agentId.IsNullOrWhiteSpace())
{
options.AgentId = agentId;
}
}
}

3
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

44
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj

@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>LINGYUN.Abp.Account.Web.OAuth</AssemblyName>
<PackageId>LINGYUN.Abp.Account.Web.OAuth</PackageId>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace>LINGYUN.Abp.Account.Web.OAuth</RootNamespace>
<OutputType>Library</OutputType>
<IsPackable>true</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="wwwroot\**\*.js" />
<EmbeddedResource Include="wwwroot\**\*.css" />
<EmbeddedResource Include="wwwroot\**\*.png" />
<Content Remove="wwwroot\**\*.js" />
<Content Remove="wwwroot\**\*.css" />
<Content Remove="wwwroot\**\*.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AspNet.Security.OAuth.Bilibili" />
<PackageReference Include="AspNet.Security.OAuth.GitHub" />
<PackageReference Include="AspNet.Security.OAuth.QQ" />
<PackageReference Include="AspNet.Security.OAuth.Weixin" />
<PackageReference Include="AspNet.Security.OAuth.WorkWeixin" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\framework\cloud-tencent\LINGYUN.Abp.Tencent.QQ\LINGYUN.Abp.Tencent.QQ.csproj" />
<ProjectReference Include="..\..\..\framework\wechat\LINGYUN.Abp.WeChat.Official\LINGYUN.Abp.WeChat.Official.csproj" />
<ProjectReference Include="..\..\..\framework\wechat\LINGYUN.Abp.WeChat.Work\LINGYUN.Abp.WeChat.Work.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.OAuth\LINGYUN.Abp.Account.OAuth.csproj" />
<ProjectReference Include="..\LINGYUN.Abp.Account.Web\LINGYUN.Abp.Account.Web.csproj" />
</ItemGroup>
</Project>

32
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs

@ -0,0 +1,32 @@
using JetBrains.Annotations;
using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Linq;
using Volo.Abp;
namespace LINGYUN.Abp.Account.Web.OAuth.Microsoft.Extensions.DependencyInjection;
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder UseSettingProvider<TOptions, THandler, TOptionsProvider>(
[NotNull] this AuthenticationBuilder authenticationBuilder)
where TOptions : RemoteAuthenticationOptions, new()
where THandler : RemoteAuthenticationHandler<TOptions>
where TOptionsProvider : IOAuthHandlerOptionsProvider<TOptions>
{
Check.NotNull(authenticationBuilder, nameof(authenticationBuilder));
var handler = authenticationBuilder.Services.LastOrDefault(x => x.ServiceType == typeof(THandler));
authenticationBuilder.Services.Replace(new ServiceDescriptor(
typeof(THandler),
provider => new AccountAuthenticationRequestHandler<TOptions, THandler>(
(THandler)ActivatorUtilities.CreateInstance(provider, typeof(THandler)),
provider.GetRequiredService<TOptionsProvider>()),
handler?.Lifetime ?? ServiceLifetime.Transient));
return authenticationBuilder;
}
}

14
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml

@ -0,0 +1,14 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel
<button
type="submit"
class="btn btn-outline-secondary m-1"
name="provider"
value="@Model.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", @Model.DisplayName]">
<img src="~/images/bilibili_logo_18x18.png" />
<span>@Model.DisplayName</span>
</button>

13
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs

@ -0,0 +1,13 @@
using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
namespace LINGYUN.Abp.Account.Web.OAuth.Pages.Account.Components.ExternalProviders;
public class ExternalProviderViewComponent : AbpViewComponent
{
public virtual IViewComponentResult Invoke(ExternalLoginProviderModel model)
{
return View($"~/Pages/Account/Components/ExternalProviders/{model.AuthenticationScheme}/Default.cshtml", model);
}
}

14
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml

@ -0,0 +1,14 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel
<button
type="submit"
class="btn btn-outline-secondary m-1"
name="provider"
value="@Model.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", @Model.DisplayName]">
<i class="fa fa-github"></i>
<span>@Model.DisplayName</span>
</button>

14
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml

@ -0,0 +1,14 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel
<button
type="submit"
class="btn btn-outline-secondary m-1"
name="provider"
value="@Model.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", @Model.DisplayName]">
<img src="~/images/qq_logo_15x18.png" />
<span>@Model.DisplayName</span>
</button>

14
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml

@ -0,0 +1,14 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel
<button
type="submit"
class="btn btn-outline-secondary m-1"
name="provider"
value="@Model.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", @Model.DisplayName]">
<i class="fa fa-weixin"></i>
<span>@Model.DisplayName</span>
</button>

13
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml

@ -0,0 +1,13 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel
<button
type="submit"
class="btn btn-outline-secondary m-1"
name="provider"
value="@Model.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", @Model.DisplayName]">
<img src="~/images/wecom_logo_77x18.png" />
</button>

12
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json

@ -0,0 +1,12 @@
{
"profiles": {
"LINGYUN.Abp.Account.Web.OAuth": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:50897;http://localhost:50898"
}
}
}

BIN
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/bilibili_logo_18x18.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/qq_logo_15x18.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

BIN
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/wecom_logo_77x18.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

60
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/OpenIddictLoginModel.cs

@ -0,0 +1,60 @@
using LINGYUN.Abp.Account.Web.ExternalProviders;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.Threading.Tasks;
using Volo.Abp.Account.Web;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.OpenIddict;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.OpenIddict.Pages.Account
{
[Dependency(ReplaceServices = true)]
[ExposeServices(
typeof(LINGYUN.Abp.Account.Web.Pages.Account.LoginModel),
typeof(OpenIddictLoginModel))]
public class OpenIddictLoginModel : LINGYUN.Abp.Account.Web.Pages.Account.LoginModel
{
protected AbpOpenIddictRequestHelper OpenIddictRequestHelper { get; }
public OpenIddictLoginModel(
IExternalProviderService externalProviderService,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IOptions<IdentityOptions> identityOptions,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache,
AbpOpenIddictRequestHelper openIddictRequestHelper)
: base(externalProviderService, schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache)
{
OpenIddictRequestHelper = openIddictRequestHelper;
}
public async override Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
var request = await OpenIddictRequestHelper.GetFromReturnUrlAsync(ReturnUrl);
if (request?.ClientId != null)
{
// TODO: Find a proper cancel way.
// ShowCancelButton = true;
LoginInput.UserNameOrEmailAddress = request.LoginHint;
//TODO: Reference AspNetCore MultiTenancy module and use options to get the tenant key!
var tenant = request.GetParameter(TenantResolverConsts.DefaultTenantKey)?.ToString();
if (!string.IsNullOrEmpty(tenant))
{
CurrentTenant.Change(Guid.Parse(tenant));
Response.Cookies.Append(TenantResolverConsts.DefaultTenantKey, tenant);
}
}
return await base.OnGetAsync();
}
}
}

64
aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OpenIddict/Pages/Account/TwoFactorSupportedLoginModel.cs

@ -1,64 +0,0 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.OpenIddict;
using IdentityOptions = Microsoft.AspNetCore.Identity.IdentityOptions;
namespace LINGYUN.Abp.Account.Web.OpenIddict.Pages.Account
{
/// <summary>
/// 重写登录模型,实现双因素登录
/// </summary>
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(LoginModel), typeof(OpenIddictSupportedLoginModel))]
public class TwoFactorSupportedLoginModel : OpenIddictSupportedLoginModel
{
public TwoFactorSupportedLoginModel(
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IOptions<IdentityOptions> identityOptions,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache,
AbpOpenIddictRequestHelper openIddictRequestHelper)
: base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache, openIddictRequestHelper)
{
}
protected async override Task<List<ExternalProviderModel>> GetExternalProviders()
{
var providers = await base.GetExternalProviders();
foreach (var provider in providers)
{
var localizedDisplayName = L[provider.DisplayName];
if (localizedDisplayName.ResourceNotFound)
{
localizedDisplayName = L["AuthenticationScheme:" + provider.DisplayName];
}
if (!localizedDisplayName.ResourceNotFound)
{
provider.DisplayName = localizedDisplayName.Value;
}
}
return providers;
}
protected override Task<IActionResult> TwoFactorLoginResultAsync()
{
// 重定向双因素认证页面
return Task.FromResult<IActionResult>(RedirectToPage("SendCode", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
}));
}
}
}

6
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountAuthenticationTypes.cs

@ -0,0 +1,6 @@
namespace LINGYUN.Abp.Account.Web;
public static class AbpAccountAuthenticationTypes
{
public const string ShouldChangePassword = "Abp.Account.ShouldChangePassword";
}

32
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/AbpAccountWebModule.cs

@ -1,14 +1,21 @@
using LINGYUN.Abp.Account.Emailing;
using LINGYUN.Abp.Account.Web.Bundling;
using LINGYUN.Abp.Account.Web.Pages.Account;
using LINGYUN.Abp.Account.Web.ProfileManagement;
using LINGYUN.Abp.Identity;
using LINGYUN.Abp.Identity.AspNetCore.QrCode;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using System;
using Volo.Abp.Account.Localization;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Account.Web.ProfileManagement;
using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.QRCode;
using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Bundling;
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
using Volo.Abp.Sms;
@ -53,17 +60,35 @@ public class AbpAccountWebModule : AbpModule
{
options.AddMaps<AbpAccountWebModule>(validate: true);
});
context.Services
.AddAuthentication()
.AddCookie(AbpAccountAuthenticationTypes.ShouldChangePassword, options =>
{
options.LoginPath = new PathString("/Account/Login");
options.ExpireTimeSpan = TimeSpan.FromMinutes(5.0);
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
});
}
private void ConfigureProfileManagementPage()
{
Configure<ProfileManagementPageOptions>(options =>
{
options.Contributors.Add(new SessionManagementPageContributor());
options.Contributors.Add(new ProfileManagementPageContributor());
});
Configure<AbpBundlingOptions>(options =>
{
options.StyleBundles
.Add(AccountBundles.Styles.UserLoginLink, bundle =>
{
bundle.AddContributors(typeof(UserLoginLinkStyleContributor));
});
options.ScriptBundles
.Configure(typeof(ManageModel).FullName,
configuration =>
@ -86,6 +111,11 @@ public class AbpAccountWebModule : AbpModule
// QrCode
configuration.AddContributors(typeof(QRCodeScriptContributor));
});
options.ScriptBundles
.Configure(AccountBundles.Scripts.ChangePassword, bundle =>
{
bundle.AddContributors(typeof(ChangePasswordScriptContributor));
});
});
}
}

18
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/AccountBundles.cs

@ -0,0 +1,18 @@
namespace LINGYUN.Abp.Account.Web.Bundling;
public static class AccountBundles
{
public static class Scripts
{
public const string Global = "Abp.Account";
public const string ChangePassword = Global + ".ChangePassword";
}
public static class Styles
{
public const string Global = "Abp.Account";
public const string UserLoginLink = Global + ".UserLoginLink";
}
}

17
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/ChangePasswordScriptContributor.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AspNetCore.Mvc.UI.Packages.JQuery;
using Volo.Abp.Modularity;
namespace LINGYUN.Abp.Account.Web.Bundling;
[DependsOn(typeof(JQueryScriptContributor))]
public class ChangePasswordScriptContributor : BundleContributor
{
public override Task ConfigureBundleAsync(BundleConfigurationContext context)
{
context.Files.Add("/Pages/Account/ChangePassword.js");
return Task.CompletedTask;
}
}

14
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Bundling/UserLoginLinkStyleContributor.cs

@ -0,0 +1,14 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
namespace LINGYUN.Abp.Account.Web.Bundling;
public class UserLoginLinkStyleContributor : BundleContributor
{
public override Task ConfigureBundleAsync(BundleConfigurationContext context)
{
context.Files.Add("/styles/user-login-link/fix-style.css");
return Task.CompletedTask;
}
}

10
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs

@ -0,0 +1,10 @@
using LINGYUN.Abp.Account.Web.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace LINGYUN.Abp.Account.Web.ExternalProviders;
public interface IExternalProviderService
{
Task<List<ExternalLoginProviderModel>> GetAllAsync();
}

5
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/LINGYUN.Abp.Account.Web.csproj

@ -38,7 +38,6 @@
<ItemGroup>
<PackageReference Include="Volo.Abp.Sms" />
<PackageReference Include="Volo.Abp.Account.Web" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
</ItemGroup>
<ItemGroup>
@ -48,4 +47,8 @@
<ProjectReference Include="..\LINGYUN.Abp.Account.Emailing\LINGYUN.Abp.Account.Emailing.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
</ItemGroup>
</Project>

11
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs

@ -0,0 +1,11 @@
using System;
namespace LINGYUN.Abp.Account.Web.Models;
public class ExternalLoginProviderModel
{
public Type ComponentType { get; set; }
public string Name { get; set; }
public string DisplayName { get; set; }
public string AuthenticationScheme { get; set; }
}

8
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Modules/Account/Components/Toolbar/UserLoginLink/Default.cshtml

@ -0,0 +1,8 @@
@using Localization.Resources.AbpUi
@using Microsoft.AspNetCore.Mvc.Localization
@using LINGYUN.Abp.Account.Web.Bundling;
@inject IHtmlLocalizer<AbpUiResource> L
<abp-style-bundle name="@AccountBundles.Styles.UserLoginLink" />
<a class="nav-link fix-margin" role="button" href="~/Account/Login">@L["Login"]</a>

4
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Modules/_ViewImports.cshtml

@ -0,0 +1,4 @@
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bundling

46
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml

@ -0,0 +1,46 @@
@page
@using Volo.Abp.Account.Localization
@using Volo.Abp.Identity
@using Volo.Abp.Users
@using Microsoft.AspNetCore.Mvc.Localization
@using LINGYUN.Abp.Account.Web.Bundling;
@using LINGYUN.Abp.Account.Web.Pages.Account
@inject IHtmlLocalizer<AccountResource> L
@model LINGYUN.Abp.Account.Web.Pages.Account.ChangePasswordModel
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["ChangePassword"]</h4>
<form id="ChangePasswordForm" method="post">
<div class="mb-3">
@if (!Model.HideOldPasswordInput)
{
<label asp-for="Input.CurrentPassword" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="Input.CurrentPassword" />
<button class="btn btn-secondary password-visibility-button" type="button"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="Input.CurrentPassword"></span>
<br />
}
<label asp-for="Input.NewPassword" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="Input.NewPassword" />
<button class="btn btn-secondary password-visibility-button" type="button"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="Input.NewPassword"></span><br />
<label asp-for="Input.NewPasswordConfirm" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="Input.NewPasswordConfirm" />
<button class="btn btn-secondary password-visibility-button" type="button"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="Input.NewPasswordConfirm"></span>
</div>
<abp-button type="submit" button-type="Primary" text="@L["Submit"].Value" />
</form>
</div>
</div>
<abp-script-bundle name="@AccountBundles.Scripts.ChangePassword" />

174
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs

@ -0,0 +1,174 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Principal;
using System.Threading.Tasks;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Validation;
namespace LINGYUN.Abp.Account.Web.Pages.Account;
public class UserInfoModel : IMultiTenant
{
public Guid Id { get; set; }
public Guid? TenantId { get; set; }
}
public class ChangePasswordInputModel
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:CurrentPassword")]
[DataType(DataType.Password)]
[DisableAuditing]
public string CurrentPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPassword")]
[DataType(DataType.Password)]
[DisableAuditing]
public string NewPassword { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[Display(Name = "DisplayName:NewPasswordConfirm")]
[DataType(DataType.Password)]
[DisableAuditing]
public string NewPasswordConfirm { get; set; }
}
public class ChangePasswordModel : AccountPageModel
{
[BindProperty]
public UserInfoModel UserInfo { get; set; }
[BindProperty]
public ChangePasswordInputModel Input { get; set; }
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; }
[BindProperty(SupportsGet = true)]
public bool RememberMe { get; set; }
public bool HideOldPasswordInput { get; set; }
public AbpSignInManager AbpSignInManager => LazyServiceProvider.LazyGetRequiredService<AbpSignInManager>();
public IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache => LazyServiceProvider.LazyGetRequiredService<IdentityDynamicClaimsPrincipalContributorCache>();
public async virtual Task<IActionResult> OnGetAsync()
{
Input = new ChangePasswordInputModel();
UserInfo = await GetCurrentUser();
if (UserInfo == null || UserInfo.TenantId != CurrentTenant.Id)
{
await HttpContext.SignOutAsync(AbpAccountAuthenticationTypes.ShouldChangePassword);
return RedirectToPage("/Login", new { ReturnUrl, ReturnUrlHash });
}
HideOldPasswordInput = (await UserManager.GetByIdAsync(UserInfo.Id)).PasswordHash == null;
return Page();
}
public async virtual Task<IActionResult> OnPostAsync()
{
if (Input.CurrentPassword == Input.NewPassword)
{
Alerts.Warning(L["NewPasswordSameAsOld"]);
return Page();
}
if (Input.NewPassword != Input.NewPasswordConfirm)
{
Alerts.Warning(L["NewPasswordConfirmFailed"]);
return Page();
}
var userInfo = await GetCurrentUser();
if (userInfo != null)
{
if (userInfo.TenantId == CurrentTenant.Id)
{
try
{
await IdentityOptions.SetAsync();
var user = await UserManager.GetByIdAsync(userInfo.Id);
if (user.PasswordHash == null)
{
(await UserManager.AddPasswordAsync(user, Input.NewPassword)).CheckErrors();
}
else
{
(await UserManager.ChangePasswordAsync(user, Input.CurrentPassword, Input.NewPassword)).CheckErrors();
}
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = IdentitySecurityLogActionConsts.ChangePassword
});
user.SetShouldChangePasswordOnNextLogin(false);
(await UserManager.UpdateAsync(user)).CheckErrors();
await HttpContext.SignOutAsync(AbpAccountAuthenticationTypes.ShouldChangePassword);
await SignInManager.SignInAsync(user, RememberMe);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = IdentitySecurityLogActionConsts.LoginSucceeded,
UserName = user.UserName
});
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
catch (Exception ex)
{
Alerts.Warning(GetLocalizeExceptionMessage(ex));
return Page();
}
}
}
await HttpContext.SignOutAsync(AbpAccountAuthenticationTypes.ShouldChangePassword);
return RedirectToPage("/Login", new { ReturnUrl, ReturnUrlHash });
}
protected async virtual Task<UserInfoModel> GetCurrentUser()
{
var result = await HttpContext.AuthenticateAsync(AbpAccountAuthenticationTypes.ShouldChangePassword);
var userId = result?.Principal?.FindUserId();
if (!userId.HasValue)
{
return null;
}
var tenantId = result.Principal.FindTenantId();
using (CurrentTenant.Change(tenantId, null))
{
var identityUser = await UserManager.FindByIdAsync(userId.Value.ToString());
return identityUser == null
? null
: new UserInfoModel()
{
Id = identityUser.Id,
TenantId = identityUser.TenantId
};
}
}
}

21
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.js

@ -0,0 +1,21 @@
$(function () {
$(".password-visibility-button").click(function (e) {
let button = $(this);
let passwordInput = button.parent().find("input");
if (!passwordInput) {
return;
}
if (passwordInput.attr("type") === "password") {
passwordInput.attr("type", "text");
}
else {
passwordInput.attr("type", "password");
}
let icon = button.find("i");
if (icon) {
icon.toggleClass("fa-eye-slash").toggleClass("fa-eye");
}
});
});

105
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml

@ -0,0 +1,105 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@using Volo.Abp.Account.Settings
@using Volo.Abp.Account.Web.Pages.Account;
@using Volo.Abp.AspNetCore.Mvc.UI.Theming;
@using Volo.Abp.Identity;
@using Volo.Abp.Settings
@model LINGYUN.Abp.Account.Web.Pages.Account.LoginModel
@inject IHtmlLocalizer<AccountResource> L
@inject IThemeManager ThemeManager
@inject Volo.Abp.Settings.ISettingProvider SettingProvider
@{
Layout = ThemeManager.CurrentTheme.GetAccountLayout();
}
@section scripts
{
<abp-script-bundle name="@typeof(LoginModel).FullName">
<abp-script src="/Pages/Account/Login.js" />
</abp-script-bundle>
}
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Login"]</h4>
@if (Model.EnableLocalLogin)
{
<form method="post" class="mt-4">
<div class="mb-3">
<label asp-for="LoginInput.UserNameOrEmailAddress" class="form-label"></label>
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" />
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span>
</div>
<div class="mb-3">
<label asp-for="LoginInput.Password" class="form-label"></label>
<div class="input-group">
<input type="password" class="form-control" autocomplete="new-password" maxlength="@IdentityUserConsts.MaxPasswordLength" asp-for="LoginInput.Password" />
<button class="btn btn-secondary" type="button" id="PasswordVisibilityButton"><i class="fa fa-eye-slash" aria-hidden="true"></i></button>
</div>
<span asp-validation-for="LoginInput.Password"></span>
</div>
<abp-row>
<abp-column>
<abp-input asp-for="LoginInput.RememberMe" class="mb-4" />
</abp-column>
<abp-column class="text-end">
<a href="@Url.Page("./ForgotPassword", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">@L["ForgotPassword"]</a>
</abp-column>
</abp-row>
@if (await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled))
{
<strong>
@L["AreYouANewUser"]
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Register"]</a>
</strong>
}
<div class="d-grid gap-2">
<abp-button type="submit" button-type="Primary" name="Action" value="Login" class="btn-lg mt-3">@L["Login"]</abp-button>
@if (Model.ShowCancelButton)
{
<abp-button type="submit" button-type="Secondary" formnovalidate="formnovalidate" name="Action" value="Cancel" class="btn-lg mt-3">@L["Cancel"]</abp-button>
}
</div>
</form>
}
@if (Model.VisibleExternalProviders.Any())
{
<div class="mt-2">
<h5>@L["OrLoginWith"]</h5>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.VisibleExternalProviders)
{
@* <button
type="submit"
class="btn btn-primary m-1"
name="provider"
value="@provider.AuthenticationScheme"
title="@L["LogInUsingYourProviderAccount", provider.DisplayName]"
>
@if (provider.Icon != null)
{
<i class="@provider.Icon"></i>
}
<span>@provider.DisplayName</span>
</button> *@
@await Component.InvokeAsync(provider.ComponentType, provider);
}
</form>
</div>
}
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any())
{
<div class="alert alert-warning">
<strong>@L["InvalidLoginRequest"]</strong>
@L["ThereAreNoLoginSchemesConfiguredForThisClient"]
</div>
}
</div>
</div>

387
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs

@ -0,0 +1,387 @@
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.Identity.AspNetCore;
using Volo.Abp.Reflection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Validation;
using static Volo.Abp.Account.Web.Pages.Account.LoginModel;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace LINGYUN.Abp.Account.Web.Pages.Account;
//[ExposeServices(typeof(Volo.Abp.Account.Web.Pages.Account.LoginModel))]
public class LoginModel : AccountPageModel
{
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }
[HiddenInput]
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; }
[BindProperty]
public LoginInputModel LoginInput { get; set; }
public bool EnableLocalLogin { get; set; }
public bool ShowCancelButton { get; set; }
public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1;
public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
public IEnumerable<ExternalLoginProviderModel> ExternalProviders { get; set; }
public IEnumerable<ExternalLoginProviderModel> VisibleExternalProviders => ExternalProviders.Where(x => !x.DisplayName.IsNullOrWhiteSpace());
protected IExternalProviderService ExternalProviderService { get; }
protected IAuthenticationSchemeProvider SchemeProvider { get; }
protected AbpAccountOptions AccountOptions { get; }
protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; }
public LoginModel(
IExternalProviderService externalProviderService,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IOptions<IdentityOptions> identityOptions,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache)
{
ExternalProviderService = externalProviderService;
SchemeProvider = schemeProvider;
IdentityOptions = identityOptions;
AccountOptions = accountOptions.Value;
IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache;
}
public virtual async Task<IActionResult> OnGetAsync()
{
LoginInput = new LoginInputModel();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
if (IsExternalLoginOnly)
{
return await OnPostExternalLogin(ExternalProviders.First().AuthenticationScheme);
}
return Page();
}
public async virtual Task<IActionResult> OnPostAsync(string action)
{
await CheckLocalLoginAsync();
ValidateModel();
ExternalProviders = await GetExternalProviders();
EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin);
await ReplaceEmailToUsernameOfInputIfNeeds();
await IdentityOptions.SetAsync();
var result = await SignInManager.PasswordSignInAsync(
LoginInput.UserNameOrEmailAddress,
LoginInput.Password,
LoginInput.RememberMe,
true
);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.Identity,
Action = result.ToIdentitySecurityLogAction(),
UserName = LoginInput.UserNameOrEmailAddress
});
if (result.RequiresTwoFactor)
{
return await TwoFactorLoginResultAsync();
}
if (result.IsLockedOut)
{
return await HandleUserLockedOut();
}
if (result.IsNotAllowed)
{
return await HandleUserNotAllowed();
}
if (!result.Succeeded)
{
return await HandleUserNameOrPasswordInvalid();
}
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
var user = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress);
Debug.Assert(user != null, nameof(user) + " != null");
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash);
}
public virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash });
var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
properties.Items["scheme"] = provider;
return await Task.FromResult(Challenge(properties, provider));
}
public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null)
{
//TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)
/* Also did not implement these:
* - Logout(string logoutId)
*/
if (remoteError != null)
{
Logger.LogWarning($"External login callback error: {remoteError}");
return RedirectToPage("./Login");
}
await IdentityOptions.SetAsync();
var loginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (loginInfo == null)
{
Logger.LogWarning("External login info is not available");
return RedirectToPage("./Login");
}
var result = await SignInManager.ExternalLoginSignInAsync(
loginInfo.LoginProvider,
loginInfo.ProviderKey,
isPersistent: false,
bypassTwoFactor: true
);
if (!result.Succeeded)
{
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = "Login" + result
});
}
if (result.IsLockedOut)
{
Logger.LogWarning($"External login callback error: user is locked out!");
throw new UserFriendlyException("Cannot proceed because user is locked out!");
}
if (result.IsNotAllowed)
{
Logger.LogWarning($"External login callback error: user is not allowed!");
throw new UserFriendlyException("Cannot proceed because user is not allowed!");
}
IdentityUser user;
if (result.Succeeded)
{
user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey);
if (user != null)
{
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
}
return await RedirectSafelyAsync(returnUrl, returnUrlHash);
}
//TODO: Handle other cases for result!
var email = loginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) ?? loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
if (email.IsNullOrWhiteSpace())
{
return RedirectToPage("./Register", new
{
IsExternalLogin = true,
ExternalLoginAuthSchema = loginInfo.LoginProvider,
ReturnUrl = returnUrl
});
}
user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
return RedirectToPage("./Register", new
{
IsExternalLogin = true,
ExternalLoginAuthSchema = loginInfo.LoginProvider,
ReturnUrl = returnUrl
});
}
if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null)
{
CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo));
}
await SignInManager.SignInAsync(user, false);
await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext()
{
Identity = IdentitySecurityLogIdentityConsts.IdentityExternal,
Action = result.ToIdentitySecurityLogAction(),
UserName = user.Name
});
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
return await RedirectSafelyAsync(returnUrl, returnUrlHash);
}
protected virtual Task<IActionResult> TwoFactorLoginResultAsync()
{
// 重定向双因素认证页面
return Task.FromResult<IActionResult>(RedirectToPage("SendCode", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
}));
}
protected virtual async Task<IdentityUser> GetIdentityUserAsync(string userNameOrEmailAddress)
{
return await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ??
await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
}
protected async virtual Task<List<ExternalLoginProviderModel>> GetExternalProviders()
{
var schemes = await SchemeProvider.GetAllSchemesAsync();
var externalProviders = await ExternalProviderService.GetAllAsync();
var externalProviderModels = new List<ExternalLoginProviderModel>();
foreach (var scheme in schemes)
{
if (TryGetExternalLoginProvider(scheme, externalProviders, out var externalLoginProvider) ||
scheme.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
{
externalProviderModels.Add(new ExternalLoginProviderModel
{
Name = externalLoginProvider.Name,
AuthenticationScheme = scheme.Name,
DisplayName = externalLoginProvider.DisplayName,
ComponentType = externalLoginProvider.ComponentType,
});
}
}
return externalProviderModels;
}
protected virtual bool TryGetExternalLoginProvider(AuthenticationScheme scheme, List<ExternalLoginProviderModel> externalProviders, out ExternalLoginProviderModel externalLoginProvider)
{
if (ReflectionHelper.IsAssignableToGenericType(scheme.HandlerType, typeof(RemoteAuthenticationHandler<>)))
{
externalLoginProvider = externalProviders.FirstOrDefault(x => x.Name == scheme.Name);
return externalLoginProvider != null;
}
externalLoginProvider = null;
return false;
}
protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds()
{
if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress))
{
return;
}
var userByUsername = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress);
if (userByUsername != null)
{
return;
}
var userByEmail = await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress);
if (userByEmail == null)
{
return;
}
LoginInput.UserNameOrEmailAddress = userByEmail.UserName;
}
protected virtual async Task CheckLocalLoginAsync()
{
if (!await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin))
{
throw new UserFriendlyException(L["LocalLoginDisabledMessage"]);
}
}
protected virtual Task<IActionResult> HandleUserLockedOut()
{
Alerts.Warning(L["UserLockedOutMessage"]);
return Task.FromResult<IActionResult>(Page());
}
protected async virtual Task<IActionResult> HandleUserNotAllowed()
{
var notAllowedUser = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress);
if (await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password))
{
// 用户必须修改密码
if (notAllowedUser.ShouldChangePasswordOnNextLogin || await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser))
{
var changePwdIdentity = new ClaimsIdentity(AbpAccountAuthenticationTypes.ShouldChangePassword);
changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.UserId, notAllowedUser.Id.ToString()));
if (notAllowedUser.TenantId.HasValue)
{
changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.TenantId, notAllowedUser.TenantId.ToString()));
}
await HttpContext.SignInAsync(AbpAccountAuthenticationTypes.ShouldChangePassword, new ClaimsPrincipal(changePwdIdentity));
return RedirectToPage("ChangePassword", new
{
returnUrl = ReturnUrl,
returnUrlHash = ReturnUrlHash,
rememberMe = LoginInput.RememberMe
});
}
}
Alerts.Warning(L["LoginIsNotAllowed"]);
return Page();
}
protected virtual Task<IActionResult> HandleUserNameOrPasswordInvalid()
{
Alerts.Danger(L["InvalidUserNameOrPassword"]);
return Task.FromResult<IActionResult>(Page());
}
}

54
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml

@ -0,0 +1,54 @@
@page
@using Microsoft.AspNetCore.Mvc.Localization
@using Volo.Abp.Account.Localization
@model LINGYUN.Abp.Account.Web.Pages.Account.RegisterModel
@inject IHtmlLocalizer<AccountResource> L
<div class="card mt-3 shadow-sm rounded">
<div class="card-body p-5">
<h4>@L["Register"]</h4>
<form method="post" class="mt-4">
@if (Model.EnableLocalRegister || Model.IsExternalLogin)
{
<abp-input asp-for="Input.UserName" auto-focus="true" />
}
@if (Model.EnableLocalRegister || Model.IsExternalLogin)
{
<abp-input asp-for="Input.EmailAddress" />
}
@if (!Model.IsExternalLogin && Model.EnableLocalRegister)
{
<abp-input asp-for="Input.Password" />
}
<strong>
@L["AlreadyRegistered"]
<a href="@Url.Page("./Login", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})" class="text-decoration-none">@L["Login"]</a>
</strong>
@if (Model.EnableLocalRegister || Model.IsExternalLogin)
{
<div class="d-grid gap-2">
<abp-button button-type="Primary" type="submit" class="btn-lg mt-4">@L["Register"]</abp-button>
</div>
}
</form>
@if (!Model.IsExternalLogin && Model.VisibleExternalProviders.Any())
{
<div class="mt-2">
<h5>@L["OrRegisterWith"]</h5>
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post">
@foreach (var provider in Model.VisibleExternalProviders)
{
@await Component.InvokeAsync(provider.ComponentType, provider);
}
</form>
</div>
}
</div>
</div>

291
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Register.cshtml.cs

@ -0,0 +1,291 @@
using LINGYUN.Abp.Account.Web.ExternalProviders;
using LINGYUN.Abp.Account.Web.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Account;
using Volo.Abp.Account.Settings;
using Volo.Abp.Account.Web;
using Volo.Abp.Account.Web.Pages.Account;
using Volo.Abp.Auditing;
using Volo.Abp.Identity;
using Volo.Abp.Reflection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Settings;
using Volo.Abp.Validation;
using IAbpAccountAppService = Volo.Abp.Account.IAccountAppService;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
namespace LINGYUN.Abp.Account.Web.Pages.Account;
public class RegisterModel : AccountPageModel
{
[BindProperty(SupportsGet = true)]
public string ReturnUrl { get; set; }
[BindProperty(SupportsGet = true)]
public string ReturnUrlHash { get; set; }
[BindProperty]
public PostInput Input { get; set; }
[BindProperty(SupportsGet = true)]
public bool IsExternalLogin { get; set; }
[BindProperty(SupportsGet = true)]
public string ExternalLoginAuthSchema { get; set; }
public IEnumerable<ExternalLoginProviderModel> ExternalProviders { get; set; }
public IEnumerable<ExternalLoginProviderModel> VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName));
public bool EnableLocalRegister { get; set; }
public bool IsExternalLoginOnly => EnableLocalRegister == false && ExternalProviders?.Count() == 1;
public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null;
protected IExternalProviderService ExternalProviderService { get; }
protected IAuthenticationSchemeProvider SchemeProvider { get; }
protected AbpAccountOptions AccountOptions { get; }
protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; }
public RegisterModel(
IExternalProviderService externalProviderService,
IAbpAccountAppService accountAppService,
IAuthenticationSchemeProvider schemeProvider,
IOptions<AbpAccountOptions> accountOptions,
IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache)
{
ExternalProviderService = externalProviderService;
SchemeProvider = schemeProvider;
IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache;
AccountAppService = accountAppService;
AccountOptions = accountOptions.Value;
}
public virtual async Task<IActionResult> OnGetAsync()
{
ExternalProviders = await GetExternalProviders();
if (!await CheckSelfRegistrationAsync())
{
if (IsExternalLoginOnly)
{
return await OnPostExternalLogin(ExternalLoginScheme);
}
Alerts.Warning(L["SelfRegistrationDisabledMessage"]);
}
await TrySetEmailAsync();
return Page();
}
protected virtual async Task TrySetEmailAsync()
{
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
return;
}
if (!externalLoginInfo.Principal.Identities.Any())
{
return;
}
var identity = externalLoginInfo.Principal.Identities.First();
var emailClaim = identity.FindFirst(AbpClaimTypes.Email) ?? identity.FindFirst(ClaimTypes.Email);
if (emailClaim == null)
{
return;
}
var userName = await UserManager.GetUserNameFromEmailAsync(emailClaim.Value);
Input = new PostInput { UserName = userName, EmailAddress = emailClaim.Value };
}
}
public virtual async Task<IActionResult> OnPostAsync()
{
try
{
ExternalProviders = await GetExternalProviders();
if (!await CheckSelfRegistrationAsync())
{
throw new UserFriendlyException(L["SelfRegistrationDisabledMessage"]);
}
if (IsExternalLogin)
{
var externalLoginInfo = await SignInManager.GetExternalLoginInfoAsync();
if (externalLoginInfo == null)
{
Logger.LogWarning("External login info is not available");
return RedirectToPage("./Login");
}
if (Input.UserName.IsNullOrWhiteSpace())
{
Input.UserName = await UserManager.GetUserNameFromEmailAsync(Input.EmailAddress);
}
await RegisterExternalUserAsync(externalLoginInfo, Input.UserName, Input.EmailAddress);
}
else
{
await RegisterLocalUserAsync();
}
return Redirect(ReturnUrl ?? "~/"); //TODO: How to ensure safety? IdentityServer requires it however it should be checked somehow!
}
catch (BusinessException e)
{
Alerts.Danger(GetLocalizeExceptionMessage(e));
return Page();
}
}
protected virtual async Task RegisterLocalUserAsync()
{
ValidateModel();
var userDto = await AccountAppService.RegisterAsync(
new RegisterDto
{
AppName = "MVC",
EmailAddress = Input.EmailAddress,
Password = Input.Password,
UserName = Input.UserName
}
);
var user = await UserManager.GetByIdAsync(userDto.Id);
await SignInManager.SignInAsync(user, isPersistent: true);
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
}
protected virtual async Task RegisterExternalUserAsync(ExternalLoginInfo externalLoginInfo, string userName, string emailAddress)
{
await IdentityOptions.SetAsync();
var user = new IdentityUser(GuidGenerator.Create(), userName, emailAddress, CurrentTenant.Id);
(await UserManager.CreateAsync(user)).CheckErrors();
(await UserManager.AddDefaultRolesAsync(user)).CheckErrors();
var userLoginAlreadyExists = user.Logins.Any(x =>
x.TenantId == user.TenantId &&
x.LoginProvider == externalLoginInfo.LoginProvider &&
x.ProviderKey == externalLoginInfo.ProviderKey);
if (!userLoginAlreadyExists)
{
(await UserManager.AddLoginAsync(user, new UserLoginInfo(
externalLoginInfo.LoginProvider,
externalLoginInfo.ProviderKey,
externalLoginInfo.ProviderDisplayName
))).CheckErrors();
}
await SignInManager.SignInAsync(user, isPersistent: true, ExternalLoginAuthSchema);
// Clear the dynamic claims cache.
await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId);
}
protected virtual async Task<bool> CheckSelfRegistrationAsync()
{
EnableLocalRegister = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin) &&
await SettingProvider.IsTrueAsync(AccountSettingNames.IsSelfRegistrationEnabled);
if (IsExternalLogin)
{
return true;
}
if (!EnableLocalRegister)
{
return false;
}
return true;
}
protected async virtual Task<List<ExternalLoginProviderModel>> GetExternalProviders()
{
var schemes = await SchemeProvider.GetAllSchemesAsync();
var externalProviders = await ExternalProviderService.GetAllAsync();
var externalProviderModels = new List<ExternalLoginProviderModel>();
foreach (var scheme in schemes)
{
if (TryGetExternalLoginProvider(scheme, externalProviders, out var externalLoginProvider) ||
scheme.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase))
{
externalProviderModels.Add(new ExternalLoginProviderModel
{
Name = externalLoginProvider.Name,
AuthenticationScheme = scheme.Name,
DisplayName = externalLoginProvider.DisplayName,
ComponentType = externalLoginProvider.ComponentType,
});
}
}
return externalProviderModels;
}
protected virtual bool TryGetExternalLoginProvider(AuthenticationScheme scheme, List<ExternalLoginProviderModel> externalProviders, out ExternalLoginProviderModel externalLoginProvider)
{
if (ReflectionHelper.IsAssignableToGenericType(scheme.HandlerType, typeof(RemoteAuthenticationHandler<>)))
{
externalLoginProvider = externalProviders.FirstOrDefault(x => x.Name == scheme.Name);
return externalLoginProvider != null;
}
externalLoginProvider = null;
return false;
}
protected virtual async Task<IActionResult> OnPostExternalLogin(string provider)
{
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash });
var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
properties.Items["scheme"] = provider;
return await Task.FromResult(Challenge(properties, provider));
}
public class PostInput
{
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxUserNameLength))]
public string UserName { get; set; }
[Required]
[EmailAddress]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxEmailLength))]
public string EmailAddress { get; set; }
[Required]
[DynamicStringLength(typeof(IdentityUserConsts), nameof(IdentityUserConsts.MaxPasswordLength))]
[DataType(DataType.Password)]
[DisableAuditing]
public string Password { get; set; }
}
}

2
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ProfileManagement/SessionManagementPageContributor.cs → aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ProfileManagement/ProfileManagementPageContributor.cs

@ -10,7 +10,7 @@ using Volo.Abp.Account.Web.ProfileManagement;
namespace LINGYUN.Abp.Account.Web.ProfileManagement;
public class SessionManagementPageContributor : IProfileManagementPageContributor
public class ProfileManagementPageContributor : IProfileManagementPageContributor
{
public virtual Task ConfigureAsync(ProfileManagementPageCreationContext context)
{

3
aspnet-core/modules/account/LINGYUN.Abp.Account.Web/wwwroot/styles/user-login-link/fix-style.css

@ -0,0 +1,3 @@
.fix-margin {
margin: 10px 0;
}

5
aspnet-core/modules/localization-management/LINGYUN.Abp.LocalizationManagement.Domain/LINGYUN/Abp/LocalizationManagement/External/ExternalLocalizationTextStoreCache.cs

@ -84,14 +84,15 @@ public class ExternalLocalizationTextStoreCache : IExternalLocalizationTextStore
var stampCacheKey = ExternalLocalizationTextStampCacheItem.CalculateCacheKey(resource.ResourceName, cultureName);
var stampCacheItem = await StampCache.GetAsync(stampCacheKey);
if (memoryCacheItem != null && memoryCacheItem.CacheStamp == stampCacheItem.Stamp)
if (memoryCacheItem != null && memoryCacheItem.CacheStamp == stampCacheItem?.Stamp)
{
memoryCacheItem.LastCheckTime = DateTime.Now;
return memoryCacheItem.Texts;
}
var distributeCacheItem = await DistributedCache.GetAsync(cacheKey);
if (distributeCacheItem != null)
if (stampCacheItem != null && distributeCacheItem != null)
{
MemoryCache[cacheKey] = new LocalizationTextMemoryCacheItem(distributeCacheItem.Texts, stampCacheItem.Stamp);

6
aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.Application.Contracts/LINGYUN/Abp/OpenIddict/AbpOpenIddictApplicationContractsModule.cs

@ -1,5 +1,6 @@
using Volo.Abp.Authorization;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict;
using Volo.Abp.OpenIddict.Localization;
@ -25,5 +26,10 @@ public class AbpOpenIddictApplicationContractsModule : AbpModule
.Get<AbpOpenIddictResource>()
.AddVirtualJson("/LINGYUN/Abp/OpenIddict/Localization/Resources");
});
Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace(OpenIddictApplicationErrorCodes.Namespace, typeof(AbpOpenIddictResource));
});
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save