├── LICENSE ├── README.md └── src ├── AuthServer ├── .gitignore ├── AuthServer.Infrastructure │ ├── AuthServer.Infrastructure.csproj │ ├── Constants │ │ └── Roles.cs │ ├── Data │ │ ├── DesignTimeDbContextFactoryBase.cs │ │ └── Identity │ │ │ ├── AppIdentityDbContext.cs │ │ │ ├── AppIdentityDbContextFactory.cs │ │ │ ├── AppUser.cs │ │ │ └── PersistedGrantDbContextFactory.cs │ ├── Migrations │ │ ├── 20190403041320_initial.Designer.cs │ │ ├── 20190403041320_initial.cs │ │ ├── AppIdentityDbContextModelSnapshot.cs │ │ └── PersistedGrantDb │ │ │ ├── 20190403041351_initial.Designer.cs │ │ │ ├── 20190403041351_initial.cs │ │ │ └── PersistedGrantDbContextModelSnapshot.cs │ ├── Services │ │ └── IdentityClaimsProfileService.cs │ └── appsettings.json ├── AuthServer.sln └── AuthServer │ ├── AuthServer.csproj │ ├── Config.cs │ ├── Controllers │ ├── AccountController.cs │ ├── ConsentController.cs │ └── HomeController.cs │ ├── Extensions │ ├── HttpResponseExtensions.cs │ └── IClientStoreExtensions.cs │ ├── Models │ ├── AccountOptions.cs │ ├── ConsentInputModel.cs │ ├── ConsentOptions.cs │ ├── ConsentViewModel.cs │ ├── ExternalProvider.cs │ ├── LoginInputModel.cs │ ├── LoginViewModel.cs │ ├── ProcessConsentResult.cs │ ├── RedirectViewModel.cs │ ├── RegisterRequestViewModel.cs │ ├── RegisterResponseViewModel.cs │ └── ScopeViewModel.cs │ ├── Program.cs │ ├── SecurityHeadersAttribute.cs │ ├── Startup.cs │ ├── Views │ ├── Account │ │ └── Login.cshtml │ ├── Consent │ │ ├── Index.cshtml │ │ └── Index.cshtml.cs │ ├── Home │ │ ├── Index.cshtml │ │ └── Privacy.cshtml │ ├── Shared │ │ ├── _CookieConsentPartial.cshtml │ │ ├── _Layout.cshtml │ │ ├── _ScopeListItem.cshtml │ │ ├── _ScopeListItem.cshtml.cs │ │ ├── _ValidationScriptsPartial.cshtml │ │ └── _ValidationSummary.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── tempkey.rsa │ └── wwwroot │ ├── css │ └── site.css │ ├── favicon.ico │ ├── js │ └── site.js │ └── lib │ ├── bootstrap │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ └── js │ │ ├── bootstrap.bundle.js │ │ ├── bootstrap.bundle.js.map │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.bundle.min.js.map │ │ ├── bootstrap.js │ │ ├── bootstrap.js.map │ │ ├── bootstrap.min.js │ │ └── bootstrap.min.js.map │ ├── jquery-validation-unobtrusive │ ├── LICENSE.txt │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ ├── additional-methods.min.js │ │ ├── jquery.validate.js │ │ └── jquery.validate.min.js │ └── jquery │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ ├── jquery.min.js │ └── jquery.min.map ├── Resource.Api ├── .gitignore ├── Resource.Api.sln └── Resource.Api │ ├── Controllers │ └── ValuesController.cs │ ├── Program.cs │ ├── Resource.Api.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json └── Spa └── oauth-client ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── src ├── app │ ├── account │ │ ├── account.module.ts │ │ ├── account.routing-module.ts │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ ├── login.component.spec.ts │ │ │ └── login.component.ts │ │ └── register │ │ │ ├── register.component.html │ │ │ ├── register.component.scss │ │ │ ├── register.component.spec.ts │ │ │ └── register.component.ts │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── auth-callback │ │ ├── auth-callback.component.html │ │ ├── auth-callback.component.scss │ │ └── auth-callback.component.ts │ ├── core │ │ ├── authentication │ │ │ ├── auth.guard.ts │ │ │ ├── auth.service.spec.ts │ │ │ └── auth.service.ts │ │ └── core.module.ts │ ├── home │ │ ├── home-routing.module.ts │ │ ├── home.module.ts │ │ └── index │ │ │ ├── index.component.html │ │ │ ├── index.component.scss │ │ │ ├── index.component.spec.ts │ │ │ └── index.component.ts │ ├── shared │ │ ├── base.service.ts │ │ ├── config.service.ts │ │ ├── directives │ │ │ └── auto-focus.directive.ts │ │ ├── mocks │ │ │ ├── fake-backend-interceptor.ts │ │ │ ├── mock-auth.service.ts │ │ │ └── mock-top-secret.service.ts │ │ ├── models │ │ │ ├── user.registration.spec.ts │ │ │ └── user.registration.ts │ │ └── shared.module.ts │ ├── shell │ │ ├── header │ │ │ ├── header.component.html │ │ │ ├── header.component.scss │ │ │ ├── header.component.spec.ts │ │ │ └── header.component.ts │ │ ├── shell.component.html │ │ ├── shell.component.scss │ │ ├── shell.component.spec.ts │ │ ├── shell.component.ts │ │ ├── shell.module.ts │ │ ├── shell.service.spec.ts │ │ └── shell.service.ts │ └── top-secret │ │ ├── index │ │ ├── index.component.html │ │ ├── index.component.scss │ │ ├── index.component.spec.ts │ │ └── index.component.ts │ │ ├── top-secret.module.ts │ │ ├── top-secret.routing-module.ts │ │ ├── top-secret.service.spec.ts │ │ └── top-secret.service.ts ├── assets │ ├── .gitkeep │ ├── images │ │ ├── angular_solidBlack.svg │ │ └── open-identity.png │ └── styles.scss ├── browserslist ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json └── tslint.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Mark Macneil 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularASPNETCoreOAuth 2 | Sample project based on the blog post demonstrating how to build out an Implicit Grant OAuth flow utilizing OAuth2/OpenID Connect protocols implementing IdentityServer4 as our OpenID Connect Provider and then using it to authenticate an Angular SPA client to authorize access to an independent ASP.NET Core Web API. 3 | 4 | 5 | 6 | ## Development Environment 7 | - Visual Studio 2019 Community 8 | - Visual Studio Code 1.32.3 9 | - .NET Core SDK 3.1 10 | - Angular 8 11 | - IdentityServer4 3.0.1 12 | - SQL Server Express 2016 LocalDB 13 | 14 | ## Setup 15 | 16 | #### To run the demo: 17 | 18 | **1.** Clone/Fork/Download this repository. 19 | 20 | **2.** Create the database on your SQL Server Express LocalDB by using the dotnet cli to run the migrations from within the AuthServer.Infrastrucuture project folder. 21 |
AuthServer.Infrastructure> dotnet ef database update --context AppIdentityDbContext
22 |
AuthServer.Infrastructure> dotnet ef database update --context PersistedGrantDbContext
23 | 24 | **3.** Install Angular CLI if necessary. `npm install -g @angular/cli` 25 | 26 | **4.** Install Angular SPA dependencies. 27 |
Spa\oauth-client> npm install
28 | 29 | **5.** Run the Angular CLI dev server to build and run the Angular app. 30 |
Spa\oauth-client> ng serve
31 | - **Important:** This must be running on the default http://localhost:4200 32 | 33 | **6.** Build/Run the `AuthServer.sln` solution using your preferred method: Visual Studio, VSCode, dotnet CLI. 34 | - **Important:** This must be running on http://localhost:5000 35 | 36 | **7.** Build/Run the `Resource.Api.sln` solution using your preferred method: Visual Studio, VSCode, dotnet CLI. 37 | - **Important:** This must be running on http://localhost:5050 38 | 39 | **8.** Point a browser to `http://localhost:4200` to access the Angular client. 40 | 41 | **9.** Use the *Signup* and *Login* functions to perform the authentication flow, then try and access the *Top Secret Area* to hit the protected ASP.NET Core Web API. 42 | 43 | ## Contact 44 | mark@fullstackmark.com 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/AuthServer/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | authserver_log.txt 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | **/Properties/launchSettings.json 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/AuthServer.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Constants/Roles.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AuthServer.Infrastructure.Constants 4 | { 5 | public static class Roles 6 | { 7 | public const string Consumer = "consumer"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Data/DesignTimeDbContextFactoryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Design; 5 | using Microsoft.Extensions.Configuration; 6 | 7 | namespace AuthServer.Infrastructure.Data 8 | { 9 | public abstract class DesignTimeDbContextFactoryBase : IDesignTimeDbContextFactory where TContext : DbContext 10 | { 11 | public TContext CreateDbContext(string[] args) 12 | { 13 | return Create(Directory.GetCurrentDirectory(), Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); 14 | } 15 | 16 | protected abstract TContext CreateNewInstance(DbContextOptions options); 17 | 18 | public TContext Create() 19 | { 20 | var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); 21 | var basePath = AppContext.BaseDirectory; 22 | return Create(basePath, environmentName); 23 | } 24 | 25 | private TContext Create(string basePath, string environmentName) 26 | { 27 | var builder = new ConfigurationBuilder() 28 | .SetBasePath(basePath) 29 | .AddJsonFile("appsettings.json") 30 | .AddJsonFile($"appsettings.{environmentName}.json", true) 31 | .AddEnvironmentVariables(); 32 | 33 | var config = builder.Build(); 34 | 35 | var connstr = config.GetConnectionString("Default"); 36 | 37 | if (string.IsNullOrWhiteSpace(connstr)) 38 | { 39 | throw new InvalidOperationException( 40 | "Could not find a connection string named 'Default'."); 41 | } 42 | return Create(connstr); 43 | } 44 | 45 | private TContext Create(string connectionString) 46 | { 47 | if (string.IsNullOrEmpty(connectionString)) 48 | throw new ArgumentException( 49 | $"{nameof(connectionString)} is null or empty.", 50 | nameof(connectionString)); 51 | 52 | var optionsBuilder = new DbContextOptionsBuilder(); 53 | 54 | Console.WriteLine("DesignTimeDbContextFactory.Create(string): Connection string: {0}", connectionString); 55 | 56 | optionsBuilder.UseSqlServer(connectionString); 57 | 58 | var options = optionsBuilder.Options; 59 | return CreateNewInstance(options); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Data/Identity/AppIdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | 5 | namespace AuthServer.Infrastructure.Data.Identity 6 | { 7 | public class AppIdentityDbContext : IdentityDbContext 8 | { 9 | public AppIdentityDbContext(DbContextOptions options) : base(options) 10 | { 11 | } 12 | 13 | protected override void OnModelCreating(ModelBuilder modelBuilder) 14 | { 15 | base.OnModelCreating(modelBuilder); 16 | // Customize the ASP.NET Identity model and override the defaults if needed. 17 | // For example, you can rename the ASP.NET Identity table names and more. 18 | // Add your customizations after calling base.OnModelCreating(builder); 19 | 20 | modelBuilder.Entity().HasData(new IdentityRole { Name = Constants.Roles.Consumer, NormalizedName = Constants.Roles.Consumer.ToUpper() }); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Data/Identity/AppIdentityDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | 3 | namespace AuthServer.Infrastructure.Data.Identity 4 | { 5 | public class AppIdentityDbContextFactory : DesignTimeDbContextFactoryBase 6 | { 7 | protected override AppIdentityDbContext CreateNewInstance(DbContextOptions options) 8 | { 9 | return new AppIdentityDbContext(options); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Data/Identity/AppUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace AuthServer.Infrastructure.Data.Identity 4 | { 5 | public class AppUser : IdentityUser 6 | { 7 | // Add additional profile data for application users by adding properties to this class 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Data/Identity/PersistedGrantDbContextFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using IdentityServer4.EntityFramework.DbContexts; 3 | using IdentityServer4.EntityFramework.Options; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Design; 6 | 7 | namespace AuthServer.Infrastructure.Data.Identity 8 | { 9 | public class PersistedGrantDbContextFactory : IDesignTimeDbContextFactory 10 | { 11 | public PersistedGrantDbContext CreateDbContext(string[] args) 12 | { 13 | var optionsBuilder = new DbContextOptionsBuilder(); 14 | optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=AuthServer;Trusted_Connection=True;MultipleActiveResultSets=true", 15 | sql => sql.MigrationsAssembly(typeof(PersistedGrantDbContextFactory).GetTypeInfo().Assembly.GetName().Name)); 16 | return new PersistedGrantDbContext(optionsBuilder.Options, new OperationalStoreOptions()); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/20190403041320_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using AuthServer.Infrastructure.Data.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace AuthServer.Infrastructure.Migrations 11 | { 12 | [DbContext(typeof(AppIdentityDbContext))] 13 | [Migration("20190403041320_initial")] 14 | partial class initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.3-servicing-35854") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("AuthServer.Infrastructure.Data.Identity.AppUser", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("AccessFailedCount"); 30 | 31 | b.Property("ConcurrencyStamp") 32 | .IsConcurrencyToken(); 33 | 34 | b.Property("Email") 35 | .HasMaxLength(256); 36 | 37 | b.Property("EmailConfirmed"); 38 | 39 | b.Property("LockoutEnabled"); 40 | 41 | b.Property("LockoutEnd"); 42 | 43 | b.Property("Name"); 44 | 45 | b.Property("NormalizedEmail") 46 | .HasMaxLength(256); 47 | 48 | b.Property("NormalizedUserName") 49 | .HasMaxLength(256); 50 | 51 | b.Property("PasswordHash"); 52 | 53 | b.Property("PhoneNumber"); 54 | 55 | b.Property("PhoneNumberConfirmed"); 56 | 57 | b.Property("SecurityStamp"); 58 | 59 | b.Property("TwoFactorEnabled"); 60 | 61 | b.Property("UserName") 62 | .HasMaxLength(256); 63 | 64 | b.HasKey("Id"); 65 | 66 | b.HasIndex("NormalizedEmail") 67 | .HasName("EmailIndex"); 68 | 69 | b.HasIndex("NormalizedUserName") 70 | .IsUnique() 71 | .HasName("UserNameIndex") 72 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 73 | 74 | b.ToTable("AspNetUsers"); 75 | }); 76 | 77 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 78 | { 79 | b.Property("Id") 80 | .ValueGeneratedOnAdd(); 81 | 82 | b.Property("ConcurrencyStamp") 83 | .IsConcurrencyToken(); 84 | 85 | b.Property("Name") 86 | .HasMaxLength(256); 87 | 88 | b.Property("NormalizedName") 89 | .HasMaxLength(256); 90 | 91 | b.HasKey("Id"); 92 | 93 | b.HasIndex("NormalizedName") 94 | .IsUnique() 95 | .HasName("RoleNameIndex") 96 | .HasFilter("[NormalizedName] IS NOT NULL"); 97 | 98 | b.ToTable("AspNetRoles"); 99 | 100 | b.HasData( 101 | new 102 | { 103 | Id = "27e8d4aa-67ca-48af-a986-7059029dbefb", 104 | ConcurrencyStamp = "96def603-94ae-4f01-b766-fc2bee1f06ab", 105 | Name = "consumer", 106 | NormalizedName = "CONSUMER" 107 | }); 108 | }); 109 | 110 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 111 | { 112 | b.Property("Id") 113 | .ValueGeneratedOnAdd() 114 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 115 | 116 | b.Property("ClaimType"); 117 | 118 | b.Property("ClaimValue"); 119 | 120 | b.Property("RoleId") 121 | .IsRequired(); 122 | 123 | b.HasKey("Id"); 124 | 125 | b.HasIndex("RoleId"); 126 | 127 | b.ToTable("AspNetRoleClaims"); 128 | }); 129 | 130 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 131 | { 132 | b.Property("Id") 133 | .ValueGeneratedOnAdd() 134 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 135 | 136 | b.Property("ClaimType"); 137 | 138 | b.Property("ClaimValue"); 139 | 140 | b.Property("UserId") 141 | .IsRequired(); 142 | 143 | b.HasKey("Id"); 144 | 145 | b.HasIndex("UserId"); 146 | 147 | b.ToTable("AspNetUserClaims"); 148 | }); 149 | 150 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 151 | { 152 | b.Property("LoginProvider"); 153 | 154 | b.Property("ProviderKey"); 155 | 156 | b.Property("ProviderDisplayName"); 157 | 158 | b.Property("UserId") 159 | .IsRequired(); 160 | 161 | b.HasKey("LoginProvider", "ProviderKey"); 162 | 163 | b.HasIndex("UserId"); 164 | 165 | b.ToTable("AspNetUserLogins"); 166 | }); 167 | 168 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 169 | { 170 | b.Property("UserId"); 171 | 172 | b.Property("RoleId"); 173 | 174 | b.HasKey("UserId", "RoleId"); 175 | 176 | b.HasIndex("RoleId"); 177 | 178 | b.ToTable("AspNetUserRoles"); 179 | }); 180 | 181 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 182 | { 183 | b.Property("UserId"); 184 | 185 | b.Property("LoginProvider"); 186 | 187 | b.Property("Name"); 188 | 189 | b.Property("Value"); 190 | 191 | b.HasKey("UserId", "LoginProvider", "Name"); 192 | 193 | b.ToTable("AspNetUserTokens"); 194 | }); 195 | 196 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 197 | { 198 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 199 | .WithMany() 200 | .HasForeignKey("RoleId") 201 | .OnDelete(DeleteBehavior.Cascade); 202 | }); 203 | 204 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 205 | { 206 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 207 | .WithMany() 208 | .HasForeignKey("UserId") 209 | .OnDelete(DeleteBehavior.Cascade); 210 | }); 211 | 212 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 213 | { 214 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 215 | .WithMany() 216 | .HasForeignKey("UserId") 217 | .OnDelete(DeleteBehavior.Cascade); 218 | }); 219 | 220 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 221 | { 222 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 223 | .WithMany() 224 | .HasForeignKey("RoleId") 225 | .OnDelete(DeleteBehavior.Cascade); 226 | 227 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 228 | .WithMany() 229 | .HasForeignKey("UserId") 230 | .OnDelete(DeleteBehavior.Cascade); 231 | }); 232 | 233 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 234 | { 235 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 236 | .WithMany() 237 | .HasForeignKey("UserId") 238 | .OnDelete(DeleteBehavior.Cascade); 239 | }); 240 | #pragma warning restore 612, 618 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/20190403041320_initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace AuthServer.Infrastructure.Migrations 6 | { 7 | public partial class initial : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "AspNetRoles", 13 | columns: table => new 14 | { 15 | Id = table.Column(nullable: false), 16 | Name = table.Column(maxLength: 256, nullable: true), 17 | NormalizedName = table.Column(maxLength: 256, nullable: true), 18 | ConcurrencyStamp = table.Column(nullable: true) 19 | }, 20 | constraints: table => 21 | { 22 | table.PrimaryKey("PK_AspNetRoles", x => x.Id); 23 | }); 24 | 25 | migrationBuilder.CreateTable( 26 | name: "AspNetUsers", 27 | columns: table => new 28 | { 29 | Id = table.Column(nullable: false), 30 | UserName = table.Column(maxLength: 256, nullable: true), 31 | NormalizedUserName = table.Column(maxLength: 256, nullable: true), 32 | Email = table.Column(maxLength: 256, nullable: true), 33 | NormalizedEmail = table.Column(maxLength: 256, nullable: true), 34 | EmailConfirmed = table.Column(nullable: false), 35 | PasswordHash = table.Column(nullable: true), 36 | SecurityStamp = table.Column(nullable: true), 37 | ConcurrencyStamp = table.Column(nullable: true), 38 | PhoneNumber = table.Column(nullable: true), 39 | PhoneNumberConfirmed = table.Column(nullable: false), 40 | TwoFactorEnabled = table.Column(nullable: false), 41 | LockoutEnd = table.Column(nullable: true), 42 | LockoutEnabled = table.Column(nullable: false), 43 | AccessFailedCount = table.Column(nullable: false), 44 | Name = table.Column(nullable: true) 45 | }, 46 | constraints: table => 47 | { 48 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 49 | }); 50 | 51 | migrationBuilder.CreateTable( 52 | name: "AspNetRoleClaims", 53 | columns: table => new 54 | { 55 | Id = table.Column(nullable: false) 56 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 57 | RoleId = table.Column(nullable: false), 58 | ClaimType = table.Column(nullable: true), 59 | ClaimValue = table.Column(nullable: true) 60 | }, 61 | constraints: table => 62 | { 63 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 64 | table.ForeignKey( 65 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 66 | column: x => x.RoleId, 67 | principalTable: "AspNetRoles", 68 | principalColumn: "Id", 69 | onDelete: ReferentialAction.Cascade); 70 | }); 71 | 72 | migrationBuilder.CreateTable( 73 | name: "AspNetUserClaims", 74 | columns: table => new 75 | { 76 | Id = table.Column(nullable: false) 77 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 78 | UserId = table.Column(nullable: false), 79 | ClaimType = table.Column(nullable: true), 80 | ClaimValue = table.Column(nullable: true) 81 | }, 82 | constraints: table => 83 | { 84 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 85 | table.ForeignKey( 86 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 87 | column: x => x.UserId, 88 | principalTable: "AspNetUsers", 89 | principalColumn: "Id", 90 | onDelete: ReferentialAction.Cascade); 91 | }); 92 | 93 | migrationBuilder.CreateTable( 94 | name: "AspNetUserLogins", 95 | columns: table => new 96 | { 97 | LoginProvider = table.Column(nullable: false), 98 | ProviderKey = table.Column(nullable: false), 99 | ProviderDisplayName = table.Column(nullable: true), 100 | UserId = table.Column(nullable: false) 101 | }, 102 | constraints: table => 103 | { 104 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 105 | table.ForeignKey( 106 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 107 | column: x => x.UserId, 108 | principalTable: "AspNetUsers", 109 | principalColumn: "Id", 110 | onDelete: ReferentialAction.Cascade); 111 | }); 112 | 113 | migrationBuilder.CreateTable( 114 | name: "AspNetUserRoles", 115 | columns: table => new 116 | { 117 | UserId = table.Column(nullable: false), 118 | RoleId = table.Column(nullable: false) 119 | }, 120 | constraints: table => 121 | { 122 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 123 | table.ForeignKey( 124 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 125 | column: x => x.RoleId, 126 | principalTable: "AspNetRoles", 127 | principalColumn: "Id", 128 | onDelete: ReferentialAction.Cascade); 129 | table.ForeignKey( 130 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 131 | column: x => x.UserId, 132 | principalTable: "AspNetUsers", 133 | principalColumn: "Id", 134 | onDelete: ReferentialAction.Cascade); 135 | }); 136 | 137 | migrationBuilder.CreateTable( 138 | name: "AspNetUserTokens", 139 | columns: table => new 140 | { 141 | UserId = table.Column(nullable: false), 142 | LoginProvider = table.Column(nullable: false), 143 | Name = table.Column(nullable: false), 144 | Value = table.Column(nullable: true) 145 | }, 146 | constraints: table => 147 | { 148 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 149 | table.ForeignKey( 150 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 151 | column: x => x.UserId, 152 | principalTable: "AspNetUsers", 153 | principalColumn: "Id", 154 | onDelete: ReferentialAction.Cascade); 155 | }); 156 | 157 | migrationBuilder.InsertData( 158 | table: "AspNetRoles", 159 | columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, 160 | values: new object[] { "27e8d4aa-67ca-48af-a986-7059029dbefb", "96def603-94ae-4f01-b766-fc2bee1f06ab", "consumer", "CONSUMER" }); 161 | 162 | migrationBuilder.CreateIndex( 163 | name: "IX_AspNetRoleClaims_RoleId", 164 | table: "AspNetRoleClaims", 165 | column: "RoleId"); 166 | 167 | migrationBuilder.CreateIndex( 168 | name: "RoleNameIndex", 169 | table: "AspNetRoles", 170 | column: "NormalizedName", 171 | unique: true, 172 | filter: "[NormalizedName] IS NOT NULL"); 173 | 174 | migrationBuilder.CreateIndex( 175 | name: "IX_AspNetUserClaims_UserId", 176 | table: "AspNetUserClaims", 177 | column: "UserId"); 178 | 179 | migrationBuilder.CreateIndex( 180 | name: "IX_AspNetUserLogins_UserId", 181 | table: "AspNetUserLogins", 182 | column: "UserId"); 183 | 184 | migrationBuilder.CreateIndex( 185 | name: "IX_AspNetUserRoles_RoleId", 186 | table: "AspNetUserRoles", 187 | column: "RoleId"); 188 | 189 | migrationBuilder.CreateIndex( 190 | name: "EmailIndex", 191 | table: "AspNetUsers", 192 | column: "NormalizedEmail"); 193 | 194 | migrationBuilder.CreateIndex( 195 | name: "UserNameIndex", 196 | table: "AspNetUsers", 197 | column: "NormalizedUserName", 198 | unique: true, 199 | filter: "[NormalizedUserName] IS NOT NULL"); 200 | } 201 | 202 | protected override void Down(MigrationBuilder migrationBuilder) 203 | { 204 | migrationBuilder.DropTable( 205 | name: "AspNetRoleClaims"); 206 | 207 | migrationBuilder.DropTable( 208 | name: "AspNetUserClaims"); 209 | 210 | migrationBuilder.DropTable( 211 | name: "AspNetUserLogins"); 212 | 213 | migrationBuilder.DropTable( 214 | name: "AspNetUserRoles"); 215 | 216 | migrationBuilder.DropTable( 217 | name: "AspNetUserTokens"); 218 | 219 | migrationBuilder.DropTable( 220 | name: "AspNetRoles"); 221 | 222 | migrationBuilder.DropTable( 223 | name: "AspNetUsers"); 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/AppIdentityDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using AuthServer.Infrastructure.Data.Identity; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace AuthServer.Infrastructure.Migrations 10 | { 11 | [DbContext(typeof(AppIdentityDbContext))] 12 | partial class AppIdentityDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.2.3-servicing-35854") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("AuthServer.Infrastructure.Data.Identity.AppUser", b => 23 | { 24 | b.Property("Id") 25 | .ValueGeneratedOnAdd(); 26 | 27 | b.Property("AccessFailedCount"); 28 | 29 | b.Property("ConcurrencyStamp") 30 | .IsConcurrencyToken(); 31 | 32 | b.Property("Email") 33 | .HasMaxLength(256); 34 | 35 | b.Property("EmailConfirmed"); 36 | 37 | b.Property("LockoutEnabled"); 38 | 39 | b.Property("LockoutEnd"); 40 | 41 | b.Property("Name"); 42 | 43 | b.Property("NormalizedEmail") 44 | .HasMaxLength(256); 45 | 46 | b.Property("NormalizedUserName") 47 | .HasMaxLength(256); 48 | 49 | b.Property("PasswordHash"); 50 | 51 | b.Property("PhoneNumber"); 52 | 53 | b.Property("PhoneNumberConfirmed"); 54 | 55 | b.Property("SecurityStamp"); 56 | 57 | b.Property("TwoFactorEnabled"); 58 | 59 | b.Property("UserName") 60 | .HasMaxLength(256); 61 | 62 | b.HasKey("Id"); 63 | 64 | b.HasIndex("NormalizedEmail") 65 | .HasName("EmailIndex"); 66 | 67 | b.HasIndex("NormalizedUserName") 68 | .IsUnique() 69 | .HasName("UserNameIndex") 70 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 71 | 72 | b.ToTable("AspNetUsers"); 73 | }); 74 | 75 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 76 | { 77 | b.Property("Id") 78 | .ValueGeneratedOnAdd(); 79 | 80 | b.Property("ConcurrencyStamp") 81 | .IsConcurrencyToken(); 82 | 83 | b.Property("Name") 84 | .HasMaxLength(256); 85 | 86 | b.Property("NormalizedName") 87 | .HasMaxLength(256); 88 | 89 | b.HasKey("Id"); 90 | 91 | b.HasIndex("NormalizedName") 92 | .IsUnique() 93 | .HasName("RoleNameIndex") 94 | .HasFilter("[NormalizedName] IS NOT NULL"); 95 | 96 | b.ToTable("AspNetRoles"); 97 | 98 | b.HasData( 99 | new 100 | { 101 | Id = "27e8d4aa-67ca-48af-a986-7059029dbefb", 102 | ConcurrencyStamp = "96def603-94ae-4f01-b766-fc2bee1f06ab", 103 | Name = "consumer", 104 | NormalizedName = "CONSUMER" 105 | }); 106 | }); 107 | 108 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 109 | { 110 | b.Property("Id") 111 | .ValueGeneratedOnAdd() 112 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 113 | 114 | b.Property("ClaimType"); 115 | 116 | b.Property("ClaimValue"); 117 | 118 | b.Property("RoleId") 119 | .IsRequired(); 120 | 121 | b.HasKey("Id"); 122 | 123 | b.HasIndex("RoleId"); 124 | 125 | b.ToTable("AspNetRoleClaims"); 126 | }); 127 | 128 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 129 | { 130 | b.Property("Id") 131 | .ValueGeneratedOnAdd() 132 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 133 | 134 | b.Property("ClaimType"); 135 | 136 | b.Property("ClaimValue"); 137 | 138 | b.Property("UserId") 139 | .IsRequired(); 140 | 141 | b.HasKey("Id"); 142 | 143 | b.HasIndex("UserId"); 144 | 145 | b.ToTable("AspNetUserClaims"); 146 | }); 147 | 148 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 149 | { 150 | b.Property("LoginProvider"); 151 | 152 | b.Property("ProviderKey"); 153 | 154 | b.Property("ProviderDisplayName"); 155 | 156 | b.Property("UserId") 157 | .IsRequired(); 158 | 159 | b.HasKey("LoginProvider", "ProviderKey"); 160 | 161 | b.HasIndex("UserId"); 162 | 163 | b.ToTable("AspNetUserLogins"); 164 | }); 165 | 166 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 167 | { 168 | b.Property("UserId"); 169 | 170 | b.Property("RoleId"); 171 | 172 | b.HasKey("UserId", "RoleId"); 173 | 174 | b.HasIndex("RoleId"); 175 | 176 | b.ToTable("AspNetUserRoles"); 177 | }); 178 | 179 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 180 | { 181 | b.Property("UserId"); 182 | 183 | b.Property("LoginProvider"); 184 | 185 | b.Property("Name"); 186 | 187 | b.Property("Value"); 188 | 189 | b.HasKey("UserId", "LoginProvider", "Name"); 190 | 191 | b.ToTable("AspNetUserTokens"); 192 | }); 193 | 194 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 195 | { 196 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 197 | .WithMany() 198 | .HasForeignKey("RoleId") 199 | .OnDelete(DeleteBehavior.Cascade); 200 | }); 201 | 202 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 203 | { 204 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 205 | .WithMany() 206 | .HasForeignKey("UserId") 207 | .OnDelete(DeleteBehavior.Cascade); 208 | }); 209 | 210 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 211 | { 212 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 213 | .WithMany() 214 | .HasForeignKey("UserId") 215 | .OnDelete(DeleteBehavior.Cascade); 216 | }); 217 | 218 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 219 | { 220 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 221 | .WithMany() 222 | .HasForeignKey("RoleId") 223 | .OnDelete(DeleteBehavior.Cascade); 224 | 225 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 226 | .WithMany() 227 | .HasForeignKey("UserId") 228 | .OnDelete(DeleteBehavior.Cascade); 229 | }); 230 | 231 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 232 | { 233 | b.HasOne("AuthServer.Infrastructure.Data.Identity.AppUser") 234 | .WithMany() 235 | .HasForeignKey("UserId") 236 | .OnDelete(DeleteBehavior.Cascade); 237 | }); 238 | #pragma warning restore 612, 618 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/PersistedGrantDb/20190403041351_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using IdentityServer4.EntityFramework.DbContexts; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Migrations; 8 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 9 | 10 | namespace AuthServer.Infrastructure.Migrations.PersistedGrantDb 11 | { 12 | [DbContext(typeof(PersistedGrantDbContext))] 13 | [Migration("20190403041351_initial")] 14 | partial class initial 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.2.3-servicing-35854") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => 25 | { 26 | b.Property("UserCode") 27 | .ValueGeneratedOnAdd() 28 | .HasMaxLength(200); 29 | 30 | b.Property("ClientId") 31 | .IsRequired() 32 | .HasMaxLength(200); 33 | 34 | b.Property("CreationTime"); 35 | 36 | b.Property("Data") 37 | .IsRequired() 38 | .HasMaxLength(50000); 39 | 40 | b.Property("DeviceCode") 41 | .IsRequired() 42 | .HasMaxLength(200); 43 | 44 | b.Property("Expiration") 45 | .IsRequired(); 46 | 47 | b.Property("SubjectId") 48 | .HasMaxLength(200); 49 | 50 | b.HasKey("UserCode"); 51 | 52 | b.HasIndex("DeviceCode") 53 | .IsUnique(); 54 | 55 | b.ToTable("DeviceCodes"); 56 | }); 57 | 58 | modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => 59 | { 60 | b.Property("Key") 61 | .HasMaxLength(200); 62 | 63 | b.Property("ClientId") 64 | .IsRequired() 65 | .HasMaxLength(200); 66 | 67 | b.Property("CreationTime"); 68 | 69 | b.Property("Data") 70 | .IsRequired() 71 | .HasMaxLength(50000); 72 | 73 | b.Property("Expiration"); 74 | 75 | b.Property("SubjectId") 76 | .HasMaxLength(200); 77 | 78 | b.Property("Type") 79 | .IsRequired() 80 | .HasMaxLength(50); 81 | 82 | b.HasKey("Key"); 83 | 84 | b.HasIndex("SubjectId", "ClientId", "Type"); 85 | 86 | b.ToTable("PersistedGrants"); 87 | }); 88 | #pragma warning restore 612, 618 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/PersistedGrantDb/20190403041351_initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | namespace AuthServer.Infrastructure.Migrations.PersistedGrantDb 5 | { 6 | public partial class initial : Migration 7 | { 8 | protected override void Up(MigrationBuilder migrationBuilder) 9 | { 10 | migrationBuilder.CreateTable( 11 | name: "DeviceCodes", 12 | columns: table => new 13 | { 14 | UserCode = table.Column(maxLength: 200, nullable: false), 15 | DeviceCode = table.Column(maxLength: 200, nullable: false), 16 | SubjectId = table.Column(maxLength: 200, nullable: true), 17 | ClientId = table.Column(maxLength: 200, nullable: false), 18 | CreationTime = table.Column(nullable: false), 19 | Expiration = table.Column(nullable: false), 20 | Data = table.Column(maxLength: 50000, nullable: false) 21 | }, 22 | constraints: table => 23 | { 24 | table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); 25 | }); 26 | 27 | migrationBuilder.CreateTable( 28 | name: "PersistedGrants", 29 | columns: table => new 30 | { 31 | Key = table.Column(maxLength: 200, nullable: false), 32 | Type = table.Column(maxLength: 50, nullable: false), 33 | SubjectId = table.Column(maxLength: 200, nullable: true), 34 | ClientId = table.Column(maxLength: 200, nullable: false), 35 | CreationTime = table.Column(nullable: false), 36 | Expiration = table.Column(nullable: true), 37 | Data = table.Column(maxLength: 50000, nullable: false) 38 | }, 39 | constraints: table => 40 | { 41 | table.PrimaryKey("PK_PersistedGrants", x => x.Key); 42 | }); 43 | 44 | migrationBuilder.CreateIndex( 45 | name: "IX_DeviceCodes_DeviceCode", 46 | table: "DeviceCodes", 47 | column: "DeviceCode", 48 | unique: true); 49 | 50 | migrationBuilder.CreateIndex( 51 | name: "IX_PersistedGrants_SubjectId_ClientId_Type", 52 | table: "PersistedGrants", 53 | columns: new[] { "SubjectId", "ClientId", "Type" }); 54 | } 55 | 56 | protected override void Down(MigrationBuilder migrationBuilder) 57 | { 58 | migrationBuilder.DropTable( 59 | name: "DeviceCodes"); 60 | 61 | migrationBuilder.DropTable( 62 | name: "PersistedGrants"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Migrations/PersistedGrantDb/PersistedGrantDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using IdentityServer4.EntityFramework.DbContexts; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace AuthServer.Infrastructure.Migrations.PersistedGrantDb 10 | { 11 | [DbContext(typeof(PersistedGrantDbContext))] 12 | partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.2.3-servicing-35854") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => 23 | { 24 | b.Property("UserCode") 25 | .ValueGeneratedOnAdd() 26 | .HasMaxLength(200); 27 | 28 | b.Property("ClientId") 29 | .IsRequired() 30 | .HasMaxLength(200); 31 | 32 | b.Property("CreationTime"); 33 | 34 | b.Property("Data") 35 | .IsRequired() 36 | .HasMaxLength(50000); 37 | 38 | b.Property("DeviceCode") 39 | .IsRequired() 40 | .HasMaxLength(200); 41 | 42 | b.Property("Expiration") 43 | .IsRequired(); 44 | 45 | b.Property("SubjectId") 46 | .HasMaxLength(200); 47 | 48 | b.HasKey("UserCode"); 49 | 50 | b.HasIndex("DeviceCode") 51 | .IsUnique(); 52 | 53 | b.ToTable("DeviceCodes"); 54 | }); 55 | 56 | modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => 57 | { 58 | b.Property("Key") 59 | .HasMaxLength(200); 60 | 61 | b.Property("ClientId") 62 | .IsRequired() 63 | .HasMaxLength(200); 64 | 65 | b.Property("CreationTime"); 66 | 67 | b.Property("Data") 68 | .IsRequired() 69 | .HasMaxLength(50000); 70 | 71 | b.Property("Expiration"); 72 | 73 | b.Property("SubjectId") 74 | .HasMaxLength(200); 75 | 76 | b.Property("Type") 77 | .IsRequired() 78 | .HasMaxLength(50); 79 | 80 | b.HasKey("Key"); 81 | 82 | b.HasIndex("SubjectId", "ClientId", "Type"); 83 | 84 | b.ToTable("PersistedGrants"); 85 | }); 86 | #pragma warning restore 612, 618 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/Services/IdentityClaimsProfileService.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using AuthServer.Infrastructure.Constants; 5 | using AuthServer.Infrastructure.Data.Identity; 6 | using IdentityModel; 7 | using IdentityServer4; 8 | using IdentityServer4.Extensions; 9 | using IdentityServer4.Models; 10 | using IdentityServer4.Services; 11 | using Microsoft.AspNetCore.Identity; 12 | 13 | namespace AuthServer.Infrastructure.Services 14 | { 15 | public class IdentityClaimsProfileService : IProfileService 16 | { 17 | private readonly IUserClaimsPrincipalFactory _claimsFactory; 18 | private readonly UserManager _userManager; 19 | 20 | public IdentityClaimsProfileService(UserManager userManager, IUserClaimsPrincipalFactory claimsFactory) 21 | { 22 | _userManager = userManager; 23 | _claimsFactory = claimsFactory; 24 | } 25 | 26 | public async Task GetProfileDataAsync(ProfileDataRequestContext context) 27 | { 28 | var sub = context.Subject.GetSubjectId(); 29 | var user = await _userManager.FindByIdAsync(sub); 30 | var principal = await _claimsFactory.CreateAsync(user); 31 | 32 | var claims = principal.Claims.ToList(); 33 | claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList(); 34 | claims.Add(new Claim(JwtClaimTypes.GivenName, user.Name)); 35 | claims.Add(new Claim(IdentityServerConstants.StandardScopes.Email, user.Email)); 36 | // note: to dynamically add roles (ie. for users other than consumers - simply look them up by sub id 37 | claims.Add(new Claim(ClaimTypes.Role, Roles.Consumer)); // need this for role-based authorization - https://stackoverflow.com/questions/40844310/role-based-authorization-with-identityserver4 38 | 39 | context.IssuedClaims = claims; 40 | } 41 | 42 | public async Task IsActiveAsync(IsActiveContext context) 43 | { 44 | var sub = context.Subject.GetSubjectId(); 45 | var user = await _userManager.FindByIdAsync(sub); 46 | context.IsActive = user != null; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.Infrastructure/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "Default": "Server=(localdb)\\mssqllocaldb;Database=AuthServer;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } -------------------------------------------------------------------------------- /src/AuthServer/AuthServer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28705.295 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthServer.Infrastructure", "AuthServer.Infrastructure\AuthServer.Infrastructure.csproj", "{6E5DAF8E-4CEE-49E3-B9B5-448D1216A868}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AuthServer", "AuthServer\AuthServer.csproj", "{9838346D-ED85-49CC-A066-92D31D009A4E}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6E5DAF8E-4CEE-49E3-B9B5-448D1216A868}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6E5DAF8E-4CEE-49E3-B9B5-448D1216A868}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6E5DAF8E-4CEE-49E3-B9B5-448D1216A868}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6E5DAF8E-4CEE-49E3-B9B5-448D1216A868}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9838346D-ED85-49CC-A066-92D31D009A4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9838346D-ED85-49CC-A066-92D31D009A4E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9838346D-ED85-49CC-A066-92D31D009A4E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9838346D-ED85-49CC-A066-92D31D009A4E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {798D95C6-049E-4430-8931-CDF6D4AFA342} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/AuthServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using IdentityServer4.Models; 3 | 4 | namespace AuthServer 5 | { 6 | public class Config 7 | { 8 | public static IEnumerable GetIdentityResources() 9 | { 10 | return new List 11 | { 12 | new IdentityResources.OpenId(), 13 | new IdentityResources.Email(), 14 | new IdentityResources.Profile(), 15 | }; 16 | } 17 | 18 | public static IEnumerable GetApiResources() 19 | { 20 | return new List 21 | { 22 | new ApiResource("resourceapi", "Resource API") 23 | { 24 | Scopes = {new Scope("api.read")} 25 | } 26 | }; 27 | } 28 | 29 | public static IEnumerable GetClients() 30 | { 31 | return new[] 32 | { 33 | new Client { 34 | RequireConsent = false, 35 | ClientId = "angular_spa", 36 | ClientName = "Angular SPA", 37 | AllowedGrantTypes = GrantTypes.Implicit, 38 | AllowedScopes = { "openid", "profile", "email", "api.read" }, 39 | RedirectUris = {"http://localhost:4200/auth-callback"}, 40 | PostLogoutRedirectUris = {"http://localhost:4200/"}, 41 | AllowedCorsOrigins = {"http://localhost:4200"}, 42 | AllowAccessTokensViaBrowser = true, 43 | AccessTokenLifetime = 3600 44 | } 45 | }; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace AuthServer.Controllers 8 | { 9 | [SecurityHeaders] 10 | [AllowAnonymous] 11 | public class HomeController : Controller 12 | { 13 | private readonly IWebHostEnvironment _environment; 14 | private readonly ILogger _logger; 15 | 16 | public HomeController(IWebHostEnvironment environment, ILogger logger) 17 | { 18 | _environment = environment; 19 | _logger = logger; 20 | } 21 | 22 | public IActionResult Index() 23 | { 24 | if (_environment.IsDevelopment()) 25 | { 26 | // only show in development 27 | return View(); 28 | } 29 | 30 | _logger.LogInformation("Homepage is disabled in production. Returning 404."); 31 | return NotFound(); 32 | } 33 | 34 | public IActionResult Privacy() 35 | { 36 | return View(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Extensions/HttpResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | 3 | namespace AuthServer.Extensions 4 | { 5 | public static class HttpResponseExtensions 6 | { 7 | public static void AddApplicationError(this HttpResponse response, string message) 8 | { 9 | response.Headers.Add("Application-Error", message); 10 | // CORS 11 | response.Headers.Add("access-control-expose-headers", "Application-Error"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Extensions/IClientStoreExtensions.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4.Stores; 2 | using System.Threading.Tasks; 3 | 4 | namespace AuthServer.Extensions 5 | { 6 | public static class Extensions 7 | { 8 | /// 9 | /// Determines whether the client is configured to use PKCE. 10 | /// 11 | /// The store. 12 | /// The client identifier. 13 | /// 14 | public static async Task IsPkceClientAsync(this IClientStore store, string client_id) 15 | { 16 | if (!string.IsNullOrWhiteSpace(client_id)) 17 | { 18 | var client = await store.FindEnabledClientByIdAsync(client_id); 19 | return client?.RequirePkce == true; 20 | } 21 | 22 | return false; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/AccountOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | 4 | namespace AuthServer.Models 5 | { 6 | public class AccountOptions 7 | { 8 | public static bool AllowLocalLogin = true; 9 | public static bool AllowRememberLogin = true; 10 | public static TimeSpan RememberMeLoginDuration = TimeSpan.FromDays(30); 11 | 12 | public static bool ShowLogoutPrompt = true; 13 | public static bool AutomaticRedirectAfterSignOut = false; 14 | 15 | // specify the Windows authentication scheme being used 16 | public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; 17 | // if user uses windows auth, should we load the groups from windows 18 | public static bool IncludeWindowsGroups = false; 19 | 20 | public static string InvalidCredentialsErrorMessage = "Invalid username or password"; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ConsentInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | 4 | namespace AuthServer.Models 5 | { 6 | public class ConsentInputModel 7 | { 8 | public string Button { get; set; } 9 | public IEnumerable ScopesConsented { get; set; } 10 | public bool RememberConsent { get; set; } 11 | public string ReturnUrl { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ConsentOptions.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AuthServer.Models 4 | { 5 | public class ConsentOptions 6 | { 7 | public static bool EnableOfflineAccess = true; 8 | public static string OfflineAccessDisplayName = "Offline Access"; 9 | public static string OfflineAccessDescription = "Access to your applications and resources, even when you are offline"; 10 | 11 | public static readonly string MustChooseOneErrorMessage = "You must pick at least one permission"; 12 | public static readonly string InvalidSelectionErrorMessage = "Invalid selection"; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ConsentViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | 4 | namespace AuthServer.Models 5 | { 6 | public class ConsentViewModel : ConsentInputModel 7 | { 8 | public string ClientName { get; set; } 9 | public string ClientUrl { get; set; } 10 | public string ClientLogoUrl { get; set; } 11 | public bool AllowRememberConsent { get; set; } 12 | public IEnumerable IdentityScopes { get; set; } 13 | public IEnumerable ResourceScopes { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ExternalProvider.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AuthServer.Models 4 | { 5 | public class ExternalProvider 6 | { 7 | public string DisplayName { get; set; } 8 | public string AuthenticationScheme { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/LoginInputModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace AuthServer.Models 4 | { 5 | public class LoginInputModel 6 | { 7 | [Required] 8 | public string Username { get; set; } 9 | [Required] 10 | public string Password { get; set; } 11 | public bool RememberLogin { get; set; } 12 | public string ReturnUrl { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | 5 | namespace AuthServer.Models 6 | { 7 | public class LoginViewModel : LoginInputModel 8 | { 9 | public bool AllowRememberLogin { get; set; } = true; 10 | public bool EnableLocalLogin { get; set; } = true; 11 | 12 | public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); 13 | public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !string.IsNullOrWhiteSpace(x.DisplayName)); 14 | 15 | public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; 16 | public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ProcessConsentResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace AuthServer.Models 7 | { 8 | public class ProcessConsentResult 9 | { 10 | public bool IsRedirect => RedirectUri != null; 11 | public string RedirectUri { get; set; } 12 | public string ClientId { get; set; } 13 | 14 | public bool ShowView => ViewModel != null; 15 | public ConsentViewModel ViewModel { get; set; } 16 | 17 | public bool HasValidationError => ValidationError != null; 18 | public string ValidationError { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/RedirectViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AuthServer.Models 4 | { 5 | public class RedirectViewModel 6 | { 7 | public string RedirectUrl { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/RegisterRequestViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | 4 | namespace AuthServer.Models 5 | { 6 | public class RegisterRequestViewModel 7 | { 8 | [Required] 9 | [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 2)] 10 | [Display(Name = "Name")] 11 | public string Name { get; set; } 12 | 13 | [Required] 14 | [EmailAddress] 15 | [Display(Name = "Email")] 16 | public string Email { get; set; } 17 | 18 | [Required] 19 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 20 | [DataType(DataType.Password)] 21 | [Display(Name = "Password")] 22 | public string Password { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/RegisterResponseViewModel.cs: -------------------------------------------------------------------------------- 1 | using AuthServer.Infrastructure.Data.Identity; 2 | 3 | namespace AuthServer.Models 4 | { 5 | public class RegisterResponseViewModel 6 | { 7 | public string Id { get; set; } 8 | public string Name { get; set; } 9 | public string Email { get; set; } 10 | 11 | public RegisterResponseViewModel(AppUser user) 12 | { 13 | Id = user.Id; 14 | Name = user.Name; 15 | Email = user.Email; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Models/ScopeViewModel.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace AuthServer.Models 4 | { 5 | public class ScopeViewModel 6 | { 7 | public string Name { get; set; } 8 | public string DisplayName { get; set; } 9 | public string Description { get; set; } 10 | public bool Emphasize { get; set; } 11 | public bool Required { get; set; } 12 | public bool Checked { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | 5 | namespace AuthServer 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseStartup() 17 | .UseUrls("http://localhost:5000"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/SecurityHeadersAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Brock Allen & Dominick Baier. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. 3 | 4 | 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.Filters; 7 | 8 | namespace AuthServer 9 | { 10 | public class SecurityHeadersAttribute : ActionFilterAttribute 11 | { 12 | public override void OnResultExecuting(ResultExecutingContext context) 13 | { 14 | var result = context.Result; 15 | if (result is ViewResult) 16 | { 17 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 18 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Type-Options")) 19 | { 20 | context.HttpContext.Response.Headers.Add("X-Content-Type-Options", "nosniff"); 21 | } 22 | 23 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 24 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Frame-Options")) 25 | { 26 | context.HttpContext.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN"); 27 | } 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 30 | var csp = "default-src 'self'; object-src 'none'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin allow-scripts; base-uri 'self';"; 31 | // also consider adding upgrade-insecure-requests once you have HTTPS in place for production 32 | //csp += "upgrade-insecure-requests;"; 33 | // also an example if you need client images to be displayed from twitter 34 | // csp += "img-src 'self' https://pbs.twimg.com;"; 35 | 36 | // once for standards compliant browsers 37 | if (!context.HttpContext.Response.Headers.ContainsKey("Content-Security-Policy")) 38 | { 39 | context.HttpContext.Response.Headers.Add("Content-Security-Policy", csp); 40 | } 41 | // and once again for IE 42 | if (!context.HttpContext.Response.Headers.ContainsKey("X-Content-Security-Policy")) 43 | { 44 | context.HttpContext.Response.Headers.Add("X-Content-Security-Policy", csp); 45 | } 46 | 47 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy 48 | var referrer_policy = "no-referrer"; 49 | if (!context.HttpContext.Response.Headers.ContainsKey("Referrer-Policy")) 50 | { 51 | context.HttpContext.Response.Headers.Add("Referrer-Policy", referrer_policy); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using AuthServer.Extensions; 2 | using AuthServer.Infrastructure.Data.Identity; 3 | using AuthServer.Infrastructure.Services; 4 | using IdentityServer4.Services; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Diagnostics; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.EntityFrameworkCore; 12 | using Microsoft.Extensions.Configuration; 13 | using Microsoft.Extensions.DependencyInjection; 14 | using Microsoft.Extensions.Hosting; 15 | using Microsoft.Extensions.Logging; 16 | using Serilog; 17 | using System.Net; 18 | 19 | 20 | 21 | namespace AuthServer 22 | { 23 | public class Startup 24 | { 25 | public Startup(IConfiguration configuration) 26 | { 27 | Configuration = configuration; 28 | } 29 | 30 | public IConfiguration Configuration { get; } 31 | 32 | // This method gets called by the runtime. Use this method to add services to the container. 33 | public void ConfigureServices(IServiceCollection services) 34 | { 35 | services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("Default"))); 36 | 37 | services.AddIdentity() 38 | .AddEntityFrameworkStores() 39 | .AddDefaultTokenProviders(); 40 | 41 | services.AddIdentityServer() 42 | .AddDeveloperSigningCredential() 43 | // this adds the operational data from DB (codes, tokens, consents) 44 | .AddOperationalStore(options => 45 | { 46 | options.ConfigureDbContext = builder => builder.UseSqlServer(Configuration.GetConnectionString("Default")); 47 | // this enables automatic token cleanup. this is optional. 48 | options.EnableTokenCleanup = true; 49 | options.TokenCleanupInterval = 30; // interval in seconds 50 | }) 51 | //.AddInMemoryPersistedGrants() 52 | .AddInMemoryIdentityResources(Config.GetIdentityResources()) 53 | .AddInMemoryApiResources(Config.GetApiResources()) 54 | .AddInMemoryClients(Config.GetClients()) 55 | .AddAspNetIdentity(); 56 | 57 | /* We'll play with this down the road... 58 | services.AddAuthentication() 59 | .AddGoogle("Google", options => 60 | { 61 | options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; 62 | 63 | options.ClientId = ""; 64 | options.ClientSecret = ""; 65 | });*/ 66 | 67 | services.AddTransient(); 68 | 69 | services.AddCors(options => options.AddPolicy("AllowAll", p => p.AllowAnyOrigin() 70 | .AllowAnyMethod() 71 | .AllowAnyHeader())); 72 | 73 | services.AddMvc(options => 74 | { 75 | options.EnableEndpointRouting = false; 76 | }).SetCompatibilityVersion(CompatibilityVersion.Latest); 77 | } 78 | 79 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 80 | public static void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) 81 | { 82 | if (env.IsDevelopment()) 83 | { 84 | app.UseDeveloperExceptionPage(); 85 | } 86 | 87 | app.UseExceptionHandler(builder => 88 | { 89 | builder.Run(async context => 90 | { 91 | context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; 92 | context.Response.Headers.Add("Access-Control-Allow-Origin", "*"); 93 | 94 | var error = context.Features.Get(); 95 | if (error != null) 96 | { 97 | context.Response.AddApplicationError(error.Error.Message); 98 | await context.Response.WriteAsync(error.Error.Message).ConfigureAwait(false); 99 | } 100 | }); 101 | }); 102 | 103 | var serilog = new LoggerConfiguration() 104 | .MinimumLevel.Verbose() 105 | .Enrich.FromLogContext() 106 | .WriteTo.File(@"authserver_log.txt"); 107 | 108 | loggerFactory.WithFilter(new FilterLoggerSettings 109 | { 110 | { "IdentityServer4", LogLevel.Debug }, 111 | { "Microsoft", LogLevel.Warning }, 112 | { "System", LogLevel.Warning }, 113 | }).AddSerilog(serilog.CreateLogger()); 114 | 115 | app.UseStaticFiles(); 116 | app.UseCors("AllowAll"); 117 | app.UseIdentityServer(); 118 | 119 | app.UseMvc(routes => 120 | { 121 | routes.MapRoute( 122 | name: "default", 123 | template: "{controller=Home}/{action=Index}/{id?}"); 124 | }); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @model LoginViewModel 2 | 3 |
4 | 5 | @if (Model.EnableLocalLogin) 6 | { 7 |
8 | 9 |

Login

10 | 11 |
12 | 13 |
14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 |
22 | 23 | @if (Model.AllowRememberLogin) 24 | { 25 |
26 | 27 | 28 |
29 | } 30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | 38 |
39 | } 40 | 41 | @if (Model.VisibleExternalProviders.Any()) 42 | { 43 |
44 | 45 |

External Login

46 | 47 |
48 | @foreach (var provider in Model.VisibleExternalProviders) 49 | { 50 | 55 | @provider.DisplayName 56 | 57 | } 58 |
59 |
60 | } 61 | 62 | @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) 63 | { 64 |
65 | Invalid login request 66 | There are no login schemes configured for this client. 67 |
68 | } 69 |
70 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Consent/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model ConsentViewModel 2 | 3 |
4 |
5 |

@Model.ClientName is requesting your permission

6 |
7 |
8 | 9 |
10 |
11 |
12 | 13 | 14 | 17 | 18 | @if (Model.IdentityScopes.Any()) 19 | { 20 |

Personal Information

21 |
    22 | @foreach (var scope in Model.IdentityScopes) 23 | { 24 | 25 | } 26 |
27 | } 28 | 29 | @if (Model.ResourceScopes.Any()) 30 | { 31 |

Application Access

32 |
    33 | @foreach (var scope in Model.ResourceScopes) 34 | { 35 | 36 | } 37 |
38 | } 39 | 40 | @if (Model.AllowRememberConsent) 41 | { 42 | 46 | } 47 | 48 |
49 | 50 | 51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Consent/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace AuthServer.Views.Consent 9 | { 10 | public class IndexModel : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var version = typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.GetName().Version.ToString(); 3 | } 4 | 5 |

Welcome to AuthServer!

6 |

Powered by IdentityServer4 (version @version)

7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 17 | 25 | } 26 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | AuthServer 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 |
22 | 41 |
42 |
43 | 44 |
45 | @RenderBody() 46 |
47 |
48 | 49 |
50 |
51 | © 2019 - AuthServer - Privacy 52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 72 | 73 | 74 | 75 | @RenderSection("Scripts", required: false) 76 | 77 | 78 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_ScopeListItem.cshtml: -------------------------------------------------------------------------------- 1 | @model ScopeViewModel 2 | 3 |
  • 4 | 19 | @if (Model.Required) 20 | { 21 | (required) 22 | } 23 | @if (Model.Description != null) 24 | { 25 | 26 | } 27 |
  • 28 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_ScopeListItem.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace AuthServer.Views.Shared 9 | { 10 | public class _ScopeListItemModel : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/Shared/_ValidationSummary.cshtml: -------------------------------------------------------------------------------- 1 | @if (ViewContext.ModelState.IsValid == false) 2 | { 3 |
    4 |
    5 |
    6 | } 7 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using AuthServer.Models 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "ConnectionStrings": { 9 | "Default": "Server=(localdb)\\mssqllocaldb;Database=AuthServer;Trusted_Connection=True;MultipleActiveResultSets=true" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/tempkey.rsa: -------------------------------------------------------------------------------- 1 | {"KeyId":"dbd98ab7246ac5d48a9061764367fd21","Parameters":{"D":"HCgoe9emRbirYzJp/ibj48hcqKPhSaaarx5njEvf/DLom12w3/dq3wZ6JvPN/CcZm44k6wSH5WuLw6uE1U/9SD9X6ARPTK0xV67set7EUYTauNf2E7qBq8WmdYs7/w+5GnXcQ9PjwbEZFNBx1c6wPnVsLRZyRpROmpjG/+RoBtQ9p5CfW4uYtSAAr1dC8zfztf5YF0SmR0Celt5B9hRmYyPXWA/TMmKCAhlC9DbbB6kOEtK0eygxxk0c7+xU+im+3AQkY936Vx//k483NjQ33FT+aNJ5GKZwTkUeG1aBOSQyzhRuxESyxybtcuWagmUAtdTi4ojfDC9kqORlCrL5fQ==","DP":"jwX6zk8BRU6ezGt1i29roMCllHKKbFZ0TvXN5dCwyH/jQHziLsFeJbO1d611n3IKwQMcjr3F3soGuT3yXYd7uHMZianPpqqhOBeGkO3BgNFGc8AEMIHVwEOyZVQw8AwWbI/4B/36rJROnf5/LUMS1tLDHHmwGsRhkpqBf7XQZwU=","DQ":"HPgKKefQwyeuCnk6Hhig7RStKKLjQCPa5i5VEbBk3rWaj6KqwVeT+Evb3yigwX9SxoUiIyNGEDTR+iSofG/ewnRoKuOI/W1CmegT2CvN3hgwSsCri/ksJ7XTkGmPjnygETCyWLaavmDl8bHKBIO1/REy+J/IFktrM3425nqcoOc=","Exponent":"AQAB","InverseQ":"Q8WGQhe9mQKK0n5Zdq8AoYZepcHQk+HVTtMaq+3om9W2aSX0p1HbE//hbTBYWncugMe+5uSyKbjIMIBTcH35fHePx7s6Terx1+81GWfRtifGstnyY0Nt74nBkQtdKsz08/T2My4xJT4Z1I6xPPiZp4vBnMf7jKwclMD+XtNnZ7o=","Modulus":"uHB/bJ8AeFXL88OlBsCE6stt+U9YFbH3s+FdFB9TcRe9bdp+zSprYBAzhJ4M8i9hocgUFxDIcuxnqMfMO5aLdNJUrWJqnx0k8dtqGtjoVtJiWXhpTxrgdUY+KAKzUp0pLKyhPhqmtyzSrsy22kWza9dvV849/5sDEWi31etf6CqNv59s342fdub0lNiTkcQYEanlVlm1W+Y4xF19NR8lNJvjelf6qNSE9dfmKxz4aRVAuw+ML+ihVPAJSgHTQCGYnzD3Ab1E+UVw40XS+amjOitca7KbVx8layetxeWuwp8sN7CVjSHKruCczNdnIfjrLZJCZ5A/hOsoUDotbVvhlQ==","P":"xwjIlm7ee9R9yEh1+b44VsfhliFe2dr5Ukz6PyhsxMjYe5eZ3h7srPW4uH1nO703FSfrT13RFH4v61NJi52jWXrftC0PaHVfQ2YXKNjtyCfJfnbUSXuuCZjMAWJIfrTVnTWcyHduX9G0BKMxdHBqbJjTWFabxlEE4D5iJD47MOc=","Q":"7TpZsU8QUByzEuGpi/is6vieUZImSmAgDZowr7mUjjvbs4YjYQfARp/A42MACTcPYZetGIQVvDa0kjSbwKzWZN0OmJLB0Gjr6re7K3XghUOJ/o8wGTrEHV1JKglGiPp72gXOl3YtpXZl3mdwo7S3YVBCVmRF7Z+CD3an9Y5p/iM="}} -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | /* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | for details on configuring this project to bundle and minify static web assets. */ 3 | 4 | a.navbar-brand { 5 | white-space: normal; 6 | text-align: center; 7 | word-break: break-all; 8 | } 9 | 10 | /* Sticky footer styles 11 | -------------------------------------------------- */ 12 | html { 13 | font-size: 14px; 14 | } 15 | @media (min-width: 768px) { 16 | html { 17 | font-size: 16px; 18 | } 19 | } 20 | 21 | .border-top { 22 | border-top: 1px solid #e5e5e5; 23 | } 24 | .border-bottom { 25 | border-bottom: 1px solid #e5e5e5; 26 | } 27 | 28 | .box-shadow { 29 | box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); 30 | } 31 | 32 | button.accept-policy { 33 | font-size: 1rem; 34 | line-height: inherit; 35 | } 36 | 37 | /* Sticky footer styles 38 | -------------------------------------------------- */ 39 | html { 40 | position: relative; 41 | min-height: 100%; 42 | } 43 | 44 | body { 45 | /* Margin bottom by footer height */ 46 | margin-bottom: 60px; 47 | } 48 | .footer { 49 | position: absolute; 50 | bottom: 0; 51 | width: 100%; 52 | white-space: nowrap; 53 | /* Set the fixed height of the footer here */ 54 | height: 60px; 55 | line-height: 60px; /* Vertically center the text there */ 56 | } 57 | 58 | .validation-summary-errors > ul { margin-bottom: 0;} -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/AuthServer/AuthServer/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification 2 | // for details on configuring this project to bundle and minify static web assets. 3 | 4 | // Write your JavaScript code. 5 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */ 8 | *, 9 | *::before, 10 | *::after { 11 | box-sizing: border-box; 12 | } 13 | 14 | html { 15 | font-family: sans-serif; 16 | line-height: 1.15; 17 | -webkit-text-size-adjust: 100%; 18 | -ms-text-size-adjust: 100%; 19 | -ms-overflow-style: scrollbar; 20 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 21 | } 22 | 23 | @-ms-viewport { 24 | width: device-width; 25 | } 26 | 27 | article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { 28 | display: block; 29 | } 30 | 31 | body { 32 | margin: 0; 33 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 34 | font-size: 1rem; 35 | font-weight: 400; 36 | line-height: 1.5; 37 | color: #212529; 38 | text-align: left; 39 | background-color: #fff; 40 | } 41 | 42 | [tabindex="-1"]:focus { 43 | outline: 0 !important; 44 | } 45 | 46 | hr { 47 | box-sizing: content-box; 48 | height: 0; 49 | overflow: visible; 50 | } 51 | 52 | h1, h2, h3, h4, h5, h6 { 53 | margin-top: 0; 54 | margin-bottom: 0.5rem; 55 | } 56 | 57 | p { 58 | margin-top: 0; 59 | margin-bottom: 1rem; 60 | } 61 | 62 | abbr[title], 63 | abbr[data-original-title] { 64 | text-decoration: underline; 65 | -webkit-text-decoration: underline dotted; 66 | text-decoration: underline dotted; 67 | cursor: help; 68 | border-bottom: 0; 69 | } 70 | 71 | address { 72 | margin-bottom: 1rem; 73 | font-style: normal; 74 | line-height: inherit; 75 | } 76 | 77 | ol, 78 | ul, 79 | dl { 80 | margin-top: 0; 81 | margin-bottom: 1rem; 82 | } 83 | 84 | ol ol, 85 | ul ul, 86 | ol ul, 87 | ul ol { 88 | margin-bottom: 0; 89 | } 90 | 91 | dt { 92 | font-weight: 700; 93 | } 94 | 95 | dd { 96 | margin-bottom: .5rem; 97 | margin-left: 0; 98 | } 99 | 100 | blockquote { 101 | margin: 0 0 1rem; 102 | } 103 | 104 | dfn { 105 | font-style: italic; 106 | } 107 | 108 | b, 109 | strong { 110 | font-weight: bolder; 111 | } 112 | 113 | small { 114 | font-size: 80%; 115 | } 116 | 117 | sub, 118 | sup { 119 | position: relative; 120 | font-size: 75%; 121 | line-height: 0; 122 | vertical-align: baseline; 123 | } 124 | 125 | sub { 126 | bottom: -.25em; 127 | } 128 | 129 | sup { 130 | top: -.5em; 131 | } 132 | 133 | a { 134 | color: #007bff; 135 | text-decoration: none; 136 | background-color: transparent; 137 | -webkit-text-decoration-skip: objects; 138 | } 139 | 140 | a:hover { 141 | color: #0056b3; 142 | text-decoration: underline; 143 | } 144 | 145 | a:not([href]):not([tabindex]) { 146 | color: inherit; 147 | text-decoration: none; 148 | } 149 | 150 | a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus { 151 | color: inherit; 152 | text-decoration: none; 153 | } 154 | 155 | a:not([href]):not([tabindex]):focus { 156 | outline: 0; 157 | } 158 | 159 | pre, 160 | code, 161 | kbd, 162 | samp { 163 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 164 | font-size: 1em; 165 | } 166 | 167 | pre { 168 | margin-top: 0; 169 | margin-bottom: 1rem; 170 | overflow: auto; 171 | -ms-overflow-style: scrollbar; 172 | } 173 | 174 | figure { 175 | margin: 0 0 1rem; 176 | } 177 | 178 | img { 179 | vertical-align: middle; 180 | border-style: none; 181 | } 182 | 183 | svg { 184 | overflow: hidden; 185 | vertical-align: middle; 186 | } 187 | 188 | table { 189 | border-collapse: collapse; 190 | } 191 | 192 | caption { 193 | padding-top: 0.75rem; 194 | padding-bottom: 0.75rem; 195 | color: #6c757d; 196 | text-align: left; 197 | caption-side: bottom; 198 | } 199 | 200 | th { 201 | text-align: inherit; 202 | } 203 | 204 | label { 205 | display: inline-block; 206 | margin-bottom: 0.5rem; 207 | } 208 | 209 | button { 210 | border-radius: 0; 211 | } 212 | 213 | button:focus { 214 | outline: 1px dotted; 215 | outline: 5px auto -webkit-focus-ring-color; 216 | } 217 | 218 | input, 219 | button, 220 | select, 221 | optgroup, 222 | textarea { 223 | margin: 0; 224 | font-family: inherit; 225 | font-size: inherit; 226 | line-height: inherit; 227 | } 228 | 229 | button, 230 | input { 231 | overflow: visible; 232 | } 233 | 234 | button, 235 | select { 236 | text-transform: none; 237 | } 238 | 239 | button, 240 | html [type="button"], 241 | [type="reset"], 242 | [type="submit"] { 243 | -webkit-appearance: button; 244 | } 245 | 246 | button::-moz-focus-inner, 247 | [type="button"]::-moz-focus-inner, 248 | [type="reset"]::-moz-focus-inner, 249 | [type="submit"]::-moz-focus-inner { 250 | padding: 0; 251 | border-style: none; 252 | } 253 | 254 | input[type="radio"], 255 | input[type="checkbox"] { 256 | box-sizing: border-box; 257 | padding: 0; 258 | } 259 | 260 | input[type="date"], 261 | input[type="time"], 262 | input[type="datetime-local"], 263 | input[type="month"] { 264 | -webkit-appearance: listbox; 265 | } 266 | 267 | textarea { 268 | overflow: auto; 269 | resize: vertical; 270 | } 271 | 272 | fieldset { 273 | min-width: 0; 274 | padding: 0; 275 | margin: 0; 276 | border: 0; 277 | } 278 | 279 | legend { 280 | display: block; 281 | width: 100%; 282 | max-width: 100%; 283 | padding: 0; 284 | margin-bottom: .5rem; 285 | font-size: 1.5rem; 286 | line-height: inherit; 287 | color: inherit; 288 | white-space: normal; 289 | } 290 | 291 | progress { 292 | vertical-align: baseline; 293 | } 294 | 295 | [type="number"]::-webkit-inner-spin-button, 296 | [type="number"]::-webkit-outer-spin-button { 297 | height: auto; 298 | } 299 | 300 | [type="search"] { 301 | outline-offset: -2px; 302 | -webkit-appearance: none; 303 | } 304 | 305 | [type="search"]::-webkit-search-cancel-button, 306 | [type="search"]::-webkit-search-decoration { 307 | -webkit-appearance: none; 308 | } 309 | 310 | ::-webkit-file-upload-button { 311 | font: inherit; 312 | -webkit-appearance: button; 313 | } 314 | 315 | output { 316 | display: inline-block; 317 | } 318 | 319 | summary { 320 | display: list-item; 321 | cursor: pointer; 322 | } 323 | 324 | template { 325 | display: none; 326 | } 327 | 328 | [hidden] { 329 | display: none !important; 330 | } 331 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v4.1.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2018 The Bootstrap Authors 4 | * Copyright 2011-2018 Twitter, Inc. 5 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 6 | * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) 7 | */*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}@-ms-viewport{width:device-width}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}dfn{font-style:italic}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent;-webkit-text-decoration-skip:objects}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important} 8 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | // Unobtrusive validation support library for jQuery and jQuery Validate 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | // @version v3.2.11 5 | !function(a){"function"==typeof define&&define.amd?define("jquery.validate.unobtrusive",["jquery-validation"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery-validation")):jQuery.validator.unobtrusive=a(jQuery)}(function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,.\/:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function u(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=f.unobtrusive.options||{},u=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),u("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),u("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),u("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var m,f=a.validator,v="unobtrusiveValidation";return f.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=u(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){f.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=u(this);a&&a.attachValidation()})}},m=f.unobtrusive.adapters,m.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},m.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},m.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},m.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},f.addMethod("__dummy__",function(a,e,n){return!0}),f.addMethod("regex",function(a,e,n){var t;return!!this.optional(e)||(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),f.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),f.methods.extension?(m.addSingleVal("accept","mimtype"),m.addSingleVal("extension","extension")):m.addSingleVal("extension","extension","accept"),m.addSingleVal("regex","pattern"),m.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),m.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),m.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),m.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),m.add("required",function(a){"INPUT"===a.element.tagName.toUpperCase()&&"CHECKBOX"===a.element.type.toUpperCase()||e(a,"required",!0)}),m.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),m.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),m.add("fileextensions",["extensions"],function(a){e(a,"extension",a.params.extensions)}),a(function(){f.unobtrusive.parse(document)}),f.unobtrusive}); -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/AuthServer/AuthServer/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /src/Resource.Api/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | authserver_log.txt 7 | 8 | # User-specific files 9 | *.suo 10 | *.user 11 | *.userosscache 12 | *.sln.docstates 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015/2017 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | **/Properties/launchSettings.json 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28714.193 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Resource.Api", "Resource.Api\Resource.Api.csproj", "{07809395-2C7A-4DC3-B7E5-B3F067810C7A}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {07809395-2C7A-4DC3-B7E5-B3F067810C7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {07809395-2C7A-4DC3-B7E5-B3F067810C7A}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {07809395-2C7A-4DC3-B7E5-B3F067810C7A}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {07809395-2C7A-4DC3-B7E5-B3F067810C7A}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {3BCD5E51-CBC4-4045-82BC-56B172DC980D} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace Resource.Api.Controllers 7 | { 8 | [Authorize(Policy = "ApiReader")] 9 | [Route("api/[controller]")] 10 | [ApiController] 11 | public class ValuesController : ControllerBase 12 | { 13 | // GET api/values 14 | [Authorize(Policy = "Consumer")] 15 | [HttpGet] 16 | public ActionResult> Get() 17 | { 18 | return new JsonResult(User.Claims.Select(c => new { c.Type, c.Value })); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | 5 | namespace Resource.Api 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseStartup() 17 | .UseUrls("http://localhost:5050"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/Resource.Api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authentication.JwtBearer; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using System.Security.Claims; 9 | 10 | namespace Resource.Api 11 | { 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | } 18 | 19 | public IConfiguration Configuration { get; } 20 | 21 | // This method gets called by the runtime. Use this method to add services to the container. 22 | public static void ConfigureServices(IServiceCollection services) 23 | { 24 | services.AddAuthentication(options => 25 | { 26 | options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; 27 | options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; 28 | }).AddJwtBearer(o => 29 | { 30 | o.Authority = "http://localhost:5000"; 31 | o.Audience = "resourceapi"; 32 | o.RequireHttpsMetadata = false; 33 | }); 34 | 35 | services.AddAuthorization(options => 36 | { 37 | options.AddPolicy("ApiReader", policy => policy.RequireClaim("scope", "api.read")); 38 | options.AddPolicy("Consumer", policy => policy.RequireClaim(ClaimTypes.Role, "consumer")); 39 | }); 40 | 41 | services.AddMvc(options => 42 | { 43 | options.EnableEndpointRouting = false; 44 | }) 45 | .SetCompatibilityVersion(CompatibilityVersion.Latest); 46 | } 47 | 48 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 49 | public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) 50 | { 51 | if (env.IsDevelopment()) 52 | { 53 | app.UseDeveloperExceptionPage(); 54 | } 55 | 56 | app.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); 57 | 58 | app.UseAuthentication(); 59 | 60 | app.UseMvc(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Resource.Api/Resource.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/README.md: -------------------------------------------------------------------------------- 1 | # OauthClient 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.5. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "oauth-client": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "sass" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/oauth-client", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": "src/polyfills.ts", 24 | "tsConfig": "src/tsconfig.app.json", 25 | "assets": [ 26 | "src/favicon.ico", 27 | "src/assets" 28 | ], 29 | "styles": [ 30 | "src/assets/styles.scss" 31 | ], 32 | "scripts": [], 33 | "es5BrowserSupport": true 34 | }, 35 | "configurations": { 36 | "production": { 37 | "fileReplacements": [ 38 | { 39 | "replace": "src/environments/environment.ts", 40 | "with": "src/environments/environment.prod.ts" 41 | } 42 | ], 43 | "optimization": true, 44 | "outputHashing": "all", 45 | "sourceMap": false, 46 | "extractCss": true, 47 | "namedChunks": false, 48 | "aot": true, 49 | "extractLicenses": true, 50 | "vendorChunk": false, 51 | "buildOptimizer": true, 52 | "budgets": [ 53 | { 54 | "type": "initial", 55 | "maximumWarning": "2mb", 56 | "maximumError": "5mb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "oauth-client:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "oauth-client:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "oauth-client:build" 77 | } 78 | }, 79 | "test": { 80 | "builder": "@angular-devkit/build-angular:karma", 81 | "options": { 82 | "main": "src/test.ts", 83 | "polyfills": "src/polyfills.ts", 84 | "tsConfig": "src/tsconfig.spec.json", 85 | "karmaConfig": "src/karma.conf.js", 86 | "styles": [ 87 | "src/assets/styles.scss" 88 | ], 89 | "scripts": [], 90 | "assets": [ 91 | "src/favicon.ico", 92 | "src/assets" 93 | ] 94 | } 95 | }, 96 | "lint": { 97 | "builder": "@angular-devkit/build-angular:tslint", 98 | "options": { 99 | "tsConfig": [ 100 | "src/tsconfig.app.json", 101 | "src/tsconfig.spec.json" 102 | ], 103 | "exclude": [ 104 | "**/node_modules/**" 105 | ] 106 | } 107 | } 108 | } 109 | }, 110 | "oauth-client-e2e": { 111 | "root": "e2e/", 112 | "projectType": "application", 113 | "prefix": "", 114 | "architect": { 115 | "e2e": { 116 | "builder": "@angular-devkit/build-angular:protractor", 117 | "options": { 118 | "protractorConfig": "e2e/protractor.conf.js", 119 | "devServerTarget": "oauth-client:serve" 120 | }, 121 | "configurations": { 122 | "production": { 123 | "devServerTarget": "oauth-client:serve:production" 124 | } 125 | } 126 | }, 127 | "lint": { 128 | "builder": "@angular-devkit/build-angular:tslint", 129 | "options": { 130 | "tsConfig": "e2e/tsconfig.e2e.json", 131 | "exclude": [ 132 | "**/node_modules/**" 133 | ] 134 | } 135 | } 136 | } 137 | } 138 | }, 139 | "defaultProject": "oauth-client" 140 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/Spa/oauth-client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display header title', () => { 12 | page.navigateTo(); 13 | expect(page.getElementText('app-root > app-shell > app-header > header > div > div > a > strong')).toEqual('OAuth Client'); 14 | }); 15 | 16 | it('can navigate to registration', () => { 17 | page.navigateTo(); 18 | page.clickButton('app-root > app-shell > main > div > app-index > div > div > a:nth-child(3)'); 19 | browser.waitForAngular(); 20 | expect(page.getElementText('h1')).toEqual('Register'); 21 | }); 22 | 23 | it('can navigate to login', () => { 24 | page.navigateTo(); 25 | page.clickButton('app-root > app-shell > main > div > app-index > div > div > a:nth-child(4)'); 26 | browser.waitForAngular(); 27 | expect(page.getElementText('h1')).toEqual('Login'); 28 | }); 29 | 30 | afterEach(async () => { 31 | // Assert that there are no errors emitted from the browser 32 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 33 | expect(logs).not.toContain(jasmine.objectContaining({ 34 | level: logging.Level.SEVERE, 35 | } as logging.Entry)); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getElementText(selector: string) { 9 | return element(by.css(selector)).getText() as Promise; 10 | } 11 | 12 | clickButton(selector: string) { 13 | return element(by.css(selector)).click(); 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth-client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular-devkit/build-angular": "^0.803.21", 15 | "@angular/animations": "~8.2.14", 16 | "@angular/common": "~8.2.14", 17 | "@angular/compiler": "~8.2.14", 18 | "@angular/core": "~8.2.14", 19 | "@angular/forms": "~8.2.14", 20 | "@angular/platform-browser": "~8.2.14", 21 | "@angular/platform-browser-dynamic": "~8.2.14", 22 | "@angular/router": "~8.2.14", 23 | "bootstrap": "^4.3.1", 24 | "core-js": "^2.6.9", 25 | "ngx-spinner": "^7.2.0", 26 | "oidc-client": "^1.9.1", 27 | "rxjs": "^6.5.3", 28 | "tslib": "^1.10.0", 29 | "zone.js": "~0.8.26" 30 | }, 31 | "devDependencies": { 32 | "@angular/cli": "^8.3.21", 33 | "@angular/compiler-cli": "~8.2.14", 34 | "@angular/language-service": "~8.2.14", 35 | "@types/jasmine": "~2.8.8", 36 | "@types/jasminewd2": "^2.0.7", 37 | "@types/node": "~8.9.4", 38 | "codelyzer": "~4.5.0", 39 | "jasmine-core": "~2.99.1", 40 | "jasmine-spec-reporter": "~4.2.1", 41 | "karma": "~4.0.0", 42 | "karma-chrome-launcher": "~2.2.0", 43 | "karma-coverage-istanbul-reporter": "^2.0.6", 44 | "karma-jasmine": "~1.1.2", 45 | "karma-jasmine-html-reporter": "^0.2.2", 46 | "protractor": "~5.4.0", 47 | "ts-node": "~7.0.0", 48 | "tslint": "~5.11.0", 49 | "typescript": "~3.5.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { LoginComponent } from './login/login.component'; 5 | import { RegisterComponent } from './register/register.component'; 6 | import { SharedModule } from '../shared/shared.module'; 7 | 8 | import { AccountRoutingModule } from './account.routing-module'; 9 | import { AuthService } from '../core/authentication/auth.service'; 10 | 11 | @NgModule({ 12 | declarations: [LoginComponent, RegisterComponent], 13 | providers: [AuthService], 14 | imports: [ 15 | CommonModule, 16 | FormsModule, 17 | AccountRoutingModule, 18 | SharedModule 19 | ] 20 | }) 21 | export class AccountModule { } 22 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/account.routing-module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { RegisterComponent } from './register/register.component'; 5 | import { Shell } from './../shell/shell.service'; 6 | 7 | const routes: Routes = [ 8 | Shell.childRoutes([ 9 | { path: 'login', component: LoginComponent }, 10 | { path: 'register', component: RegisterComponent } 11 | ]) 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forChild(routes)], 16 | exports: [RouterModule], 17 | providers: [] 18 | }) 19 | export class AccountRoutingModule { } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/login/login.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    {{title}}

    4 |

    This step uses the implicit flow type and redirects to the IdentityServer instance to perform the authentication step. Before you can login here, you must register a new account.

    5 | 6 |

    7 | 8 |

    9 |
    10 |
    11 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/login/login.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/account/login/login.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { By } from '@angular/platform-browser'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 5 | import { LoginComponent } from './login.component'; 6 | import { NgxSpinnerModule } from 'ngx-spinner'; 7 | import { ConfigService } from '../../shared/config.service'; 8 | 9 | describe('LoginComponent', () => { 10 | let el: HTMLElement; 11 | let component: LoginComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | providers: [ConfigService], 17 | imports: [FormsModule, HttpClientTestingModule, NgxSpinnerModule], 18 | declarations: [LoginComponent] 19 | }) 20 | .compileComponents(); 21 | })); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(LoginComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should have title "Login"', () => { 30 | expect(component.title).toEqual('Login'); 31 | }); 32 | 33 | it('should call the login method', () => { 34 | spyOn(component, 'login'); 35 | el = fixture.debugElement.query(By.css('button')).nativeElement; 36 | el.click(); 37 | expect(component.login).toHaveBeenCalled(); 38 | }); 39 | 40 | it('should create', () => { 41 | expect(component).toBeTruthy(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgxSpinnerService } from 'ngx-spinner'; 3 | import { AuthService } from '../../core/authentication/auth.service'; 4 | 5 | @Component({ 6 | selector: 'app-login', 7 | templateUrl: './login.component.html', 8 | styleUrls: ['./login.component.scss'] 9 | }) 10 | export class LoginComponent implements OnInit { 11 | 12 | constructor(private authService: AuthService, private spinner: NgxSpinnerService) { } 13 | 14 | title = "Login"; 15 | 16 | login() { 17 | this.spinner.show(); 18 | this.authService.login(); 19 | } 20 | 21 | ngOnInit() { 22 | } 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/register/register.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |

    Register

    6 |
    7 | 8 | 9 |
    10 |
    11 | 12 | 13 | Please enter a valid email 14 |
    15 |
    16 | 17 | 18 | Min. 6 characters with at least one non alphanumeric character 19 |
    20 | 21 | 22 | 25 |
    26 | 30 |
    31 |
    32 | 33 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/register/register.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/account/register/register.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { AuthService } from '../../core/authentication/auth.service'; 3 | import { By } from '@angular/platform-browser'; 4 | import { MockAuthService } from '../../shared/mocks/mock-auth.service'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { NgxSpinnerModule } from 'ngx-spinner'; 7 | import { DebugElement } from '@angular/core'; 8 | 9 | import { RegisterComponent } from './register.component'; 10 | 11 | describe('RegisterComponent', () => { 12 | let component: RegisterComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [FormsModule,NgxSpinnerModule], 18 | declarations: [RegisterComponent], 19 | providers: [ 20 | {provide: AuthService, useClass: MockAuthService} 21 | ] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(RegisterComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | 36 | it('should have disabled submit button by default', ()=>{ 37 | 38 | fixture.whenStable().then(() => { 39 | fixture.detectChanges(); // need another change detection pass after initialization 40 | let submitEL: DebugElement = fixture.debugElement.query(By.css('button[type=submit]')); 41 | expect(submitEL.nativeElement.disabled).toBe(true); 42 | }); 43 | }); 44 | 45 | it('should show invalid email tip', async(() => { 46 | 47 | fixture.whenStable().then(() => { 48 | let input = fixture.debugElement.query(By.css('#email')); 49 | let inputElement = input.nativeElement; 50 | //set input value 51 | inputElement.value = 'test value'; 52 | inputElement.dispatchEvent(new Event('input')); 53 | expect(component.userRegistration.email).toBe('test value'); 54 | var validationError = fixture.debugElement.query(By.css('.text-danger')); 55 | expect(validationError).toBeTruthy(); 56 | }); 57 | })); 58 | }); 59 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/account/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgxSpinnerService } from 'ngx-spinner'; 3 | import { finalize } from 'rxjs/operators' 4 | import { AuthService } from '../../core/authentication/auth.service'; 5 | import { UserRegistration } from '../../shared/models/user.registration'; 6 | 7 | @Component({ 8 | selector: 'app-register', 9 | templateUrl: './register.component.html', 10 | styleUrls: ['./register.component.scss'] 11 | }) 12 | export class RegisterComponent implements OnInit { 13 | 14 | success: boolean; 15 | error: string; 16 | userRegistration: UserRegistration = { name: '', email: '', password: ''}; 17 | submitted: boolean = false; 18 | 19 | constructor(private authService: AuthService, private spinner: NgxSpinnerService) { 20 | 21 | } 22 | 23 | ngOnInit() { 24 | } 25 | 26 | onSubmit() { 27 | 28 | this.spinner.show(); 29 | 30 | this.authService.register(this.userRegistration) 31 | .pipe(finalize(() => { 32 | this.spinner.hide(); 33 | })) 34 | .subscribe( 35 | result => { 36 | if(result) { 37 | this.success = true; 38 | } 39 | }, 40 | error => { 41 | this.error = error; 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { AuthCallbackComponent } from './auth-callback/auth-callback.component'; 4 | 5 | 6 | const routes: Routes = [ 7 | { path: 'auth-callback', component: AuthCallbackComponent }, 8 | // Fallback when no prior route is matched 9 | { path: '**', redirectTo: '', pathMatch: 'full' } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forRoot(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class AppRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/app.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | import { HeaderComponent } from './shell/header/header.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule 11 | ], 12 | declarations: [ 13 | AppComponent, 14 | HeaderComponent 15 | ], 16 | }).compileComponents(); 17 | })); 18 | 19 | it('should create the app', () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app).toBeTruthy(); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | 5 | // used to create fake backend 6 | import { FakeBackendProvider } from './shared/mocks/fake-backend-interceptor'; 7 | 8 | import { AppRoutingModule } from './app-routing.module'; 9 | import { AppComponent } from './app.component'; 10 | 11 | import { ConfigService } from './shared/config.service'; 12 | 13 | import { AuthCallbackComponent } from './auth-callback/auth-callback.component'; 14 | 15 | 16 | /* Module Imports */ 17 | import { CoreModule } from './core/core.module'; 18 | import { HomeModule } from './home/home.module'; 19 | import { AccountModule } from './account/account.module'; 20 | import { ShellModule } from './shell/shell.module'; 21 | import { TopSecretModule } from './top-secret/top-secret.module'; 22 | import { SharedModule } from './shared/shared.module'; 23 | 24 | @NgModule({ 25 | declarations: [ 26 | AppComponent, 27 | AuthCallbackComponent 28 | ], 29 | imports: [ 30 | BrowserModule, 31 | HttpClientModule, 32 | CoreModule, 33 | HomeModule, 34 | AccountModule, 35 | TopSecretModule, 36 | AppRoutingModule, 37 | ShellModule, 38 | SharedModule 39 | ], 40 | providers: [ 41 | ConfigService, 42 | // provider used to create fake backend 43 | FakeBackendProvider 44 | ], 45 | bootstrap: [AppComponent] 46 | }) 47 | export class AppModule { } 48 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/auth-callback/auth-callback.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 6 |
    7 |
    -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/auth-callback/auth-callback.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/auth-callback/auth-callback.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/auth-callback/auth-callback.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AuthService } from '../core/authentication/auth.service'; 3 | import { ActivatedRoute, Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-auth-callback', 7 | templateUrl: './auth-callback.component.html', 8 | styleUrls: ['./auth-callback.component.scss'] 9 | }) 10 | export class AuthCallbackComponent implements OnInit { 11 | 12 | error: boolean; 13 | 14 | constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute) {} 15 | 16 | async ngOnInit() { 17 | 18 | // check for error 19 | if (this.route.snapshot.fragment.indexOf('error') >= 0) { 20 | this.error=true; 21 | return; 22 | } 23 | 24 | await this.authService.completeAuthentication(); 25 | this.router.navigate(['/home']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/core/authentication/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | 9 | constructor(private router: Router, private authService: AuthService) { } 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 12 | if (this.authService.isAuthenticated()) { return true; } 13 | this.router.navigate(['/login'], { queryParams: { redirect: state.url }, replaceUrl: true }); 14 | return false; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/core/authentication/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 3 | import { ConfigService } from '../../shared/config.service'; 4 | 5 | import { AuthService } from './auth.service'; 6 | 7 | describe('AuthService', () => { 8 | beforeEach(() => TestBed.configureTestingModule({ 9 | providers: [ConfigService], 10 | imports: [ 11 | HttpClientTestingModule 12 | ], 13 | })); 14 | 15 | it('should be created', () => { 16 | const service: AuthService = TestBed.get(AuthService); 17 | expect(service).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/core/authentication/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { catchError } from 'rxjs/operators'; 4 | import { UserManager, UserManagerSettings, User } from 'oidc-client'; 5 | import { BehaviorSubject } from 'rxjs'; 6 | 7 | import { BaseService } from "../../shared/base.service"; 8 | import { ConfigService } from '../../shared/config.service'; 9 | 10 | @Injectable({ 11 | providedIn: 'root' 12 | }) 13 | export class AuthService extends BaseService { 14 | 15 | // Observable navItem source 16 | private _authNavStatusSource = new BehaviorSubject(false); 17 | // Observable navItem stream 18 | authNavStatus$ = this._authNavStatusSource.asObservable(); 19 | 20 | private manager = new UserManager(getClientSettings()); 21 | private user: User | null; 22 | 23 | constructor(private http: HttpClient, private configService: ConfigService) { 24 | super(); 25 | 26 | this.manager.getUser().then(user => { 27 | this.user = user; 28 | this._authNavStatusSource.next(this.isAuthenticated()); 29 | }); 30 | } 31 | 32 | login() { 33 | return this.manager.signinRedirect(); 34 | } 35 | 36 | async completeAuthentication() { 37 | this.user = await this.manager.signinRedirectCallback(); 38 | this._authNavStatusSource.next(this.isAuthenticated()); 39 | } 40 | 41 | register(userRegistration: any) { 42 | return this.http.post(this.configService.authApiURI + '/account', userRegistration).pipe(catchError(this.handleError)); 43 | } 44 | 45 | isAuthenticated(): boolean { 46 | return this.user != null && !this.user.expired; 47 | } 48 | 49 | get authorizationHeaderValue(): string { 50 | return `${this.user.token_type} ${this.user.access_token}`; 51 | } 52 | 53 | get name(): string { 54 | return this.user != null ? this.user.profile.name : ''; 55 | } 56 | 57 | async signout() { 58 | await this.manager.signoutRedirect(); 59 | } 60 | } 61 | 62 | export function getClientSettings(): UserManagerSettings { 63 | return { 64 | authority: 'http://localhost:5000', 65 | client_id: 'angular_spa', 66 | redirect_uri: 'http://localhost:4200/auth-callback', 67 | post_logout_redirect_uri: 'http://localhost:4200/', 68 | response_type:"id_token token", 69 | scope:"openid profile email api.read", 70 | filterProtocolClaims: true, 71 | loadUserInfo: true, 72 | automaticSilentRenew: true, 73 | silent_redirect_uri: 'http://localhost:4200/silent-refresh.html' 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; 2 | import { AuthService } from './authentication/auth.service'; 3 | import { AuthGuard } from './authentication/auth.guard'; 4 | 5 | 6 | @NgModule({ 7 | imports: [ 8 | ], 9 | providers: [ 10 | AuthService, 11 | AuthGuard 12 | ] 13 | }) 14 | export class CoreModule { 15 | 16 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) { 17 | // Import guard 18 | if (parentModule) { 19 | throw new Error(`${parentModule} has already been loaded. Import Core module in the AppModule only.`); 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { IndexComponent } from './index/index.component'; 5 | import { Shell } from './../shell/shell.service'; 6 | 7 | const routes: Routes = [ 8 | Shell.childRoutes([ 9 | { path: '', redirectTo: '/home', pathMatch: 'full' }, 10 | { path: 'home', component: IndexComponent } 11 | ]) 12 | ]; 13 | 14 | @NgModule({ 15 | imports: [RouterModule.forChild(routes)], 16 | exports: [RouterModule], 17 | providers: [] 18 | }) 19 | export class HomeRoutingModule { } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { IndexComponent } from './index/index.component'; 5 | import { HomeRoutingModule } from './home-routing.module'; 6 | 7 | @NgModule({ 8 | declarations: [IndexComponent], 9 | imports: [ 10 | CommonModule, 11 | RouterModule, 12 | HomeRoutingModule 13 | ] 14 | }) 15 | export class HomeModule { } 16 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/index/index.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    OAuth Client Demo

    4 |

    OAuth/OpenID Connect Demo using Angular, ASP.NET Core and IdentityServer 4

    5 | Signup » 6 | Login » 7 | Top Secret Area » 8 |
    9 |
    10 | 11 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/index/index.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/home/index/index.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/index/index.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { IndexComponent } from './index.component'; 4 | 5 | describe('IndexComponent', () => { 6 | let component: IndexComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ IndexComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(IndexComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | 26 | it('should render title in a h1 tag', () => { 27 | const fixture = TestBed.createComponent(IndexComponent); 28 | fixture.detectChanges(); 29 | const compiled = fixture.debugElement.nativeElement; 30 | expect(compiled.querySelector('h1').textContent).toContain('OAuth Client Demo'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/home/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-index', 5 | templateUrl: './index.component.html', 6 | styleUrls: ['./index.component.scss'] 7 | }) 8 | export class IndexComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/base.service.ts: -------------------------------------------------------------------------------- 1 | import { throwError } from 'rxjs'; 2 | 3 | export abstract class BaseService { 4 | 5 | constructor() { } 6 | 7 | protected handleError(error: any) { 8 | 9 | var applicationError = error.headers.get('Application-Error'); 10 | 11 | // either application-error in header or model error in body 12 | if (applicationError) { 13 | return throwError(applicationError); 14 | } 15 | 16 | var modelStateErrors: string = ''; 17 | 18 | // for now just concatenate the error descriptions, alternative we could simply pass the entire error response upstream 19 | for (var key in error.error) { 20 | if (error.error[key]) modelStateErrors += error.error[key].description + '\n'; 21 | } 22 | 23 | modelStateErrors = modelStateErrors = '' ? null : modelStateErrors; 24 | return throwError(modelStateErrors || 'Server error'); 25 | } 26 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class ConfigService { 5 | 6 | constructor() {} 7 | 8 | get authApiURI() { 9 | return 'http://localhost:5000/api'; 10 | } 11 | 12 | get resourceApiURI() { 13 | return 'http://localhost:5050/api'; 14 | } 15 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/directives/auto-focus.directive.ts: -------------------------------------------------------------------------------- 1 | import { AfterContentInit, Directive, ElementRef, Input } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[autoFocus]' 5 | }) 6 | export class AutofocusDirective implements AfterContentInit { 7 | 8 | @Input() public appAutoFocus: boolean; 9 | 10 | public constructor(private el: ElementRef) { 11 | } 12 | 13 | public ngAfterContentInit() { 14 | 15 | setTimeout(() => { 16 | this.el.nativeElement.focus(); 17 | }, 500); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/mocks/fake-backend-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpResponse, HttpHandler, HttpEvent, HttpInterceptor, HTTP_INTERCEPTORS } from '@angular/common/http'; 3 | import { Observable, of, throwError } from 'rxjs'; 4 | import { delay, mergeMap, materialize, dematerialize } from 'rxjs/operators'; 5 | 6 | @Injectable() 7 | export class FakeBackendInterceptor implements HttpInterceptor { 8 | 9 | constructor() { } 10 | 11 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 12 | // array in local storage for registered users 13 | let users: any[] = JSON.parse(localStorage.getItem('users')) || []; 14 | 15 | // wrap in delayed observable to simulate server api call 16 | return of(null).pipe(mergeMap(() => { 17 | 18 | // authenticate 19 | if (request.url.endsWith('/users/authenticate') && request.method === 'POST') { 20 | // find if any user matches login credentials 21 | let filteredUsers = users.filter(user => { 22 | return user.username === request.body.username && user.password === request.body.password; 23 | }); 24 | 25 | if (filteredUsers.length) { 26 | // if login details are valid return 200 OK with user details and fake jwt token 27 | let user = filteredUsers[0]; 28 | let body = { 29 | id: user.id, 30 | username: user.username, 31 | firstName: user.firstName, 32 | lastName: user.lastName, 33 | token: 'fake-jwt-token' 34 | }; 35 | 36 | return of(new HttpResponse({ status: 200, body: body })); 37 | } else { 38 | // else return 400 bad request 39 | return throwError({ error: { message: 'Username or password is incorrect' } }); 40 | } 41 | } 42 | 43 | // get users 44 | if (request.url.endsWith('/users') && request.method === 'GET') { 45 | // check for fake auth token in header and return users if valid, this security is implemented server side in a real application 46 | if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { 47 | return of(new HttpResponse({ status: 200, body: users })); 48 | } else { 49 | // return 401 not authorised if token is null or invalid 50 | return throwError({ error: { message: 'Unauthorised' } }); 51 | } 52 | } 53 | 54 | // get user by id 55 | if (request.url.match(/\/users\/\d+$/) && request.method === 'GET') { 56 | // check for fake auth token in header and return user if valid, this security is implemented server side in a real application 57 | if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { 58 | // find user by id in users array 59 | let urlParts = request.url.split('/'); 60 | let id = parseInt(urlParts[urlParts.length - 1]); 61 | let matchedUsers = users.filter(user => { return user.id === id; }); 62 | let user = matchedUsers.length ? matchedUsers[0] : null; 63 | 64 | return of(new HttpResponse({ status: 200, body: user })); 65 | } else { 66 | // return 401 not authorised if token is null or invalid 67 | return throwError({ error: { message: 'Unauthorised' } }); 68 | } 69 | } 70 | 71 | // register user 72 | if (request.url.endsWith('/users/register') && request.method === 'POST') { 73 | // get new user object from post body 74 | let newUser = request.body; 75 | 76 | // validation 77 | let duplicateUser = users.filter(user => { return user.username === newUser.username; }).length; 78 | if (duplicateUser) { 79 | return throwError({ error: { message: 'Username "' + newUser.username + '" is already taken' } }); 80 | } 81 | 82 | // save new user 83 | newUser.id = users.length + 1; 84 | users.push(newUser); 85 | localStorage.setItem('users', JSON.stringify(users)); 86 | 87 | // respond 200 OK 88 | return of(new HttpResponse({ status: 200 })); 89 | } 90 | 91 | // delete user 92 | if (request.url.match(/\/users\/\d+$/) && request.method === 'DELETE') { 93 | // check for fake auth token in header and return user if valid, this security is implemented server side in a real application 94 | if (request.headers.get('Authorization') === 'Bearer fake-jwt-token') { 95 | // find user by id in users array 96 | let urlParts = request.url.split('/'); 97 | let id = parseInt(urlParts[urlParts.length - 1]); 98 | for (let i = 0; i < users.length; i++) { 99 | let user = users[i]; 100 | if (user.id === id) { 101 | // delete user 102 | users.splice(i, 1); 103 | localStorage.setItem('users', JSON.stringify(users)); 104 | break; 105 | } 106 | } 107 | 108 | // respond 200 OK 109 | return of(new HttpResponse({ status: 200 })); 110 | } else { 111 | // return 401 not authorised if token is null or invalid 112 | return throwError({ error: { message: 'Unauthorised' } }); 113 | } 114 | } 115 | 116 | // pass through any requests not handled above 117 | return next.handle(request); 118 | 119 | })) 120 | 121 | // call materialize and dematerialize to ensure delay even if an error is thrown (https://github.com/Reactive-Extensions/RxJS/issues/648) 122 | .pipe(materialize()) 123 | .pipe(delay(1000)) 124 | .pipe(dematerialize()); 125 | } 126 | } 127 | 128 | export let FakeBackendProvider = { 129 | // use fake backend in place of Http service for backend-less development 130 | provide: HTTP_INTERCEPTORS, 131 | useClass: FakeBackendInterceptor, 132 | multi: true 133 | }; -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/mocks/mock-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | 3 | export class MockAuthService { 4 | 5 | authNavStatus$ = of(true); 6 | 7 | register(userRegistration: any) { 8 | return of(''); 9 | } 10 | 11 | isAuthenticated(): boolean { 12 | return false; 13 | } 14 | 15 | get authorizationHeaderValue(): string { 16 | return ''; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/mocks/mock-top-secret.service.ts: -------------------------------------------------------------------------------- 1 | import { of } from 'rxjs'; 2 | 3 | 4 | export class MockTopSecretService { 5 | fetchTopSecretData(token: string) { 6 | return of([1,2,3,4,5]); 7 | } 8 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/models/user.registration.spec.ts: -------------------------------------------------------------------------------- 1 | import { UserRegistration } from './user.registration'; 2 | 3 | describe('User.Registration', () => { 4 | it('should create an instance', () => { 5 | expect(new UserRegistration("","","")).toBeTruthy(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/models/user.registration.ts: -------------------------------------------------------------------------------- 1 | export class UserRegistration { 2 | 3 | constructor( 4 | public name: string, 5 | public email: string, 6 | public password: string 7 | ) { } 8 | } 9 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | // include directives/components commonly used in features modules in this shared modules 2 | // and import me into the feature module 3 | // importing them individually results in: Type xxx is part of the declarations of 2 modules: ... Please consider moving to a higher module... 4 | // https://github.com/angular/angular/issues/10646 5 | 6 | import { NgModule } from '@angular/core'; 7 | import { CommonModule } from '@angular/common'; 8 | 9 | import { NgxSpinnerModule } from 'ngx-spinner'; 10 | import { AutofocusDirective } from './directives/auto-focus.directive'; 11 | 12 | //https://stackoverflow.com/questions/41433766/directive-doesnt-work-in-a-sub-module 13 | //https://stackoverflow.com/questions/45032043/uncaught-error-unexpected-module-formsmodule-declared-by-the-module-appmodul/45032201 14 | 15 | @NgModule({ 16 | imports: [CommonModule, NgxSpinnerModule], 17 | declarations: [AutofocusDirective], 18 | exports: [NgxSpinnerModule, AutofocusDirective], 19 | providers: [] 20 | }) 21 | export class SharedModule { } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/header/header.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 17 |
    18 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/header/header.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/shell/header/header.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/header/header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 3 | import { AuthService } from '../../core/authentication/auth.service'; 4 | import { MockAuthService } from '../../shared/mocks/mock-auth.service'; 5 | 6 | import { HeaderComponent } from './header.component'; 7 | 8 | describe('HeaderComponent', () => { 9 | let component: HeaderComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HeaderComponent ], 15 | imports: [HttpClientTestingModule], 16 | providers: [{provide: AuthService, useClass: MockAuthService}] 17 | }) 18 | .compileComponents(); 19 | })); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(HeaderComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | import { AuthService } from '../../core/authentication/auth.service'; 3 | import { Subscription } from 'rxjs'; 4 | 5 | @Component({ 6 | selector: 'app-header', 7 | templateUrl: './header.component.html', 8 | styleUrls: ['./header.component.scss'] 9 | }) 10 | export class HeaderComponent implements OnInit, OnDestroy { 11 | 12 | name: string; 13 | isAuthenticated: boolean; 14 | subscription:Subscription; 15 | 16 | constructor(private authService:AuthService) { } 17 | 18 | ngOnInit() { 19 | this.subscription = this.authService.authNavStatus$.subscribe(status => this.isAuthenticated = status); 20 | this.name = this.authService.name; 21 | } 22 | 23 | async signout() { 24 | await this.authService.signout(); 25 | } 26 | 27 | ngOnDestroy() { 28 | // prevent memory leak when component is destroyed 29 | this.subscription.unsubscribe(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 |
    5 | 6 |
    7 |
    -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/shell/shell.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { ShellComponent } from './shell.component'; 4 | import { HeaderComponent } from './header/header.component'; 5 | import { AuthService } from '../core/authentication/auth.service'; 6 | import { MockAuthService } from '../shared/mocks/mock-auth.service'; 7 | 8 | describe('ShellComponent', () => { 9 | let component: ShellComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [ 15 | RouterTestingModule 16 | ], 17 | providers: [ 18 | {provide: AuthService, useClass: MockAuthService} 19 | ], 20 | declarations: [ HeaderComponent, ShellComponent ] 21 | }) 22 | .compileComponents(); 23 | })); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(ShellComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-shell', 5 | templateUrl: './shell.component.html', 6 | styleUrls: ['./shell.component.scss'] 7 | }) 8 | export class ShellComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { ShellComponent } from './shell.component'; 5 | import { HeaderComponent } from './header/header.component'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | RouterModule 11 | ], 12 | declarations: [ShellComponent,HeaderComponent] 13 | }) 14 | export class ShellModule { } 15 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ShellComponent } from './shell.component'; 4 | import { Shell } from './shell.service'; 5 | 6 | describe('Shell', () => { 7 | beforeEach(() => TestBed.configureTestingModule({ 8 | declarations: [ 9 | ShellComponent 10 | ], 11 | })); 12 | 13 | describe('childRoutes', () => { 14 | it('should create routes as children of shell', () => { 15 | // Prepare 16 | const testRoutes = [{ path: 'test' }]; 17 | 18 | // Act 19 | const result = Shell.childRoutes(testRoutes); 20 | 21 | // Assert 22 | expect(result.path).toBe(''); 23 | expect(result.children).toBe(testRoutes); 24 | expect(result.component).toBe(ShellComponent); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/shell/shell.service.ts: -------------------------------------------------------------------------------- 1 | import { Routes, Route } from '@angular/router'; 2 | import { ShellComponent } from './shell.component'; 3 | import { Injectable } from '@angular/core'; 4 | 5 | @Injectable({ 6 | providedIn: 'root' 7 | }) 8 | 9 | /** 10 | * Provides helper methods to create routes. 11 | */ 12 | export class Shell { 13 | 14 | /** 15 | * Creates routes using the shell component and authentication. 16 | * @param routes The routes to add. 17 | * @return {Route} The new route using shell as the base. 18 | */ 19 | static childRoutes(routes: Routes): Route { 20 | return { 21 | path: '', 22 | component: ShellComponent, 23 | children: routes, 24 | // =canActivate: [AuthenticationGuard], 25 | // Reuse ShellComponent instance when navigating between child views 26 | data: { reuse: true } 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/index/index.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Super-secret user claims sent from the server!

    5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
    #TypeValue
    {{i}}{{claim.type}}{{claim.value}}
    21 |
    22 |
    23 | 24 |
    25 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/index/index.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/app/top-secret/index/index.component.scss -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/index/index.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { NgxSpinnerModule } from 'ngx-spinner'; 3 | import { AuthService } from '../../core/authentication/auth.service'; 4 | import { MockAuthService } from '../../shared/mocks/mock-auth.service'; 5 | import { TopSecretService } from '../top-secret.service'; 6 | import { MockTopSecretService } from '../../shared/mocks/mock-top-secret.service'; 7 | 8 | import { IndexComponent } from './index.component'; 9 | 10 | describe('IndexComponent', () => { 11 | let component: IndexComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async(() => { 15 | TestBed.configureTestingModule({ 16 | declarations: [ IndexComponent ], 17 | imports: [NgxSpinnerModule], 18 | providers: [ 19 | {provide: AuthService, useClass: MockAuthService}, 20 | {provide: TopSecretService, useClass: MockTopSecretService} 21 | ] 22 | }) 23 | .compileComponents(); 24 | })); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(IndexComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgxSpinnerService } from 'ngx-spinner'; 3 | import { finalize } from 'rxjs/operators' 4 | import { AuthService } from '../../core/authentication/auth.service'; 5 | import { TopSecretService } from '../top-secret.service'; 6 | 7 | @Component({ 8 | selector: 'top-secret-index', 9 | templateUrl: './index.component.html', 10 | styleUrls: ['./index.component.scss'] 11 | }) 12 | export class IndexComponent implements OnInit { 13 | 14 | claims=null; 15 | busy: boolean; 16 | 17 | constructor(private authService: AuthService, private topSecretService: TopSecretService, private spinner: NgxSpinnerService) { 18 | } 19 | 20 | ngOnInit() { 21 | this.busy = true; 22 | this.spinner.show(); 23 | this.topSecretService.fetchTopSecretData(this.authService.authorizationHeaderValue) 24 | .pipe(finalize(() => { 25 | this.spinner.hide(); 26 | this.busy = false; 27 | })).subscribe( 28 | result => { 29 | this.claims = result; 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/top-secret.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SharedModule } from '../shared/shared.module'; 4 | import { IndexComponent } from './index/index.component'; 5 | 6 | import { TopSecretService } from '../top-secret/top-secret.service'; 7 | 8 | import { TopSecretRoutingModule } from './top-secret.routing-module'; 9 | 10 | @NgModule({ 11 | declarations: [IndexComponent], 12 | providers: [ TopSecretService], 13 | imports: [ 14 | CommonModule, 15 | TopSecretRoutingModule, 16 | SharedModule 17 | ] 18 | }) 19 | export class TopSecretModule { } 20 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/top-secret.routing-module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { Shell } from './../shell/shell.service'; 4 | import { IndexComponent } from './index/index.component'; 5 | import { AuthGuard } from '../core/authentication/auth.guard'; 6 | 7 | const routes: Routes = [ 8 | Shell.childRoutes([ 9 | { path: 'topsecret', component: IndexComponent, canActivate: [AuthGuard] } 10 | ]) 11 | ]; 12 | 13 | @NgModule({ 14 | imports: [RouterModule.forChild(routes)], 15 | exports: [RouterModule], 16 | providers: [] 17 | }) 18 | export class TopSecretRoutingModule { } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/top-secret.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 3 | import { ConfigService } from '../shared/config.service'; 4 | 5 | import { TopSecretService } from './top-secret.service'; 6 | 7 | describe('TopSecretService', () => { 8 | beforeEach(() => TestBed.configureTestingModule({ 9 | 10 | imports: [HttpClientTestingModule], 11 | providers: [ ConfigService ] 12 | })); 13 | 14 | it('should be created', () => { 15 | const service: TopSecretService = TestBed.get(TopSecretService); 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/app/top-secret/top-secret.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { catchError } from 'rxjs/operators'; 4 | import { BaseService } from "../shared/base.service"; 5 | import { ConfigService } from '../shared/config.service'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | 11 | export class TopSecretService extends BaseService { 12 | 13 | constructor(private http: HttpClient, private configService: ConfigService) { 14 | super(); 15 | } 16 | 17 | fetchTopSecretData(token: string) { 18 | 19 | const httpOptions = { 20 | headers: new HttpHeaders({ 21 | 'Content-Type': 'application/json', 22 | 'Authorization': token 23 | }) 24 | }; 25 | 26 | return this.http.get(this.configService.resourceApiURI + '/values', httpOptions).pipe(catchError(this.handleError)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/assets/images/angular_solidBlack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/assets/images/open-identity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/assets/images/open-identity.png -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/assets/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @import "~bootstrap/scss/bootstrap.scss"; 4 | 5 | main > .container { 6 | padding: 35px 0; 7 | } 8 | 9 | .ng-valid[required], .ng-valid.required { 10 | border-left: 5px solid #42A948; /* green */ 11 | } 12 | 13 | .ng-invalid:not(form) { 14 | border-left: 5px solid #a94442; /* red */ 15 | } -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmacneil/AngularASPNETCoreOAuth/8951af5ef78bcf6fbc6751b62da03dbd0d413bdc/src/Spa/oauth-client/src/favicon.ico -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OauthClient 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage/oauth-client'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags.ts'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es5", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Spa/oauth-client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rulesDirectory": [ 4 | "codelyzer" 5 | ], 6 | "rules": { 7 | "array-type": false, 8 | "arrow-parens": false, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "import-blacklist": [ 13 | true, 14 | "rxjs/Rx" 15 | ], 16 | "interface-name": false, 17 | "max-classes-per-file": false, 18 | "max-line-length": [ 19 | true, 20 | 140 21 | ], 22 | "member-access": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "static-method", 30 | "instance-method" 31 | ] 32 | } 33 | ], 34 | "no-consecutive-blank-lines": false, 35 | "no-console": [ 36 | true, 37 | "debug", 38 | "info", 39 | "time", 40 | "timeEnd", 41 | "trace" 42 | ], 43 | "no-empty": false, 44 | "no-inferrable-types": [ 45 | true, 46 | "ignore-params" 47 | ], 48 | "no-non-null-assertion": true, 49 | "no-redundant-jsdoc": true, 50 | "no-switch-case-fall-through": true, 51 | "no-use-before-declare": true, 52 | "no-var-requires": false, 53 | "object-literal-key-quotes": [ 54 | true, 55 | "as-needed" 56 | ], 57 | "object-literal-sort-keys": false, 58 | "ordered-imports": false, 59 | "quotemark": [ 60 | true, 61 | "single" 62 | ], 63 | "trailing-comma": false, 64 | "no-output-on-prefix": true, 65 | "use-input-property-decorator": true, 66 | "use-output-property-decorator": true, 67 | "use-host-property-decorator": true, 68 | "no-input-rename": true, 69 | "no-output-rename": true, 70 | "use-life-cycle-interface": true, 71 | "use-pipe-transform-interface": true, 72 | "component-class-suffix": true, 73 | "directive-class-suffix": true 74 | } 75 | } 76 | --------------------------------------------------------------------------------