├── .gitattributes ├── .gitignore ├── Angular-IdentityServer-WebAPI.sln ├── LICENSE ├── README.md └── src ├── Angular ├── .gitignore ├── Angular.csproj ├── ClientApp │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── angular.json │ ├── browserslist │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.json │ ├── karma.conf.js │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── account │ │ │ │ ├── account-routing.module.ts │ │ │ │ ├── account.module.ts │ │ │ │ ├── changepassword │ │ │ │ │ ├── changepassword.component.css │ │ │ │ │ ├── changepassword.component.html │ │ │ │ │ └── changepassword.component.ts │ │ │ │ ├── login │ │ │ │ │ ├── login.component.css │ │ │ │ │ ├── login.component.html │ │ │ │ │ └── login.component.ts │ │ │ │ ├── register │ │ │ │ │ ├── register.component.css │ │ │ │ │ ├── register.component.html │ │ │ │ │ └── register.component.ts │ │ │ │ ├── settings │ │ │ │ │ ├── settings.component.css │ │ │ │ │ ├── settings.component.html │ │ │ │ │ └── settings.component.ts │ │ │ │ └── shared │ │ │ │ │ ├── account.service.ts │ │ │ │ │ ├── auth.guard.ts │ │ │ │ │ ├── auth.service.ts │ │ │ │ │ ├── jwt-response.ts │ │ │ │ │ ├── password-match-validator.ts │ │ │ │ │ ├── profile.service.ts │ │ │ │ │ └── token.service.ts │ │ │ ├── api │ │ │ │ ├── api-routing.module.ts │ │ │ │ ├── api.component.css │ │ │ │ ├── api.component.html │ │ │ │ ├── api.component.ts │ │ │ │ └── api.module.ts │ │ │ ├── app-routing.module.ts │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── home │ │ │ │ ├── home-routing.module.ts │ │ │ │ ├── home.component.css │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.ts │ │ │ │ └── home.module.ts │ │ │ └── material.module.ts │ │ ├── assets │ │ │ ├── .gitkeep │ │ │ └── images │ │ │ │ ├── angular-white-transparent.svg │ │ │ │ └── favicon.ico │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ └── _ViewImports.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── appsettings.Development.json └── appsettings.json ├── IdentityServer ├── Config.cs ├── Controllers │ └── AccountController.cs ├── Data │ └── ApplicationDbContext.cs ├── IdentityProfileService.cs ├── IdentityServer.csproj ├── Migrations │ ├── 20190713034352_initial.Designer.cs │ ├── 20190713034352_initial.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Models │ ├── Account │ │ ├── ChangePasswordViewModel.cs │ │ └── RegisterViewModel.cs │ └── ApplicationUser.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs └── appsettings.json └── WebAPI ├── Controllers └── NumbersController.cs ├── Data └── NumberService.cs ├── Models └── Number.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Startup.cs ├── WebAPI.csproj ├── appsettings.Development.json └── appsettings.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## 2 | src/Angular/wwwroot 3 | src/Angular/Properties/PublishProfiles 4 | src/IdentityServer/Properties/PublishProfiles 5 | src/WebAPI/Properties/PublishProfiles 6 | 7 | ## Ignore Visual Studio temporary files, build results, and 8 | ## files generated by popular Visual Studio add-ons. 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | [Xx]64/ 25 | [Xx]86/ 26 | [Bb]uild/ 27 | bld/ 28 | [Bb]in/ 29 | [Oo]bj/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # DNX 50 | project.lock.json 51 | artifacts/ 52 | 53 | *_i.c 54 | *_p.c 55 | *_i.h 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.tmp_proj 70 | *.log 71 | *.vspscc 72 | *.vssscc 73 | .builds 74 | *.pidb 75 | *.svclog 76 | *.scc 77 | 78 | # Chutzpah Test files 79 | _Chutzpah* 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opendb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | *.VC.db 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | 149 | # TODO: Un-comment the next line if you do not want to checkin 150 | # your web deploy settings because they may include unencrypted 151 | # passwords 152 | #*.pubxml 153 | *.publishproj 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directory 176 | AppPackages/ 177 | BundleArtifacts/ 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | [Ss]tyle[Cc]op.* 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.pfx 193 | *.publishsettings 194 | node_modules/ 195 | orleans.codegen.cs 196 | 197 | # RIA/Silverlight projects 198 | Generated_Code/ 199 | 200 | # Backup & report files from converting an old project file 201 | # to a newer Visual Studio version. Backup files are not needed, 202 | # because we have git ;-) 203 | _UpgradeReport_Files/ 204 | Backup*/ 205 | UpgradeLog*.XML 206 | UpgradeLog*.htm 207 | 208 | # SQL Server files 209 | *.mdf 210 | *.ldf 211 | 212 | # Business Intelligence projects 213 | *.rdl.data 214 | *.bim.layout 215 | *.bim_*.settings 216 | 217 | # Microsoft Fakes 218 | FakesAssemblies/ 219 | 220 | # GhostDoc plugin setting file 221 | *.GhostDoc.xml 222 | 223 | # Node.js Tools for Visual Studio 224 | .ntvs_analysis.dat 225 | 226 | # Visual Studio 6 build log 227 | *.plg 228 | 229 | # Visual Studio 6 workspace options file 230 | *.opt 231 | 232 | # Visual Studio LightSwitch build output 233 | **/*.HTMLClient/GeneratedArtifacts 234 | **/*.DesktopClient/GeneratedArtifacts 235 | **/*.DesktopClient/ModelManifest.xml 236 | **/*.Server/GeneratedArtifacts 237 | **/*.Server/ModelManifest.xml 238 | _Pvt_Extensions 239 | 240 | # LightSwitch generated files 241 | GeneratedArtifacts/ 242 | ModelManifest.xml 243 | 244 | # Paket dependency manager 245 | .paket/paket.exe 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | src/Angular2-Webpack/Properties/PublishProfiles 251 | src/AspNetCoreWebApi/Properties/PublishProfiles 252 | src/AuthServer/Properties/PublishProfiles 253 | src/Angular2-Webpack/wwwroot 254 | -------------------------------------------------------------------------------- /Angular-IdentityServer-WebAPI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{437CD583-09FD-4799-9FCE-3F2C2446DCCC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "src\IdentityServer\IdentityServer.csproj", "{C4256096-0211-44EB-9582-DFDBE3024A91}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAPI", "src\WebAPI\WebAPI.csproj", "{C9B11185-F5B5-4A80-8313-E51A49A87CC7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Angular", "src\Angular\Angular.csproj", "{4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Debug|x64 = Debug|x64 18 | Debug|x86 = Debug|x86 19 | Release|Any CPU = Release|Any CPU 20 | Release|x64 = Release|x64 21 | Release|x86 = Release|x86 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|x64.ActiveCfg = Debug|Any CPU 27 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|x64.Build.0 = Debug|Any CPU 28 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Debug|x86.Build.0 = Debug|Any CPU 30 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|x64.ActiveCfg = Release|Any CPU 33 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|x64.Build.0 = Release|Any CPU 34 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|x86.ActiveCfg = Release|Any CPU 35 | {C4256096-0211-44EB-9582-DFDBE3024A91}.Release|x86.Build.0 = Release|Any CPU 36 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|x64.ActiveCfg = Debug|Any CPU 39 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|x64.Build.0 = Debug|Any CPU 40 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|x86.ActiveCfg = Debug|Any CPU 41 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Debug|x86.Build.0 = Debug|Any CPU 42 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|x64.ActiveCfg = Release|Any CPU 45 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|x64.Build.0 = Release|Any CPU 46 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|x86.ActiveCfg = Release|Any CPU 47 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7}.Release|x86.Build.0 = Release|Any CPU 48 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|x64.ActiveCfg = Debug|Any CPU 51 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|x64.Build.0 = Debug|Any CPU 52 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|x86.ActiveCfg = Debug|Any CPU 53 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Debug|x86.Build.0 = Debug|Any CPU 54 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|x64.ActiveCfg = Release|Any CPU 57 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|x64.Build.0 = Release|Any CPU 58 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|x86.ActiveCfg = Release|Any CPU 59 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE}.Release|x86.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {C4256096-0211-44EB-9582-DFDBE3024A91} = {437CD583-09FD-4799-9FCE-3F2C2446DCCC} 66 | {C9B11185-F5B5-4A80-8313-E51A49A87CC7} = {437CD583-09FD-4799-9FCE-3F2C2446DCCC} 67 | {4A1B51FD-8799-4C8C-9BD6-4FFA3544B7DE} = {437CD583-09FD-4799-9FCE-3F2C2446DCCC} 68 | EndGlobalSection 69 | GlobalSection(ExtensibilityGlobals) = postSolution 70 | SolutionGuid = {71BC31D9-FCD7-4671-8119-1B2F348A8FD7} 71 | EndGlobalSection 72 | EndGlobal 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mark Laygo 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 | 2 | Angular IdentityServer WebAPI 3 | ========= 4 | 5 | This repo consist of 3 basic projects. Client, Authentication Service and a WebAPI. The reason I used 3 separate project is to simulate 6 | a scenario where the client access an already existing api and an authentication service. 7 | 8 | ## Features 9 | 10 | * Token based authentication 11 | * Web API access 12 | * Login, Register & Change password 13 | * Form input validation 14 | * Angular Material Design 15 | 16 | ## Live demo 17 | 18 | [Live demo][d1] 19 | 20 | ## Steps to run the project 21 | Note: First time you open the projet, it will install / download / restore all the dependencies (2 - 5mins) depending on your internet speed.
22 | 23 | After Visual Studio installed all the dependencies 24 | 1. In Visual Studio run the project `IdentityServer` 25 | 2. In Visual Studio run the project `WebAPI` 26 | 3. Open cmd/powershell or any other command line interface and navigate to `Angular-IdentityServer-WebAPI/src/Angular/ClientApp` then type `npm install`. 27 | 4. then type the command `ng serve` to run the angular app. 28 | 5. Open your browser and navigate to `localhost:4200` 29 | 30 | ## What's included 31 | 32 | * Angular 33 | * Webpack 34 | * ASP.NET Core 35 | * IdentityServer 36 | * Angular2-jwt 37 | * Material 38 | 39 | ## References 40 | 41 | [http://docs.identityserver.io/en/release/quickstarts/0_overview.html][ref1]
42 | [https://github.com/auth0/angular2-jwt][ref2]
43 | [http://blog.ionic.io/ionic-2-and-auth0/][ref3]
44 | [https://auth0.com/docs/quickstart/spa/angular2][ref4]
45 | [http://stackoverflow.com/questions/31788681/angular2-validator-which-relies-on-multiple-form-fields/34582914#34582914][ref5]
46 | [https://angular.io/docs/ts/latest/guide/router.html#!#teach-authguard-to-authenticate][ref6]
47 | [https://angular.io/docs/ts/latest/cookbook/form-validation.html#!#reactive][ref7]
48 | [https://github.com/angular/material2][ref8]
49 | 50 | ## License 51 | 52 | MIT 53 | 54 | [d1]: https://angular-d1.azurewebsites.net 55 | [ref1]: http://docs.identityserver.io/en/release/quickstarts/0_overview.html 56 | [ref2]: https://github.com/auth0/angular2-jwt 57 | [ref3]: http://blog.ionic.io/ionic-2-and-auth0 58 | [ref4]: https://auth0.com/docs/quickstart/spa/angular2 59 | [ref5]: http://stackoverflow.com/questions/31788681/angular2-validator-which-relies-on-multiple-form-fields/34582914#34582914 60 | [ref6]: https://angular.io/docs/ts/latest/guide/router.html#!#teach-authguard-to-authenticate 61 | [ref7]: https://angular.io/docs/ts/latest/cookbook/form-validation.html#!#reactive 62 | [ref8]: https://github.com/angular/material2 63 | -------------------------------------------------------------------------------- /src/Angular/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | bin/ 23 | Bin/ 24 | obj/ 25 | Obj/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | *_i.c 44 | *_p.c 45 | *_i.h 46 | *.ilk 47 | *.meta 48 | *.obj 49 | *.pch 50 | *.pdb 51 | *.pgc 52 | *.pgd 53 | *.rsp 54 | *.sbr 55 | *.tlb 56 | *.tli 57 | *.tlh 58 | *.tmp 59 | *.tmp_proj 60 | *.log 61 | *.vspscc 62 | *.vssscc 63 | .builds 64 | *.pidb 65 | *.svclog 66 | *.scc 67 | 68 | # Chutzpah Test files 69 | _Chutzpah* 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opendb 76 | *.opensdf 77 | *.sdf 78 | *.cachefile 79 | 80 | # Visual Studio profiler 81 | *.psess 82 | *.vsp 83 | *.vspx 84 | *.sap 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | nCrunchTemp_* 110 | 111 | # MightyMoose 112 | *.mm.* 113 | AutoTest.Net/ 114 | 115 | # Web workbench (sass) 116 | .sass-cache/ 117 | 118 | # Installshield output folder 119 | [Ee]xpress/ 120 | 121 | # DocProject is a documentation generator add-in 122 | DocProject/buildhelp/ 123 | DocProject/Help/*.HxT 124 | DocProject/Help/*.HxC 125 | DocProject/Help/*.hhc 126 | DocProject/Help/*.hhk 127 | DocProject/Help/*.hhp 128 | DocProject/Help/Html2 129 | DocProject/Help/html 130 | 131 | # Click-Once directory 132 | publish/ 133 | 134 | # Publish Web Output 135 | *.[Pp]ublish.xml 136 | *.azurePubxml 137 | # TODO: Comment the next line if you want to checkin your web deploy settings 138 | # but database connection strings (with potential passwords) will be unencrypted 139 | *.pubxml 140 | *.publishproj 141 | 142 | # NuGet Packages 143 | *.nupkg 144 | # The packages folder can be ignored because of Package Restore 145 | **/packages/* 146 | # except build/, which is used as an MSBuild target. 147 | !**/packages/build/ 148 | # Uncomment if necessary however generally it will be regenerated when needed 149 | #!**/packages/repositories.config 150 | 151 | # Microsoft Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Microsoft Azure Emulator 156 | ecf/ 157 | rcf/ 158 | 159 | # Microsoft Azure ApplicationInsights config file 160 | ApplicationInsights.config 161 | 162 | # Windows Store app package directory 163 | AppPackages/ 164 | BundleArtifacts/ 165 | 166 | # Visual Studio cache files 167 | # files ending in .cache can be ignored 168 | *.[Cc]ache 169 | # but keep track of directories ending in .cache 170 | !*.[Cc]ache/ 171 | 172 | # Others 173 | ClientBin/ 174 | ~$* 175 | *~ 176 | *.dbmdl 177 | *.dbproj.schemaview 178 | *.pfx 179 | *.publishsettings 180 | orleans.codegen.cs 181 | 182 | /node_modules 183 | 184 | # RIA/Silverlight projects 185 | Generated_Code/ 186 | 187 | # Backup & report files from converting an old project file 188 | # to a newer Visual Studio version. Backup files are not needed, 189 | # because we have git ;-) 190 | _UpgradeReport_Files/ 191 | Backup*/ 192 | UpgradeLog*.XML 193 | UpgradeLog*.htm 194 | 195 | # SQL Server files 196 | *.mdf 197 | *.ldf 198 | 199 | # Business Intelligence projects 200 | *.rdl.data 201 | *.bim.layout 202 | *.bim_*.settings 203 | 204 | # Microsoft Fakes 205 | FakesAssemblies/ 206 | 207 | # GhostDoc plugin setting file 208 | *.GhostDoc.xml 209 | 210 | # Node.js Tools for Visual Studio 211 | .ntvs_analysis.dat 212 | 213 | # Visual Studio 6 build log 214 | *.plg 215 | 216 | # Visual Studio 6 workspace options file 217 | *.opt 218 | 219 | # Visual Studio LightSwitch build output 220 | **/*.HTMLClient/GeneratedArtifacts 221 | **/*.DesktopClient/GeneratedArtifacts 222 | **/*.DesktopClient/ModelManifest.xml 223 | **/*.Server/GeneratedArtifacts 224 | **/*.Server/ModelManifest.xml 225 | _Pvt_Extensions 226 | 227 | # Paket dependency manager 228 | .paket/paket.exe 229 | 230 | # FAKE - F# Make 231 | .fake/ 232 | -------------------------------------------------------------------------------- /src/Angular/Angular.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | true 6 | Latest 7 | false 8 | ClientApp\ 9 | $(DefaultItemExcludes);$(SpaRoot)node_modules\** 10 | 11 | 12 | false 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | %(DistFiles.Identity) 53 | PreserveNewest 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/.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/Angular/ClientApp/.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/Angular/ClientApp/README.md: -------------------------------------------------------------------------------- 1 | # ClientApp 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.0. 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/Angular/ClientApp/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ClientApp": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/ClientApp", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": false, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "aot": true, 45 | "extractLicenses": true, 46 | "vendorChunk": false, 47 | "buildOptimizer": true, 48 | "budgets": [ 49 | { 50 | "type": "initial", 51 | "maximumWarning": "2mb", 52 | "maximumError": "5mb" 53 | } 54 | ] 55 | } 56 | } 57 | }, 58 | "serve": { 59 | "builder": "@angular-devkit/build-angular:dev-server", 60 | "options": { 61 | "browserTarget": "ClientApp:build" 62 | }, 63 | "configurations": { 64 | "production": { 65 | "browserTarget": "ClientApp:build:production" 66 | } 67 | } 68 | }, 69 | "extract-i18n": { 70 | "builder": "@angular-devkit/build-angular:extract-i18n", 71 | "options": { 72 | "browserTarget": "ClientApp:build" 73 | } 74 | }, 75 | "test": { 76 | "builder": "@angular-devkit/build-angular:karma", 77 | "options": { 78 | "main": "src/test.ts", 79 | "polyfills": "src/polyfills.ts", 80 | "tsConfig": "tsconfig.spec.json", 81 | "karmaConfig": "karma.conf.js", 82 | "assets": [ 83 | "src/favicon.ico", 84 | "src/assets" 85 | ], 86 | "styles": [ 87 | "src/styles.css" 88 | ], 89 | "scripts": [] 90 | } 91 | }, 92 | "lint": { 93 | "builder": "@angular-devkit/build-angular:tslint", 94 | "options": { 95 | "tsConfig": [ 96 | "tsconfig.app.json", 97 | "tsconfig.spec.json", 98 | "e2e/tsconfig.json" 99 | ], 100 | "exclude": [ 101 | "**/node_modules/**" 102 | ] 103 | } 104 | }, 105 | "e2e": { 106 | "builder": "@angular-devkit/build-angular:protractor", 107 | "options": { 108 | "protractorConfig": "e2e/protractor.conf.js", 109 | "devServerTarget": "ClientApp:serve" 110 | }, 111 | "configurations": { 112 | "production": { 113 | "devServerTarget": "ClientApp:serve:production" 114 | } 115 | } 116 | } 117 | } 118 | }}, 119 | "defaultProject": "ClientApp" 120 | } -------------------------------------------------------------------------------- /src/Angular/ClientApp/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /src/Angular/ClientApp/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /src/Angular/ClientApp/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 welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to ClientApp!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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/ClientApp'), 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/Angular/ClientApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client-app", 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/animations": "^8.1.1", 15 | "@angular/cdk": "^8.0.2", 16 | "@angular/common": "~8.1.0", 17 | "@angular/compiler": "~8.1.0", 18 | "@angular/core": "~8.1.0", 19 | "@angular/forms": "~8.1.0", 20 | "@angular/material": "^8.0.2", 21 | "@angular/platform-browser": "~8.1.0", 22 | "@angular/platform-browser-dynamic": "~8.1.0", 23 | "@angular/router": "~8.1.0", 24 | "@auth0/angular-jwt": "^2.1.1", 25 | "rxjs": "~6.4.0", 26 | "tslib": "^1.9.0", 27 | "zone.js": "~0.9.1" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~0.801.0", 31 | "@angular/cli": "~8.1.0", 32 | "@angular/compiler-cli": "~8.1.0", 33 | "@angular/language-service": "~8.1.0", 34 | "@types/node": "~8.9.4", 35 | "@types/jasmine": "~3.3.8", 36 | "@types/jasminewd2": "~2.0.3", 37 | "codelyzer": "^5.0.0", 38 | "jasmine-core": "~3.4.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.1.0", 41 | "karma-chrome-launcher": "~2.2.0", 42 | "karma-coverage-istanbul-reporter": "~2.0.1", 43 | "karma-jasmine": "~2.0.1", 44 | "karma-jasmine-html-reporter": "^1.4.0", 45 | "protractor": "~5.4.0", 46 | "ts-node": "~7.0.0", 47 | "tslint": "~5.15.0", 48 | "typescript": "~3.4.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/account-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { AuthGuard } from './shared/auth.guard'; 5 | 6 | import { LoginComponent } from './login/login.component'; 7 | import { RegisterComponent } from './register/register.component'; 8 | import { SettingsComponent } from './settings/settings.component'; 9 | import { ChangepasswordComponent } from './changepassword/changepassword.component'; 10 | 11 | 12 | const routes: Routes = [ 13 | { path: 'login', component: LoginComponent }, 14 | { path: 'register', component: RegisterComponent }, 15 | { path: 'accountsettings', component: SettingsComponent, canActivate: [AuthGuard] }, 16 | { path: 'changepassword', component: ChangepasswordComponent, canActivate: [AuthGuard] } 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [RouterModule.forRoot(routes)], 21 | exports: [RouterModule] 22 | }) 23 | export class AccountRoutingModule { } 24 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/account.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { AccountRoutingModule } from './account-routing.module'; 6 | import { MaterialModule } from '../material.module'; 7 | 8 | import { LoginComponent } from './login/login.component'; 9 | import { RegisterComponent } from './register/register.component'; 10 | import { SettingsComponent } from './settings/settings.component'; 11 | import { ChangepasswordComponent } from './changepassword/changepassword.component'; 12 | 13 | @NgModule({ 14 | declarations: [LoginComponent, RegisterComponent, SettingsComponent, ChangepasswordComponent,], 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | AccountRoutingModule, 20 | MaterialModule 21 | ] 22 | }) 23 | export class AccountModule { } 24 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/changepassword/changepassword.component.css: -------------------------------------------------------------------------------- 1 | mat-card { 2 | width: 350px; 3 | } 4 | 5 | mat-toolbar { 6 | margin-bottom: 40px; 7 | } 8 | 9 | mat-card-actions { 10 | display: flex; 11 | justify-content: center; 12 | } 13 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/changepassword/changepassword.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Change password

3 |
4 |
5 | 6 | 7 |
9 | {{ actionErrorMsg }} 10 |

11 | 12 | 13 | 14 | 15 | 22 | 23 | 26 | 27 |
28 | 29 | 30 | 31 | 37 | 38 | 41 | 42 |
43 | 44 | 45 | 46 | 52 | 53 | 56 | 57 |
58 | 59 |
60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/changepassword/changepassword.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Location } from '@angular/common'; 3 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 4 | 5 | import { AccountService } from '../shared/account.service'; 6 | import { PasswordMatchValidator } from './../shared/password-match-validator'; 7 | 8 | export class ChangePasswordModel { 9 | public oldPassword: string; 10 | public newPassword: string; 11 | public confirmPassword: string; 12 | } 13 | 14 | @Component({ 15 | selector: 'app-changepassword', 16 | templateUrl: './changepassword.component.html', 17 | styleUrls: ['./changepassword.component.css'] 18 | }) 19 | export class ChangepasswordComponent implements OnInit { 20 | private loading: boolean; 21 | private actionError: boolean; 22 | private actionErrorMsg: string; 23 | private alertStatus: boolean; 24 | private changePasswordModel: ChangePasswordModel; 25 | private changePasswordForm: FormGroup; 26 | 27 | constructor(private accountService: AccountService, private fb: FormBuilder, private location: Location) { } 28 | 29 | ngOnInit(): void { 30 | this.changePasswordModel = new ChangePasswordModel(); 31 | this.buildForm(); 32 | } 33 | 34 | private onSubmit(): void { 35 | this.loading = true; 36 | this.accountService.changePassword(this.changePasswordForm.value.oldPassword, this.changePasswordForm.value.newPassword) 37 | .subscribe( 38 | data => { 39 | this.actionError = true; // show the error 40 | 41 | if (data.code == 'Succeeded') { 42 | this.alertStatus = false; 43 | this.actionErrorMsg = data.msg; 44 | } else if (data.code === 'PasswordMismatch') { 45 | this.alertStatus = true; // show alert error 46 | this.actionErrorMsg = data.msg; 47 | } 48 | 49 | this.changePasswordForm.reset(); 50 | this.loading = false; 51 | }, 52 | error => { 53 | this.loading = false; 54 | this.actionError = true; 55 | } 56 | ); 57 | } 58 | 59 | private goBack() { 60 | this.location.back(); 61 | } 62 | 63 | // https://angular.io/guide/form-validation#reactive-form-validation 64 | private buildForm() { 65 | this.changePasswordForm = this.fb.group({ 66 | oldPassword: [this.changePasswordModel.oldPassword, Validators.required], 67 | newPassword: [this.changePasswordModel.oldPassword, [ 68 | Validators.required, 69 | Validators.minLength(4) 70 | ] 71 | ], 72 | confirmPassword: [this.changePasswordModel.oldPassword, Validators.required], 73 | }, { validator: PasswordMatchValidator('newPassword', 'confirmPassword') }); 74 | 75 | this.changePasswordForm.valueChanges.subscribe(data => this.onValueChanged(data)); 76 | 77 | this.onValueChanged(); // re(set) validation messages now 78 | } 79 | 80 | private onValueChanged(data?: any) { 81 | if (!this.changePasswordForm) { 82 | return; 83 | } 84 | const form = this.changePasswordForm; 85 | 86 | for (const field in this.formErrors) { 87 | // clear previous error message (if any) 88 | this.formErrors[field] = ''; 89 | const control = form.get(field); 90 | 91 | if (control && control.dirty && !control.valid) { 92 | const messages = this.validationMessages[field]; 93 | for (const key in control.errors) { 94 | this.formErrors[field] += messages[key] + '
'; 95 | } 96 | } 97 | } 98 | 99 | // validation for confirm password 100 | const confirmPasswordError = 'doesNotMatch'; 101 | const confirmPasswordKey = 'confirmPassword'; 102 | if (form.hasError(confirmPasswordError)) { 103 | const messages = this.validationMessages[confirmPasswordKey]; 104 | this.formErrors[confirmPasswordKey] += messages[confirmPasswordError] + '
'; 105 | } 106 | } 107 | 108 | private formErrors = { 109 | 'oldPassword': '', 110 | 'newPassword': '', 111 | 'confirmPassword': '' 112 | } 113 | 114 | private validationMessages = { 115 | 'oldPassword': { 116 | 'required': 'Password is required.', 117 | }, 118 | 'newPassword': { 119 | 'required': 'Password is required.', 120 | 'minlength': 'Password must be at least 4 characters long.' 121 | }, 122 | 'confirmPassword': { 123 | 'required': 'Confirm password is required.', 124 | 'doesNotMatch': 'Password does not match.' 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/login/login.component.css: -------------------------------------------------------------------------------- 1 | .card-container { 2 | padding-top: 50px; 3 | display: flex; 4 | justify-content: center; 5 | } 6 | 7 | mat-card { 8 | width: 350px; 9 | } 10 | 11 | mat-toolbar { 12 | margin-bottom: 40px; 13 | } 14 | 15 | mat-card-actions { 16 | display: flex; 17 | justify-content: center; 18 | } 19 | 20 | a, a:hover, a:visited { 21 | color: #2196f3; 22 | cursor: pointer; 23 | text-decoration: none; 24 | } 25 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | 8 | 9 | {{ toolbarTitle }} 10 | 11 | 12 | 13 |
14 | {{ actionErrorMsg }} 15 |

16 | 17 | 18 | 19 | 20 | 26 | 27 | 30 | 31 |
32 | 33 | 34 | 35 | 41 | 42 | 45 |
46 | 47 | 48 |
49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, Router } from '@angular/router'; 3 | import { FormGroup, FormBuilder, Validators } from '@angular/forms'; 4 | 5 | import { AuthService } from '../shared/auth.service'; 6 | 7 | export class LoginModel { 8 | constructor( 9 | public email: string, 10 | public password: string 11 | ) { } 12 | } 13 | 14 | export interface ErrorLoginResponse { 15 | error: string; 16 | error_description: string; 17 | } 18 | 19 | @Component({ 20 | selector: 'app-login', 21 | templateUrl: './login.component.html', 22 | styleUrls: ['./login.component.css'] 23 | }) 24 | export class LoginComponent implements OnInit { 25 | private toolbarTitle: string = 'Login Form'; 26 | private loading: boolean; 27 | private returnUrl: string; 28 | private actionError: boolean; 29 | private actionErrorMsg: string; 30 | private loginModel: LoginModel; 31 | private loginForm: FormGroup; 32 | 33 | constructor(private fb: FormBuilder, private authService: AuthService, private route: ActivatedRoute, private router: Router) { 34 | this.returnUrl = this.route.snapshot.queryParams['returnUrl']; // get returnUrl parameter 35 | } 36 | 37 | ngOnInit(): void { 38 | this.loginModel = new LoginModel('test@test.com', '11234'); 39 | this.buildForm(); 40 | } 41 | 42 | private onSubmit(): void { 43 | this.loading = true; 44 | this.authService.signIn(this.loginForm.value.email, this.loginForm.value.password) 45 | .subscribe( 46 | () => { 47 | if (this.authService.isAuthenticated()) { 48 | // start schedule token refresh 49 | this.authService.scheduleRefresh(); 50 | 51 | // check if there is a returnUrl, otherwise navigate to home 52 | let redirectUrl = this.returnUrl ? this.returnUrl : '/home'; 53 | this.router.navigate([redirectUrl]); 54 | } 55 | }, 56 | error => { 57 | if (error.error_description === 'invalid_username_or_password') { 58 | this.actionError = true; 59 | this.actionErrorMsg = 'Email or password is incorrect'; 60 | this.loginForm.get('password').reset(); 61 | } 62 | 63 | this.loading = false; 64 | } 65 | ); 66 | } 67 | 68 | // https://angular.io/guide/form-validation#reactive-form-validation 69 | private buildForm(): void { 70 | this.loginForm = this.fb.group({ 71 | email: [this.loginModel.email, [ 72 | Validators.required, 73 | Validators.email] 74 | ], 75 | password: [this.loginModel.password, Validators.required] 76 | }); 77 | 78 | this.loginForm.valueChanges.subscribe(data => this.onValueChanged(data)); 79 | 80 | this.onValueChanged(); // re(set) validation messages now 81 | } 82 | 83 | private onValueChanged(data?: any) { 84 | if (!this.loginForm) { 85 | return; 86 | } 87 | const form = this.loginForm; 88 | 89 | for (const field in this.formErrors) { 90 | // clear previous error message (if any) 91 | this.formErrors[field] = ''; 92 | const control = form.get(field); 93 | 94 | if (control && control.dirty && !control.valid) { 95 | const messages = this.validationMessages[field]; 96 | for (const key in control.errors) { 97 | this.formErrors[field] += messages[key] + '
'; 98 | } 99 | } 100 | } 101 | } 102 | 103 | private formErrors = { 104 | 'email': '', 105 | 'password': '' 106 | } 107 | 108 | private validationMessages = { 109 | 'email': { 110 | 'required': 'Email is required.', 111 | 'email': 'Email is invalid.' 112 | }, 113 | 'password': { 114 | 'required': 'Password is required.' 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/register/register.component.css: -------------------------------------------------------------------------------- 1 | mat-card { 2 | width: 350px; 3 | } 4 | 5 | .card-container { 6 | padding-top: 50px; 7 | display: flex; 8 | justify-content: center; 9 | } 10 | 11 | mat-toolbar { 12 | margin-bottom: 40px; 13 | } 14 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | 8 | 9 | {{ toolbarTitle }} 10 | 11 | 12 | 13 |
15 | {{ actionErrorMsg }} 16 |

17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | 39 | 40 | 43 | 44 |
45 | 46 | 47 | 48 | 54 | 55 | 58 | 59 |
60 | 61 | 62 |
63 |
64 |
65 |
66 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | 4 | import { AccountService } from '../shared/account.service'; 5 | import { PasswordMatchValidator } from '../shared/password-match-validator'; 6 | 7 | export class RegisterModel { 8 | constructor( 9 | public email: string, 10 | public password: string, 11 | public confirmPassword: string, 12 | ) { } 13 | } 14 | 15 | @Component({ 16 | selector: 'app-register', 17 | templateUrl: './register.component.html', 18 | styleUrls: ['./register.component.css'] 19 | }) 20 | export class RegisterComponent implements OnInit { 21 | private toolbarTitle: string = 'Registration Form'; 22 | private loading: boolean; 23 | private actionError: boolean; 24 | private actionErrorMsg: string; 25 | private alertStatus: boolean; 26 | private registerModel: RegisterModel; 27 | private registerForm: FormGroup; 28 | 29 | constructor(private accountService: AccountService, private fb: FormBuilder) { } 30 | 31 | ngOnInit(): void { 32 | this.registerModel = new RegisterModel("test@test.com", "11234", "11234"); 33 | this.buildForm(); 34 | } 35 | 36 | private onSubmit(): void { 37 | this.loading = true; 38 | this.accountService.register(this.registerForm.value.email, this.registerForm.value.password) 39 | .subscribe( 40 | data => { 41 | this.actionError = true; // show the error 42 | 43 | if (data.code == 'Succeeded') { 44 | this.alertStatus = false; 45 | this.actionErrorMsg = data.msg; 46 | this.registerForm.reset(); 47 | } else if (data.code === 'DuplicateUserName') { 48 | this.alertStatus = true; // show alert error 49 | this.actionErrorMsg = data.msg; 50 | this.registerForm.get('password').reset(); 51 | this.registerForm.get('confirmPassword').reset(); 52 | } 53 | 54 | this.loading = false; 55 | }, 56 | error => { 57 | this.loading = false; 58 | } 59 | ); 60 | } 61 | 62 | // https://angular.io/guide/form-validation#reactive-form-validation 63 | private buildForm() { 64 | this.registerForm = this.fb.group({ 65 | email: [this.registerModel.email, [ 66 | Validators.required, 67 | Validators.email 68 | ] 69 | ], 70 | password: [this.registerModel.password, [ 71 | Validators.required, 72 | Validators.minLength(4) 73 | ] 74 | ], 75 | confirmPassword: [this.registerModel.confirmPassword, Validators.required] 76 | }, { validator: PasswordMatchValidator('password', 'confirmPassword') }); 77 | 78 | this.registerForm.valueChanges.subscribe(data => this.onValueChanged(data)); 79 | 80 | this.onValueChanged(); // re(set) validation messages now 81 | } 82 | 83 | private onValueChanged(data?: any) { 84 | if (!this.registerForm) { 85 | return; 86 | } 87 | const form = this.registerForm; 88 | 89 | for (const field in this.formErrors) { 90 | // clear previous error message (if any) 91 | this.formErrors[field] = ''; 92 | const control = form.get(field); 93 | 94 | if (control && control.dirty && !control.valid) { 95 | const messages = this.validationMessages[field]; 96 | for (const key in control.errors) { 97 | this.formErrors[field] += messages[key] + '
'; 98 | } 99 | } 100 | } 101 | 102 | // validation for confirm password 103 | const confirmPasswordError = 'doesNotMatch'; 104 | const confirmPasswordKey = 'confirmPassword'; 105 | if (form.hasError(confirmPasswordError)) { 106 | const messages = this.validationMessages[confirmPasswordKey]; 107 | this.formErrors[confirmPasswordKey] += messages[confirmPasswordError] + '
'; 108 | } 109 | } 110 | 111 | private formErrors = { 112 | 'email': '', 113 | 'password': '', 114 | 'confirmPassword': '' 115 | } 116 | 117 | private validationMessages = { 118 | 'email': { 119 | 'required': 'Email is required.', 120 | 'email': 'Email is invalid.' 121 | }, 122 | 'password': { 123 | 'required': 'Password is required.', 124 | 'minlength': 'Password must be at least 4 characters long.' 125 | }, 126 | 'confirmPassword': { 127 | 'required': 'Confirm password is required.', 128 | 'doesNotMatch': 'Password does not match.' 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/settings/settings.component.css: -------------------------------------------------------------------------------- 1 | .account-card { 2 | width: 400px; 3 | } 4 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Account settings

3 |
4 | 17 |
18 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-settings', 5 | templateUrl: './settings.component.html', 6 | styleUrls: ['./settings.component.css'] 7 | }) 8 | export class SettingsComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/account.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, of, throwError, BehaviorSubject, interval } from 'rxjs'; 3 | import { catchError, map, tap } from 'rxjs/operators'; 4 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 5 | 6 | import { ProfileService } from './profile.service'; 7 | import { environment } from '../../../environments/environment'; 8 | 9 | const httpOptions = { 10 | headers: new HttpHeaders({ 'Content-Type': 'application/json' }) 11 | }; 12 | 13 | export interface ErrorAccountResponse { 14 | succeeded: boolean; 15 | errors: any[]; 16 | } 17 | 18 | @Injectable({ 19 | providedIn: 'root' 20 | }) 21 | export class AccountService { 22 | 23 | constructor(private http: HttpClient, private profileService: ProfileService) { } 24 | 25 | public register(username: string, password: string): Observable { 26 | 27 | let requestBody = { 28 | Email: username, 29 | Password: password 30 | } 31 | 32 | return this.http.post(environment.Register_Endpoint, JSON.stringify(requestBody), httpOptions) 33 | .pipe( 34 | map((res: ErrorAccountResponse) => { 35 | let obj: Object; 36 | 37 | if (res.succeeded) { 38 | obj = { 39 | code: 'Succeeded', 40 | msg: 'Register complete' 41 | } 42 | } else { 43 | if (typeof res.errors[0].code !== 'undefined') { 44 | switch (res.errors[0].code) { 45 | case 'DuplicateUserName': { 46 | obj = { 47 | code: 'DuplicateUserName', 48 | msg: 'Email already exist' 49 | } 50 | break; 51 | } 52 | } 53 | } 54 | } 55 | 56 | return obj; 57 | }), 58 | catchError(error => this.handleErrorObservable(error)) 59 | ); 60 | } 61 | 62 | public changePassword(oldPassword: string, newPassword: string): Observable { 63 | let requestBody = { 64 | Email: this.profileService.getUserEmail(), 65 | OldPassword: oldPassword, 66 | NewPassword: newPassword 67 | } 68 | 69 | return this.http.post(environment.ChangePassword_Endpoint, JSON.stringify(requestBody), httpOptions) 70 | .pipe( 71 | map((res: ErrorAccountResponse) => { 72 | let obj: Object; 73 | 74 | if (res.succeeded) { 75 | obj = { 76 | code: 'Succeeded', 77 | msg: 'Change password complete' 78 | } 79 | } else { 80 | if (typeof res.errors[0].code !== 'undefined') { 81 | switch (res.errors[0].code) { 82 | case 'PasswordMismatch': { 83 | obj = { 84 | code: 'PasswordMismatch', 85 | msg: 'Incorrect password' 86 | } 87 | break; 88 | } 89 | } 90 | } 91 | } 92 | 93 | return obj; 94 | 95 | }), 96 | catchError(error => this.handleErrorObservable(error)) 97 | ); 98 | } 99 | 100 | // 101 | // Helpers 102 | // 103 | 104 | private handleErrorObservable(error: Response | any) { 105 | return throwError(error.error); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, RouterState } from '@angular/router'; 3 | 4 | import { AuthService } from './auth.service'; 5 | 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AuthGuard implements CanActivate { 11 | constructor(private authService: AuthService, private router: Router) { } 12 | 13 | // https://angular.io/guide/router#milestone-5-route-guards 14 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 15 | let url: string = state.url; 16 | 17 | return this.checkLogin(url); 18 | } 19 | 20 | private checkLogin(url: string): boolean { 21 | if (this.authService.isAuthenticatedBehaviorSubject.getValue()) { 22 | return true; 23 | } 24 | 25 | this.router.navigate(['/login'], { queryParams: { returnUrl: url } }); 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, of, throwError, BehaviorSubject, interval } from 'rxjs'; 3 | import { catchError, map, tap } from 'rxjs/operators'; 4 | import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; 5 | 6 | import { TokenService } from './token.service'; 7 | import { ProfileService } from './profile.service'; 8 | import { JwtResponse } from './jwt-response'; 9 | 10 | import { environment } from '../../../environments/environment'; 11 | 12 | const httpOptions = { 13 | headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }) 14 | }; 15 | 16 | @Injectable({ 17 | providedIn: 'root' 18 | }) 19 | export class AuthService { 20 | public isAuthenticatedBehaviorSubject = new BehaviorSubject(this.tokenService.isTokenExpired()); 21 | 22 | private refreshSubscription: any; 23 | 24 | constructor( 25 | private http: HttpClient, 26 | private profileService: ProfileService, 27 | private tokenService: TokenService) { } 28 | 29 | public isAuthenticated(): Observable { 30 | return this.isAuthenticatedBehaviorSubject.asObservable(); 31 | } 32 | 33 | // sign in 34 | public signIn(username: string, password: string): Observable { 35 | let httpParams = new HttpParams(); 36 | httpParams = httpParams.append('client_id', environment.Client_Id); 37 | httpParams = httpParams.append('grant_type', environment.Grant_Type_Password); 38 | httpParams = httpParams.append('username', username); 39 | httpParams = httpParams.append('password', password); 40 | httpParams = httpParams.append('scope', environment.Scope); 41 | 42 | return this.http.post(environment.Token_Endpoint, httpParams, httpOptions) 43 | .pipe( 44 | map((res: JwtResponse) => { 45 | if (typeof res.access_token !== 'undefined') { 46 | this.saveToken(res); 47 | this.isAuthenticatedBehaviorSubject.next(true); 48 | 49 | this.getUserProfile(); 50 | } 51 | }), 52 | catchError(error => this.handleErrorObservable(error)) 53 | ); 54 | } 55 | 56 | // sign out 57 | public signOut(): void { 58 | this.tokenService.removeToken(); 59 | this.isAuthenticatedBehaviorSubject.next(false); 60 | this.unscheduleRefresh(); 61 | } 62 | 63 | // 64 | // token refresh 65 | // 66 | 67 | public scheduleRefresh(): void { 68 | console.log('refresh token schedule start'); 69 | let delay = this.tokenService.getExpiresIn() * 1000; // seconds to ms 70 | delay -= delay * 0.10; // decrease delay by 10% to avoid unexpected 401 71 | 72 | this.refreshSubscription = interval(delay).subscribe(() => { 73 | this.getNewJwt().subscribe(); 74 | }); 75 | } 76 | 77 | public startupTokenRefresh(): void { 78 | if (this.isAuthenticatedBehaviorSubject.getValue()) { 79 | let now: number = new Date().valueOf(); 80 | let delay = (this.tokenService.getExpiresIn() - now) * 1000; 81 | 82 | this.refreshSubscription = interval(delay).subscribe(() => { 83 | this.getNewJwt().subscribe(); 84 | }); 85 | } 86 | } 87 | 88 | public unscheduleRefresh(): void { 89 | if (this.refreshSubscription) { 90 | this.refreshSubscription.unsubscribe(); 91 | } 92 | } 93 | 94 | private getNewJwt(): Observable { 95 | // identityserver4 openid spec 96 | let httpParams = new HttpParams(); 97 | httpParams = httpParams.append('client_id', environment.Client_Id); 98 | httpParams = httpParams.append('grant_type', environment.Grant_Type_RefreshToken); 99 | httpParams = httpParams.append('refresh_token', this.tokenService.getRefreshToken()); 100 | 101 | return this.http.post(environment.Token_Endpoint, httpParams, httpOptions) 102 | .pipe( 103 | map((res: JwtResponse) => { 104 | if (typeof res.access_token !== 'undefined') { 105 | this.saveToken(res); 106 | this.isAuthenticatedBehaviorSubject.next(true); 107 | } 108 | }), 109 | catchError(error => this.handleErrorObservable(error)) 110 | ); 111 | } 112 | 113 | private getUserProfile(): void { 114 | if (this.isAuthenticatedBehaviorSubject.getValue()) { 115 | this.http.get(environment.UserInfo_Endpoint) 116 | .subscribe(res => { 117 | this.profileService.saveUserProfile(JSON.stringify(res)); 118 | }); 119 | } 120 | } 121 | 122 | // 123 | // helpers 124 | // 125 | 126 | private saveToken(res: JwtResponse): void { 127 | this.tokenService.saveAcessToken(res.access_token); 128 | this.tokenService.saveExpiresIn(res.expires_in); 129 | this.tokenService.saveRefreshToken(res.refresh_token); 130 | } 131 | 132 | private handleErrorObservable(error: Response | any) { 133 | return throwError(error.error); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/jwt-response.ts: -------------------------------------------------------------------------------- 1 | export interface JwtResponse { 2 | access_token: string; 3 | expires_in: number; 4 | refresh_token: string; 5 | token_type: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/password-match-validator.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup } from '@angular/forms'; 2 | 3 | // http://stackoverflow.com/questions/31788681/angular2-validator-which-relies-on-multiple-form-fields/34582914#34582914 4 | // FORM GROUP VALIDATORS 5 | export function PasswordMatchValidator(passwordKey: string, confirmPasswordKey: string) { 6 | return (group: FormGroup): { [key: string]: any } => { 7 | let password = group.controls[passwordKey]; 8 | let confirmPassword = group.controls[confirmPasswordKey]; 9 | 10 | if (password.value !== confirmPassword.value) { 11 | return { 12 | doesNotMatch: true 13 | }; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/profile.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ 4 | providedIn: 'root' 5 | }) 6 | export class ProfileService { 7 | 8 | public saveUserProfile(profile: string): void { 9 | localStorage.setItem('user_profile', profile); 10 | } 11 | 12 | public getUserProfile(): string { 13 | return localStorage.getItem('user_profile'); 14 | } 15 | 16 | public getUserEmail(): string { 17 | let userProfile: any = JSON.parse(this.getUserProfile()); 18 | return userProfile.name; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/account/shared/token.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { JwtHelperService } from '@auth0/angular-jwt'; 3 | 4 | @Injectable({ 5 | providedIn: 'root' 6 | }) 7 | export class TokenService { 8 | private jwtHelper = new JwtHelperService(); 9 | 10 | public removeToken(): void { 11 | localStorage.removeItem('access_token'); 12 | localStorage.removeItem('expires_in'); 13 | localStorage.removeItem('refresh_token'); 14 | } 15 | 16 | public getAccessToken(): string { 17 | return localStorage.getItem('access_token'); 18 | } 19 | 20 | public getRefreshToken(): string { 21 | return localStorage.getItem('refresh_token'); 22 | } 23 | 24 | public getExpiresIn(): number { 25 | return parseInt(localStorage.getItem('expires_in')); 26 | } 27 | 28 | public saveAcessToken(token: string): void { 29 | localStorage.setItem('access_token', token); 30 | } 31 | 32 | public saveExpiresIn(expiresIn: number): void { 33 | localStorage.setItem('expires_in', expiresIn.toString()); 34 | } 35 | 36 | public saveRefreshToken(token: string): void { 37 | localStorage.setItem('refresh_token', token); 38 | } 39 | 40 | public isTokenExpired(): boolean { 41 | let token: string = this.getAccessToken(); 42 | let tokenExpired: boolean = token != null && this.jwtHelper.isTokenExpired(token); 43 | 44 | return tokenExpired; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/api/api-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { ApiComponent } from './api.component'; 5 | import { AuthGuard } from '../account/shared/auth.guard'; 6 | 7 | const routes: Routes = [ 8 | { path: 'api', component: ApiComponent, canActivate: [AuthGuard] } 9 | ]; 10 | 11 | @NgModule({ 12 | imports: [RouterModule.forRoot(routes)], 13 | exports: [RouterModule] 14 | }) 15 | export class ApiRoutingModule { } 16 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/api/api.component.css: -------------------------------------------------------------------------------- 1 | .values-card { 2 | width: 300px; 3 | } 4 | 5 | .value-card-content { 6 | display: flex; 7 | align-items: center; 8 | } 9 | 10 | .space-filler { 11 | flex: 1 1 auto; 12 | } 13 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/api/api.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |

Api Component

7 | 8 |

9 | 10 |

11 | 12 |
13 | 14 | 15 |
16 |

Id: {{number.id}} | Value: {{ number.value }}

17 | 18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/api/api.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable, of, throwError, BehaviorSubject } from 'rxjs'; 4 | import { catchError, map, tap } from 'rxjs/operators'; 5 | 6 | import { environment } from '../../environments/environment'; 7 | 8 | @Component({ 9 | selector: 'app-api', 10 | templateUrl: './api.component.html', 11 | styleUrls: ['./api.component.css'] 12 | }) 13 | export class ApiComponent implements OnInit { 14 | private numbers: any; 15 | private loading: boolean; 16 | 17 | constructor(private http: HttpClient) { } 18 | 19 | ngOnInit() { 20 | this.getValues(); 21 | } 22 | 23 | public getValues() { 24 | this.loading = true; 25 | this.http.get(environment.NumbersApi_Endpoint) 26 | .subscribe( 27 | data => this.numbers = data, 28 | err => { this.loading = false; }, 29 | () => { this.loading = false; } 30 | ); 31 | } 32 | 33 | public add() { 34 | this.loading = true; 35 | this.http.post(environment.NumbersApi_Endpoint, Math.floor(Math.random() * 999) + 1) 36 | .subscribe( 37 | () => { this.getValues(); }, 38 | err => { this.loading = false; }, 39 | () => { this.loading = false; } 40 | ); 41 | } 42 | 43 | public delete(id: number) { 44 | this.loading = true; 45 | this.http.post(environment.NumbersApi_Endpoint + '/delete', id) 46 | .subscribe( 47 | () => { this.getValues(); }, 48 | err => { this.loading = false; }, 49 | () => { this.loading = false; } 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/api/api.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { ApiRoutingModule } from './api-routing.module'; 4 | import { MaterialModule } from '../material.module'; 5 | 6 | import { ApiComponent } from './api.component'; 7 | 8 | @NgModule({ 9 | declarations: [ApiComponent], 10 | imports: [ 11 | ApiRoutingModule, 12 | MaterialModule 13 | ] 14 | }) 15 | export class ApiModule { } 16 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | 5 | const routes: Routes = [ 6 | { path: '', redirectTo: '/home', pathMatch: 'full' } 7 | ]; 8 | 9 | @NgModule({ 10 | imports: [RouterModule.forRoot(routes)], 11 | exports: [RouterModule] 12 | }) 13 | export class AppRoutingModule { } 14 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .app-nav { 2 | box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0 rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12); 3 | position: relative; 4 | } 5 | 6 | .angular-logo { 7 | margin: 0 4px 3px 0; 8 | height: 26px; 9 | vertical-align: middle; 10 | } 11 | 12 | .toolbar-spacer { 13 | flex: 1 1 auto; 14 | } 15 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | {{ title }} 6 | 7 | Home 8 | Web API 9 | 10 | 11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 30 | 34 | 35 | 36 |
37 | 38 |
39 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Observable } from 'rxjs'; 4 | 5 | import { AuthService } from './account/shared/auth.service'; 6 | 7 | @Component({ 8 | selector: 'app-root', 9 | templateUrl: './app.component.html', 10 | styleUrls: ['./app.component.css'] 11 | }) 12 | export class AppComponent { 13 | private title: string = 'Angular'; 14 | private isAuthenticated: Observable; 15 | 16 | constructor(private authService: AuthService, private router: Router) { 17 | // schedule startup token refresh 18 | this.authService.startupTokenRefresh(); 19 | 20 | this.isAuthenticated = this.authService.isAuthenticated(); 21 | } 22 | 23 | private signOut(): void { 24 | this.authService.signOut(); 25 | this.router.navigate(['/home']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { JwtModule } from "@auth0/angular-jwt"; 4 | import { HttpClientModule } from "@angular/common/http"; 5 | 6 | import { MaterialModule } from './material.module'; 7 | import { AppRoutingModule } from './app-routing.module'; 8 | import { HomeModule } from './home/home.module'; 9 | import { AccountModule } from './account/account.module'; 10 | import { ApiModule } from './api/api.module'; 11 | 12 | import { AppComponent } from './app.component'; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | AppComponent 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | HttpClientModule, 21 | AppRoutingModule, 22 | MaterialModule, 23 | HomeModule, 24 | AccountModule, 25 | ApiModule, 26 | JwtModule.forRoot({ 27 | config: { 28 | tokenGetter: () => { 29 | return localStorage.getItem("access_token"); 30 | }, 31 | whitelistedDomains: ["localhost:5000", "localhost:5001"], 32 | blacklistedRoutes: ["localhost:5000/connect/token"] 33 | } 34 | }) 35 | ], 36 | providers: [], 37 | bootstrap: [AppComponent] 38 | }) 39 | export class AppModule { } 40 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { HomeComponent } from './home.component'; 5 | 6 | const routes: Routes = [ 7 | { path: 'home', component: HomeComponent } 8 | ]; 9 | 10 | @NgModule({ 11 | imports: [RouterModule.forRoot(routes)], 12 | exports: [RouterModule] 13 | }) 14 | export class HomeRoutingModule { } 15 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | mat-card-content div { 2 | margin-bottom: 10px; 3 | } 4 | 5 | mat-card-content div:last-child { 6 | margin-bottom: 0px; 7 | } 8 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Home Component

3 |
4 | 5 | 6 |

Project repository

7 |
8 | 9 | 10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | tiles = [ 16 | { text: 'One', cols: 3, rows: 1, color: 'lightblue' }, 17 | { text: 'Two', cols: 1, rows: 2, color: 'lightgreen' }, 18 | { text: 'Three', cols: 1, rows: 1, color: 'lightpink' }, 19 | { text: 'Four', cols: 2, rows: 1, color: '#DDBDF1' }, 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | 3 | import { HomeRoutingModule } from './home-routing.module'; 4 | import { MaterialModule } from '../material.module'; 5 | 6 | import { HomeComponent } from './home.component'; 7 | 8 | @NgModule({ 9 | declarations: [HomeComponent], 10 | imports: [ 11 | HomeRoutingModule, 12 | MaterialModule 13 | ] 14 | }) 15 | export class HomeModule { } 16 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/app/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { MatButtonModule, MatCardModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule, MatProgressBarModule, MatToolbarModule } from '@angular/material'; 4 | 5 | 6 | @NgModule({ 7 | declarations: [], 8 | imports: [ 9 | BrowserAnimationsModule, 10 | MatButtonModule, 11 | MatCardModule, 12 | MatIconModule, 13 | MatInputModule, 14 | MatListModule, 15 | MatMenuModule, 16 | MatProgressBarModule, 17 | MatToolbarModule 18 | ], 19 | exports: [ 20 | BrowserAnimationsModule, 21 | MatButtonModule, 22 | MatCardModule, 23 | MatIconModule, 24 | MatInputModule, 25 | MatListModule, 26 | MatMenuModule, 27 | MatProgressBarModule, 28 | MatToolbarModule 29 | ] 30 | }) 31 | export class MaterialModule { } 32 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marklaygo/Angular-IdentityServer-WebAPI/f7440e6b2e9fe9e0e32e1367579bc7f8cee8941a/src/Angular/ClientApp/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/assets/images/angular-white-transparent.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marklaygo/Angular-IdentityServer-WebAPI/f7440e6b2e9fe9e0e32e1367579bc7f8cee8941a/src/Angular/ClientApp/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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 globalEndPoint = { 6 | AuthEndPoint: 'http://localhost:5000', 7 | ApiEndPoint: 'http://localhost:5001', 8 | } 9 | 10 | export const environment = { 11 | production: false, 12 | 13 | // token 14 | TokenName: 'access_token', 15 | Token_Endpoint: globalEndPoint.AuthEndPoint + '/connect/token', 16 | UserInfo_Endpoint: globalEndPoint.AuthEndPoint + '/connect/userinfo', 17 | Client_Id: 'Angular', 18 | Grant_Type_Password: 'password', 19 | Grant_Type_RefreshToken: 'refresh_token', 20 | Scope: 'api1 offline_access openid profile accountApi', 21 | 22 | // account 23 | Register_Endpoint: globalEndPoint.AuthEndPoint + '/api/account/register', 24 | ChangePassword_Endpoint: globalEndPoint.AuthEndPoint + '/api/account/changepassword', 25 | 26 | // api 27 | NumbersApi_Endpoint: globalEndPoint.ApiEndPoint + '/api/numbers', 28 | }; 29 | 30 | /* 31 | * For easier debugging in development mode, you can import the following file 32 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 33 | * 34 | * This import should be commented out in production mode because it will have a negative impact 35 | * on performance if an error is thrown. 36 | */ 37 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 38 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marklaygo/Angular-IdentityServer-WebAPI/f7440e6b2e9fe9e0e32e1367579bc7f8cee8941a/src/Angular/ClientApp/src/favicon.ico -------------------------------------------------------------------------------- /src/Angular/ClientApp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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/Angular/ClientApp/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__UNPATCHED_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/Angular/ClientApp/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import '@angular/material/prebuilt-themes/deeppurple-amber.css'; 3 | 4 | * { 5 | margin: 0px; 6 | } 7 | 8 | body { 9 | background-color: #fafafa; 10 | color: rgba(0,0,0,.87); 11 | } 12 | 13 | .max-width { 14 | width: 100%; 15 | } 16 | 17 | .container { 18 | margin: 3%; 19 | } 20 | 21 | .form-val-hint { 22 | font-size: 75%; 23 | } 24 | 25 | .h-space { 26 | margin-bottom: 10px; 27 | } 28 | 29 | .failed-color { 30 | color: red; 31 | } 32 | 33 | .success-color { 34 | color: green; 35 | } 36 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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/Angular/ClientApp/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | }, 22 | "angularCompilerOptions": { 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/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 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/Angular/ClientApp/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } -------------------------------------------------------------------------------- /src/Angular/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /src/Angular/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | 9 | namespace Angular.Pages 10 | { 11 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 12 | public class ErrorModel : PageModel 13 | { 14 | public string RequestId { get; set; } 15 | 16 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 17 | 18 | public void OnGet() 19 | { 20 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Angular/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Angular 2 | @namespace Angular.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /src/Angular/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Angular 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Angular/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5002", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "Angular": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5002", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Angular/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.SpaServices.AngularCli; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Angular 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | public IConfiguration Configuration { get; } 18 | 19 | // This method gets called by the runtime. Use this method to add services to the container. 20 | public void ConfigureServices(IServiceCollection services) 21 | { 22 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 23 | 24 | // In production, the Angular files will be served from this directory 25 | services.AddSpaStaticFiles(configuration => 26 | { 27 | configuration.RootPath = "ClientApp/dist"; 28 | }); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | else 39 | { 40 | app.UseExceptionHandler("/Error"); 41 | } 42 | 43 | app.UseStaticFiles(); 44 | app.UseSpaStaticFiles(); 45 | 46 | app.UseMvc(routes => 47 | { 48 | routes.MapRoute( 49 | name: "default", 50 | template: "{controller}/{action=Index}/{id?}"); 51 | }); 52 | 53 | app.UseSpa(spa => 54 | { 55 | // To learn more about options for serving an Angular SPA from ASP.NET Core, 56 | // see https://go.microsoft.com/fwlink/?linkid=864501 57 | 58 | spa.Options.SourcePath = "ClientApp"; 59 | 60 | if (env.IsDevelopment()) 61 | { 62 | spa.UseAngularCliServer(npmScript: "start"); 63 | } 64 | }); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Angular/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Angular/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /src/IdentityServer/Config.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer4; 2 | using IdentityServer4.Models; 3 | using IdentityServer4.Test; 4 | using System.Collections.Generic; 5 | 6 | namespace IdentityServer 7 | { 8 | public static class Config 9 | { 10 | public static List GetUsers() 11 | { 12 | return new List 13 | { 14 | new TestUser 15 | { 16 | SubjectId = "1", 17 | Username = "alice@alice.com", 18 | Password = "password" 19 | } 20 | }; 21 | } 22 | 23 | public static IEnumerable GetIdentityResources() 24 | { 25 | return new IdentityResource[] 26 | { 27 | new IdentityResources.OpenId(), 28 | new IdentityResources.Profile() 29 | }; 30 | } 31 | 32 | public static IEnumerable GetApis() 33 | { 34 | return new ApiResource[] 35 | { 36 | new ApiResource("api1", "webApi"), 37 | new ApiResource("accountApi", "AccountApi") 38 | }; 39 | } 40 | 41 | public static IEnumerable GetClients() 42 | { 43 | return new Client[] 44 | { 45 | new Client 46 | { 47 | ClientId = "Angular", 48 | AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, 49 | RequireClientSecret = false, 50 | AllowAccessTokensViaBrowser = true, 51 | AccessTokenLifetime = 300, 52 | AllowOfflineAccess = true, 53 | AllowedScopes = 54 | { 55 | IdentityServerConstants.StandardScopes.OpenId, 56 | IdentityServerConstants.StandardScopes.Profile, 57 | "api1", 58 | "accountApi" 59 | } 60 | } 61 | }; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/IdentityServer/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer.Models; 2 | using IdentityServer.Models.Account; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | 11 | namespace IdentityServer.Controllers 12 | { 13 | [Route("api/[controller]")] 14 | public class AccountController : ControllerBase 15 | { 16 | #region Fields 17 | 18 | private readonly UserManager _userManager; 19 | private readonly IUserClaimsPrincipalFactory _claimsFactory; 20 | 21 | #endregion 22 | 23 | #region Constructor 24 | 25 | public AccountController( 26 | UserManager userManager, 27 | IUserClaimsPrincipalFactory claimsFactory) 28 | { 29 | _userManager = userManager; 30 | _claimsFactory = claimsFactory; 31 | } 32 | 33 | #endregion 34 | 35 | #region Methods 36 | 37 | [HttpPost("Register")] 38 | public async Task Register([FromBody]RegisterViewModel model) 39 | { 40 | if (ModelState.IsValid) 41 | { 42 | var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; 43 | var result = await _userManager.CreateAsync(user, model.Password); 44 | 45 | return new JsonResult(result); 46 | } 47 | 48 | return BadRequest(); 49 | } 50 | 51 | [HttpPost("ChangePassword")] 52 | [Authorize(AuthenticationSchemes = "Bearer")] 53 | public async Task ChangePassword([FromBody]ChangePasswordViewModel model) 54 | { 55 | if (ModelState.IsValid) 56 | { 57 | var user = await _userManager.FindByEmailAsync(model.Email); 58 | if (user != null) 59 | { 60 | var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); 61 | return new JsonResult(result); 62 | } 63 | } 64 | 65 | return BadRequest(); 66 | } 67 | 68 | #endregion 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/IdentityServer/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using IdentityServer.Models; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace IdentityServer.Data 10 | { 11 | public class ApplicationDbContext : IdentityDbContext 12 | { 13 | public ApplicationDbContext(DbContextOptions options) 14 | : base(options) 15 | { 16 | } 17 | 18 | protected override void OnModelCreating(ModelBuilder builder) 19 | { 20 | base.OnModelCreating(builder); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/IdentityServer/IdentityProfileService.cs: -------------------------------------------------------------------------------- 1 | using IdentityModel; 2 | using IdentityServer.Models; 3 | using IdentityServer4.Extensions; 4 | using IdentityServer4.Models; 5 | using IdentityServer4.Services; 6 | using Microsoft.AspNetCore.Identity; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using System.Security.Claims; 11 | using System.Threading.Tasks; 12 | 13 | namespace IdentityServer 14 | { 15 | public class IdentityProfileService : IProfileService 16 | { 17 | #region Fields 18 | 19 | private readonly IUserClaimsPrincipalFactory _claimsFactory; 20 | private readonly UserManager _userManager; 21 | 22 | #endregion 23 | 24 | #region Constructor 25 | 26 | public IdentityProfileService( 27 | IUserClaimsPrincipalFactory claimsFactory, 28 | UserManager userManager) 29 | { 30 | _claimsFactory = claimsFactory; 31 | _userManager = userManager; 32 | } 33 | 34 | #endregion 35 | 36 | #region Method 37 | 38 | public async Task GetProfileDataAsync(ProfileDataRequestContext context) 39 | { 40 | var subjectId = context.Subject.GetSubjectId(); 41 | var user = await _userManager.FindByIdAsync(subjectId); 42 | var principal = await _claimsFactory.CreateAsync(user); 43 | var claims = principal.Claims.ToList(); 44 | 45 | // test role 46 | claims.Add(new Claim(JwtClaimTypes.Role, "test_role")); 47 | 48 | context.IssuedClaims = claims; 49 | } 50 | 51 | public async Task IsActiveAsync(IsActiveContext context) 52 | { 53 | var subjectId = context.Subject.GetSubjectId(); 54 | var user = await _userManager.FindByIdAsync(subjectId); 55 | context.IsActive = user != null; 56 | } 57 | 58 | #endregion 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/IdentityServer/IdentityServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/IdentityServer/Migrations/20190713034352_initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using IdentityServer.Data; 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 IdentityServer.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | [Migration("20190713034352_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.1.11-servicing-32099") 21 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("IdentityServer.Models.ApplicationUser", 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("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 | 99 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 100 | { 101 | b.Property("Id") 102 | .ValueGeneratedOnAdd() 103 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 104 | 105 | b.Property("ClaimType"); 106 | 107 | b.Property("ClaimValue"); 108 | 109 | b.Property("RoleId") 110 | .IsRequired(); 111 | 112 | b.HasKey("Id"); 113 | 114 | b.HasIndex("RoleId"); 115 | 116 | b.ToTable("AspNetRoleClaims"); 117 | }); 118 | 119 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 120 | { 121 | b.Property("Id") 122 | .ValueGeneratedOnAdd() 123 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 124 | 125 | b.Property("ClaimType"); 126 | 127 | b.Property("ClaimValue"); 128 | 129 | b.Property("UserId") 130 | .IsRequired(); 131 | 132 | b.HasKey("Id"); 133 | 134 | b.HasIndex("UserId"); 135 | 136 | b.ToTable("AspNetUserClaims"); 137 | }); 138 | 139 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 140 | { 141 | b.Property("LoginProvider"); 142 | 143 | b.Property("ProviderKey"); 144 | 145 | b.Property("ProviderDisplayName"); 146 | 147 | b.Property("UserId") 148 | .IsRequired(); 149 | 150 | b.HasKey("LoginProvider", "ProviderKey"); 151 | 152 | b.HasIndex("UserId"); 153 | 154 | b.ToTable("AspNetUserLogins"); 155 | }); 156 | 157 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 158 | { 159 | b.Property("UserId"); 160 | 161 | b.Property("RoleId"); 162 | 163 | b.HasKey("UserId", "RoleId"); 164 | 165 | b.HasIndex("RoleId"); 166 | 167 | b.ToTable("AspNetUserRoles"); 168 | }); 169 | 170 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 171 | { 172 | b.Property("UserId"); 173 | 174 | b.Property("LoginProvider"); 175 | 176 | b.Property("Name"); 177 | 178 | b.Property("Value"); 179 | 180 | b.HasKey("UserId", "LoginProvider", "Name"); 181 | 182 | b.ToTable("AspNetUserTokens"); 183 | }); 184 | 185 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 186 | { 187 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 188 | .WithMany() 189 | .HasForeignKey("RoleId") 190 | .OnDelete(DeleteBehavior.Cascade); 191 | }); 192 | 193 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 194 | { 195 | b.HasOne("IdentityServer.Models.ApplicationUser") 196 | .WithMany() 197 | .HasForeignKey("UserId") 198 | .OnDelete(DeleteBehavior.Cascade); 199 | }); 200 | 201 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 202 | { 203 | b.HasOne("IdentityServer.Models.ApplicationUser") 204 | .WithMany() 205 | .HasForeignKey("UserId") 206 | .OnDelete(DeleteBehavior.Cascade); 207 | }); 208 | 209 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 210 | { 211 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 212 | .WithMany() 213 | .HasForeignKey("RoleId") 214 | .OnDelete(DeleteBehavior.Cascade); 215 | 216 | b.HasOne("IdentityServer.Models.ApplicationUser") 217 | .WithMany() 218 | .HasForeignKey("UserId") 219 | .OnDelete(DeleteBehavior.Cascade); 220 | }); 221 | 222 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 223 | { 224 | b.HasOne("IdentityServer.Models.ApplicationUser") 225 | .WithMany() 226 | .HasForeignKey("UserId") 227 | .OnDelete(DeleteBehavior.Cascade); 228 | }); 229 | #pragma warning restore 612, 618 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/IdentityServer/Migrations/20190713034352_initial.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Metadata; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace IdentityServer.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 | }, 45 | constraints: table => 46 | { 47 | table.PrimaryKey("PK_AspNetUsers", x => x.Id); 48 | }); 49 | 50 | migrationBuilder.CreateTable( 51 | name: "AspNetRoleClaims", 52 | columns: table => new 53 | { 54 | Id = table.Column(nullable: false) 55 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 56 | RoleId = table.Column(nullable: false), 57 | ClaimType = table.Column(nullable: true), 58 | ClaimValue = table.Column(nullable: true) 59 | }, 60 | constraints: table => 61 | { 62 | table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); 63 | table.ForeignKey( 64 | name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", 65 | column: x => x.RoleId, 66 | principalTable: "AspNetRoles", 67 | principalColumn: "Id", 68 | onDelete: ReferentialAction.Cascade); 69 | }); 70 | 71 | migrationBuilder.CreateTable( 72 | name: "AspNetUserClaims", 73 | columns: table => new 74 | { 75 | Id = table.Column(nullable: false) 76 | .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), 77 | UserId = table.Column(nullable: false), 78 | ClaimType = table.Column(nullable: true), 79 | ClaimValue = table.Column(nullable: true) 80 | }, 81 | constraints: table => 82 | { 83 | table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); 84 | table.ForeignKey( 85 | name: "FK_AspNetUserClaims_AspNetUsers_UserId", 86 | column: x => x.UserId, 87 | principalTable: "AspNetUsers", 88 | principalColumn: "Id", 89 | onDelete: ReferentialAction.Cascade); 90 | }); 91 | 92 | migrationBuilder.CreateTable( 93 | name: "AspNetUserLogins", 94 | columns: table => new 95 | { 96 | LoginProvider = table.Column(nullable: false), 97 | ProviderKey = table.Column(nullable: false), 98 | ProviderDisplayName = table.Column(nullable: true), 99 | UserId = table.Column(nullable: false) 100 | }, 101 | constraints: table => 102 | { 103 | table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); 104 | table.ForeignKey( 105 | name: "FK_AspNetUserLogins_AspNetUsers_UserId", 106 | column: x => x.UserId, 107 | principalTable: "AspNetUsers", 108 | principalColumn: "Id", 109 | onDelete: ReferentialAction.Cascade); 110 | }); 111 | 112 | migrationBuilder.CreateTable( 113 | name: "AspNetUserRoles", 114 | columns: table => new 115 | { 116 | UserId = table.Column(nullable: false), 117 | RoleId = table.Column(nullable: false) 118 | }, 119 | constraints: table => 120 | { 121 | table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); 122 | table.ForeignKey( 123 | name: "FK_AspNetUserRoles_AspNetRoles_RoleId", 124 | column: x => x.RoleId, 125 | principalTable: "AspNetRoles", 126 | principalColumn: "Id", 127 | onDelete: ReferentialAction.Cascade); 128 | table.ForeignKey( 129 | name: "FK_AspNetUserRoles_AspNetUsers_UserId", 130 | column: x => x.UserId, 131 | principalTable: "AspNetUsers", 132 | principalColumn: "Id", 133 | onDelete: ReferentialAction.Cascade); 134 | }); 135 | 136 | migrationBuilder.CreateTable( 137 | name: "AspNetUserTokens", 138 | columns: table => new 139 | { 140 | UserId = table.Column(nullable: false), 141 | LoginProvider = table.Column(nullable: false), 142 | Name = table.Column(nullable: false), 143 | Value = table.Column(nullable: true) 144 | }, 145 | constraints: table => 146 | { 147 | table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); 148 | table.ForeignKey( 149 | name: "FK_AspNetUserTokens_AspNetUsers_UserId", 150 | column: x => x.UserId, 151 | principalTable: "AspNetUsers", 152 | principalColumn: "Id", 153 | onDelete: ReferentialAction.Cascade); 154 | }); 155 | 156 | migrationBuilder.CreateIndex( 157 | name: "IX_AspNetRoleClaims_RoleId", 158 | table: "AspNetRoleClaims", 159 | column: "RoleId"); 160 | 161 | migrationBuilder.CreateIndex( 162 | name: "RoleNameIndex", 163 | table: "AspNetRoles", 164 | column: "NormalizedName", 165 | unique: true, 166 | filter: "[NormalizedName] IS NOT NULL"); 167 | 168 | migrationBuilder.CreateIndex( 169 | name: "IX_AspNetUserClaims_UserId", 170 | table: "AspNetUserClaims", 171 | column: "UserId"); 172 | 173 | migrationBuilder.CreateIndex( 174 | name: "IX_AspNetUserLogins_UserId", 175 | table: "AspNetUserLogins", 176 | column: "UserId"); 177 | 178 | migrationBuilder.CreateIndex( 179 | name: "IX_AspNetUserRoles_RoleId", 180 | table: "AspNetUserRoles", 181 | column: "RoleId"); 182 | 183 | migrationBuilder.CreateIndex( 184 | name: "EmailIndex", 185 | table: "AspNetUsers", 186 | column: "NormalizedEmail"); 187 | 188 | migrationBuilder.CreateIndex( 189 | name: "UserNameIndex", 190 | table: "AspNetUsers", 191 | column: "NormalizedUserName", 192 | unique: true, 193 | filter: "[NormalizedUserName] IS NOT NULL"); 194 | } 195 | 196 | protected override void Down(MigrationBuilder migrationBuilder) 197 | { 198 | migrationBuilder.DropTable( 199 | name: "AspNetRoleClaims"); 200 | 201 | migrationBuilder.DropTable( 202 | name: "AspNetUserClaims"); 203 | 204 | migrationBuilder.DropTable( 205 | name: "AspNetUserLogins"); 206 | 207 | migrationBuilder.DropTable( 208 | name: "AspNetUserRoles"); 209 | 210 | migrationBuilder.DropTable( 211 | name: "AspNetUserTokens"); 212 | 213 | migrationBuilder.DropTable( 214 | name: "AspNetRoles"); 215 | 216 | migrationBuilder.DropTable( 217 | name: "AspNetUsers"); 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/IdentityServer/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using System; 3 | using IdentityServer.Data; 4 | using Microsoft.EntityFrameworkCore; 5 | using Microsoft.EntityFrameworkCore.Infrastructure; 6 | using Microsoft.EntityFrameworkCore.Metadata; 7 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 | 9 | namespace IdentityServer.Migrations 10 | { 11 | [DbContext(typeof(ApplicationDbContext))] 12 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "2.1.11-servicing-32099") 19 | .HasAnnotation("Relational:MaxIdentifierLength", 128) 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("IdentityServer.Models.ApplicationUser", 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("NormalizedEmail") 42 | .HasMaxLength(256); 43 | 44 | b.Property("NormalizedUserName") 45 | .HasMaxLength(256); 46 | 47 | b.Property("PasswordHash"); 48 | 49 | b.Property("PhoneNumber"); 50 | 51 | b.Property("PhoneNumberConfirmed"); 52 | 53 | b.Property("SecurityStamp"); 54 | 55 | b.Property("TwoFactorEnabled"); 56 | 57 | b.Property("UserName") 58 | .HasMaxLength(256); 59 | 60 | b.HasKey("Id"); 61 | 62 | b.HasIndex("NormalizedEmail") 63 | .HasName("EmailIndex"); 64 | 65 | b.HasIndex("NormalizedUserName") 66 | .IsUnique() 67 | .HasName("UserNameIndex") 68 | .HasFilter("[NormalizedUserName] IS NOT NULL"); 69 | 70 | b.ToTable("AspNetUsers"); 71 | }); 72 | 73 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 74 | { 75 | b.Property("Id") 76 | .ValueGeneratedOnAdd(); 77 | 78 | b.Property("ConcurrencyStamp") 79 | .IsConcurrencyToken(); 80 | 81 | b.Property("Name") 82 | .HasMaxLength(256); 83 | 84 | b.Property("NormalizedName") 85 | .HasMaxLength(256); 86 | 87 | b.HasKey("Id"); 88 | 89 | b.HasIndex("NormalizedName") 90 | .IsUnique() 91 | .HasName("RoleNameIndex") 92 | .HasFilter("[NormalizedName] IS NOT NULL"); 93 | 94 | b.ToTable("AspNetRoles"); 95 | }); 96 | 97 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 98 | { 99 | b.Property("Id") 100 | .ValueGeneratedOnAdd() 101 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 102 | 103 | b.Property("ClaimType"); 104 | 105 | b.Property("ClaimValue"); 106 | 107 | b.Property("RoleId") 108 | .IsRequired(); 109 | 110 | b.HasKey("Id"); 111 | 112 | b.HasIndex("RoleId"); 113 | 114 | b.ToTable("AspNetRoleClaims"); 115 | }); 116 | 117 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 118 | { 119 | b.Property("Id") 120 | .ValueGeneratedOnAdd() 121 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 122 | 123 | b.Property("ClaimType"); 124 | 125 | b.Property("ClaimValue"); 126 | 127 | b.Property("UserId") 128 | .IsRequired(); 129 | 130 | b.HasKey("Id"); 131 | 132 | b.HasIndex("UserId"); 133 | 134 | b.ToTable("AspNetUserClaims"); 135 | }); 136 | 137 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 138 | { 139 | b.Property("LoginProvider"); 140 | 141 | b.Property("ProviderKey"); 142 | 143 | b.Property("ProviderDisplayName"); 144 | 145 | b.Property("UserId") 146 | .IsRequired(); 147 | 148 | b.HasKey("LoginProvider", "ProviderKey"); 149 | 150 | b.HasIndex("UserId"); 151 | 152 | b.ToTable("AspNetUserLogins"); 153 | }); 154 | 155 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 156 | { 157 | b.Property("UserId"); 158 | 159 | b.Property("RoleId"); 160 | 161 | b.HasKey("UserId", "RoleId"); 162 | 163 | b.HasIndex("RoleId"); 164 | 165 | b.ToTable("AspNetUserRoles"); 166 | }); 167 | 168 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 169 | { 170 | b.Property("UserId"); 171 | 172 | b.Property("LoginProvider"); 173 | 174 | b.Property("Name"); 175 | 176 | b.Property("Value"); 177 | 178 | b.HasKey("UserId", "LoginProvider", "Name"); 179 | 180 | b.ToTable("AspNetUserTokens"); 181 | }); 182 | 183 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 184 | { 185 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 186 | .WithMany() 187 | .HasForeignKey("RoleId") 188 | .OnDelete(DeleteBehavior.Cascade); 189 | }); 190 | 191 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 192 | { 193 | b.HasOne("IdentityServer.Models.ApplicationUser") 194 | .WithMany() 195 | .HasForeignKey("UserId") 196 | .OnDelete(DeleteBehavior.Cascade); 197 | }); 198 | 199 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 200 | { 201 | b.HasOne("IdentityServer.Models.ApplicationUser") 202 | .WithMany() 203 | .HasForeignKey("UserId") 204 | .OnDelete(DeleteBehavior.Cascade); 205 | }); 206 | 207 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 208 | { 209 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 210 | .WithMany() 211 | .HasForeignKey("RoleId") 212 | .OnDelete(DeleteBehavior.Cascade); 213 | 214 | b.HasOne("IdentityServer.Models.ApplicationUser") 215 | .WithMany() 216 | .HasForeignKey("UserId") 217 | .OnDelete(DeleteBehavior.Cascade); 218 | }); 219 | 220 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 221 | { 222 | b.HasOne("IdentityServer.Models.ApplicationUser") 223 | .WithMany() 224 | .HasForeignKey("UserId") 225 | .OnDelete(DeleteBehavior.Cascade); 226 | }); 227 | #pragma warning restore 612, 618 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/IdentityServer/Models/Account/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace IdentityServer.Models.Account 8 | { 9 | public class ChangePasswordViewModel 10 | { 11 | [Required] 12 | [DataType(DataType.EmailAddress)] 13 | public string Email { get; set; } 14 | 15 | [Required] 16 | [DataType(DataType.Password)] 17 | public string OldPassword { get; set; } 18 | 19 | [Required] 20 | [DataType(DataType.Password)] 21 | public string NewPassword { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/IdentityServer/Models/Account/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace IdentityServer.Models.Account 8 | { 9 | public class RegisterViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required] 16 | [DataType(DataType.Password)] 17 | public string Password { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/IdentityServer/Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace IdentityServer.Models 8 | { 9 | public class ApplicationUser : IdentityUser 10 | { 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/IdentityServer/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Serilog; 4 | using Serilog.Events; 5 | using Serilog.Sinks.SystemConsole.Themes; 6 | using System; 7 | 8 | namespace IdentityServer 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | Console.Title = "IdentityServer4"; 15 | 16 | CreateWebHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) 20 | { 21 | return WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .UseSerilog((context, configuration) => 24 | { 25 | configuration 26 | .MinimumLevel.Debug() 27 | .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) 28 | .MinimumLevel.Override("System", LogEventLevel.Warning) 29 | .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) 30 | .Enrich.FromLogContext() 31 | .WriteTo.File(@"identityserver4_log.txt") 32 | .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate); 33 | }); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/IdentityServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": false, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "SelfHost": { 19 | "commandName": "Project", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | }, 23 | "applicationUrl": "http://localhost:5000" 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/IdentityServer/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using IdentityServer.Data; 4 | using IdentityServer.Models; 5 | using IdentityServer4.Services; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Identity; 9 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 10 | using Microsoft.EntityFrameworkCore; 11 | using Microsoft.Extensions.Configuration; 12 | using Microsoft.Extensions.DependencyInjection; 13 | using Microsoft.IdentityModel.Tokens; 14 | 15 | namespace IdentityServer 16 | { 17 | public class Startup 18 | { 19 | public IConfiguration Configuration { get; } 20 | public IHostingEnvironment Environment { get; } 21 | 22 | public Startup(IConfiguration configuration, IHostingEnvironment environment) 23 | { 24 | Configuration = configuration; 25 | Environment = environment; 26 | } 27 | 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | // add db context 31 | services.AddDbContext(options => 32 | //options.UseInMemoryDatabase(databaseName: "IdentityServer4")); 33 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 34 | 35 | // add identity 36 | services.AddIdentity() 37 | .AddEntityFrameworkStores() 38 | .AddDefaultTokenProviders(); 39 | 40 | // configure password requirements 41 | services.Configure(options => 42 | { 43 | options.Password.RequireDigit = false; 44 | options.Password.RequiredLength = 4; 45 | options.Password.RequireNonAlphanumeric = false; 46 | options.Password.RequireUppercase = false; 47 | options.Password.RequireLowercase = false; 48 | }); 49 | 50 | // add mvc 51 | services.AddMvcCore() 52 | .AddAuthorization() 53 | .AddJsonFormatters(); 54 | 55 | // dependency injection 56 | services.AddTransient(); 57 | 58 | // add identityserver4 59 | var builder = services.AddIdentityServer() 60 | .AddInMemoryIdentityResources(Config.GetIdentityResources()) 61 | .AddInMemoryApiResources(Config.GetApis()) 62 | .AddInMemoryClients(Config.GetClients()) 63 | //.AddTestUsers(Config.GetUsers()) 64 | .AddAspNetIdentity() 65 | .AddProfileService(); 66 | 67 | 68 | services.AddAuthentication("Bearer") 69 | .AddJwtBearer("Bearer", options => 70 | { 71 | options.Authority = "http://localhost:5000"; 72 | options.RequireHttpsMetadata = false; 73 | options.Audience = "accountApi"; 74 | options.TokenValidationParameters = new TokenValidationParameters() 75 | { 76 | ClockSkew = TimeSpan.FromMinutes(0) 77 | }; 78 | }); 79 | 80 | // add cors 81 | services.AddCors(options => 82 | { 83 | // this defines a CORS policy called "default" 84 | options.AddPolicy("default", policy => 85 | { 86 | policy.WithOrigins("http://localhost:4200") 87 | .AllowAnyHeader() 88 | .AllowAnyMethod(); 89 | }); 90 | }); 91 | 92 | if (Environment.IsDevelopment()) 93 | { 94 | builder.AddDeveloperSigningCredential(); 95 | } 96 | else 97 | { 98 | throw new Exception("need to configure key material"); 99 | } 100 | } 101 | 102 | public void Configure(IApplicationBuilder app, ApplicationDbContext context) 103 | { 104 | if (Environment.IsDevelopment()) 105 | { 106 | app.UseDeveloperExceptionPage(); 107 | } 108 | 109 | app.UseCors("default"); 110 | app.UseIdentityServer(); 111 | app.UseAuthentication(); 112 | app.UseMvc(); 113 | 114 | // quick way to apply migration and test user 115 | try 116 | { 117 | //var context = app.ApplicationServices.GetService(); 118 | 119 | // apply migration 120 | context.Database.Migrate(); 121 | 122 | // delete all users 123 | context.Users.RemoveRange(context.Users.ToList()); 124 | context.SaveChanges(); 125 | 126 | // add test user 127 | var user = new ApplicationUser() 128 | { 129 | UserName = "test@test.com", 130 | NormalizedUserName = "TEST@TEST.COM", 131 | Email = "test@test.com", 132 | NormalizedEmail = "TEST@TEST.COM", 133 | EmailConfirmed = true, 134 | LockoutEnabled = false, 135 | SecurityStamp = Guid.NewGuid().ToString() 136 | }; 137 | 138 | if (!context.Users.Any(u => u.UserName == user.UserName)) 139 | { 140 | var passwordHasher = new PasswordHasher(); 141 | var hashed = passwordHasher.HashPassword(user, "11234"); 142 | user.PasswordHash = hashed; 143 | var userStore = new UserStore(context); 144 | userStore.CreateAsync(user); 145 | } 146 | } 147 | catch (Exception) 148 | { 149 | throw; 150 | } 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /src/IdentityServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=IdentityServer4;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } -------------------------------------------------------------------------------- /src/WebAPI/Controllers/NumbersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using WebAPI.Data; 8 | 9 | namespace WebAPI.Controllers 10 | { 11 | [Authorize] 12 | [Route("api/[controller]")] 13 | [ApiController] 14 | public class NumbersController : ControllerBase 15 | { 16 | // GET api/numbers 17 | [HttpGet] 18 | public IActionResult Get() 19 | { 20 | return new JsonResult(NumberService.GetAllNumbers()); 21 | } 22 | 23 | // POST api/numbers 24 | [HttpPost] 25 | public IActionResult Post([FromBody]int value) 26 | { 27 | NumberService.Add(value); 28 | return new NoContentResult(); 29 | } 30 | 31 | // POST api/numbers/delete 32 | [HttpPost("Delete")] 33 | public IActionResult Delete([FromBody]int id) 34 | { 35 | if (NumberService.GetNumberById(id) == null) 36 | { 37 | return BadRequest(); 38 | } 39 | 40 | NumberService.Delete(id); 41 | 42 | return new NoContentResult(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/WebAPI/Data/NumberService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using WebAPI.Models; 6 | 7 | namespace WebAPI.Data 8 | { 9 | // static service to test api 10 | public static class NumberService 11 | { 12 | private static List Numbers = new List(); 13 | private static int id = 1; 14 | 15 | public static void Initialize() 16 | { 17 | Random r = new Random(); 18 | 19 | for (int i = 0; i < 5; i++) 20 | { 21 | Numbers.Add(new Number { Id = id, Value = r.Next(1, 999) }); 22 | id++; 23 | } 24 | } 25 | 26 | public static List GetAllNumbers() 27 | { 28 | return Numbers; 29 | } 30 | 31 | public static Number GetNumberById(int id) 32 | { 33 | return Numbers.SingleOrDefault(x => x.Id == id); 34 | } 35 | 36 | public static void Add(int value) 37 | { 38 | var n = new Number { Id = id, Value = value }; 39 | id++; 40 | Numbers.Add(n); 41 | } 42 | 43 | public static void Delete(int id) 44 | { 45 | var number = GetNumberById(id); 46 | Numbers.Remove(number); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/WebAPI/Models/Number.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace WebAPI.Models 7 | { 8 | public class Number 9 | { 10 | public int Id { get; set; } 11 | public int Value { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/WebAPI/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace WebAPI 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/WebAPI/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:5001", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WebAPI": { 19 | "commandName": "Project", 20 | "applicationUrl": "http://localhost:5001", 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/WebAPI/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using Microsoft.IdentityModel.Tokens; 13 | using WebAPI.Data; 14 | 15 | namespace WebAPI 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services.AddMvcCore() 30 | .AddAuthorization() 31 | .AddJsonFormatters(); 32 | 33 | services.AddAuthentication("Bearer") 34 | .AddJwtBearer("Bearer", options => 35 | { 36 | options.Authority = "http://localhost:5000"; 37 | options.RequireHttpsMetadata = false; 38 | options.Audience = "api1"; 39 | options.TokenValidationParameters = new TokenValidationParameters() 40 | { 41 | ClockSkew = TimeSpan.FromMinutes(0) 42 | }; 43 | }); 44 | 45 | services.AddCors(options => 46 | { 47 | options.AddPolicy("default", policy => 48 | { 49 | policy.WithOrigins("http://localhost:4200") 50 | .AllowAnyHeader() 51 | .AllowAnyMethod(); 52 | }); 53 | }); 54 | } 55 | 56 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 57 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 58 | { 59 | if (env.IsDevelopment()) 60 | { 61 | app.UseDeveloperExceptionPage(); 62 | } 63 | 64 | app.UseCors("default"); 65 | app.UseAuthentication(); 66 | app.UseMvc(); 67 | 68 | NumberService.Initialize(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/WebAPI/WebAPI.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/WebAPI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/WebAPI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | --------------------------------------------------------------------------------