├── .dockerignore ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── TripTracker.BackService ├── Controllers │ └── TripsController.cs ├── Data │ └── TripContext.cs ├── Dockerfile ├── Models │ ├── Repository.cs │ ├── Segment.cs │ └── Trip.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── TripTracker.BackService.csproj ├── appsettings.Development.json └── appsettings.json ├── TripTracker.UI ├── Controllers │ └── AccountController.cs ├── Data │ ├── ApplicationDbContext.cs │ ├── ApplicationUser.cs │ └── Migrations │ │ ├── 00000000000000_CreateIdentitySchema.Designer.cs │ │ ├── 00000000000000_CreateIdentitySchema.cs │ │ └── ApplicationDbContextModelSnapshot.cs ├── Dockerfile ├── Extensions │ ├── EmailSenderExtensions.cs │ └── UrlHelperExtensions.cs ├── HttpClientExtensions.cs ├── Pages │ ├── Account │ │ ├── AccessDenied.cshtml │ │ ├── AccessDenied.cshtml.cs │ │ ├── ConfirmEmail.cshtml │ │ ├── ConfirmEmail.cshtml.cs │ │ ├── ExternalLogin.cshtml │ │ ├── ExternalLogin.cshtml.cs │ │ ├── ForgotPassword.cshtml │ │ ├── ForgotPassword.cshtml.cs │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml.cs │ │ ├── Lockout.cshtml │ │ ├── Lockout.cshtml.cs │ │ ├── Login.cshtml │ │ ├── Login.cshtml.cs │ │ ├── LoginWith2fa.cshtml │ │ ├── LoginWith2fa.cshtml.cs │ │ ├── LoginWithRecoveryCode.cshtml │ │ ├── LoginWithRecoveryCode.cshtml.cs │ │ ├── Manage │ │ │ ├── ChangePassword.cshtml │ │ │ ├── ChangePassword.cshtml.cs │ │ │ ├── Disable2fa.cshtml │ │ │ ├── Disable2fa.cshtml.cs │ │ │ ├── EnableAuthenticator.cshtml │ │ │ ├── EnableAuthenticator.cshtml.cs │ │ │ ├── ExternalLogins.cshtml │ │ │ ├── ExternalLogins.cshtml.cs │ │ │ ├── GenerateRecoveryCodes.cshtml │ │ │ ├── GenerateRecoveryCodes.cshtml.cs │ │ │ ├── Index.cshtml │ │ │ ├── Index.cshtml.cs │ │ │ ├── ManageNavPages.cs │ │ │ ├── ResetAuthenticator.cshtml │ │ │ ├── ResetAuthenticator.cshtml.cs │ │ │ ├── SetPassword.cshtml │ │ │ ├── SetPassword.cshtml.cs │ │ │ ├── ShowRecoveryCodes.cshtml │ │ │ ├── ShowRecoveryCodes.cshtml.cs │ │ │ ├── TwoFactorAuthentication.cshtml │ │ │ ├── TwoFactorAuthentication.cshtml.cs │ │ │ ├── _Layout.cshtml │ │ │ ├── _ManageNav.cshtml │ │ │ ├── _StatusMessage.cshtml │ │ │ └── _ViewImports.cshtml │ │ ├── Register.cshtml │ │ ├── Register.cshtml.cs │ │ ├── ResetPassword.cshtml │ │ ├── ResetPassword.cshtml.cs │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── ResetPasswordConfirmation.cshtml.cs │ │ ├── SignedOut.cshtml │ │ ├── SignedOut.cshtml.cs │ │ └── _ViewImports.cshtml │ ├── Components │ │ └── EditTrip │ │ │ └── Edit.cshtml │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.cshtml │ ├── Index.cshtml.cs │ ├── Trips │ │ ├── Create.cshtml │ │ ├── Create.cshtml.cs │ │ ├── Delete.cshtml │ │ ├── Delete.cshtml.cs │ │ ├── Details.cshtml │ │ ├── Details.cshtml.cs │ │ ├── Edit.cshtml │ │ ├── Edit.cshtml.cs │ │ ├── Index.cshtml │ │ └── Index.cshtml.cs │ ├── _Layout.cshtml │ ├── _LoginPartial.cshtml │ ├── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Security.cs ├── Services │ ├── EmailSender.cs │ ├── IApiClient.cs │ └── IEmailSender.cs ├── Startup.cs ├── TagHelpers │ └── DangerTagHelper.cs ├── TripTracker.UI.csproj ├── ViewComponents │ └── EditViewComponent.cs ├── appsettings.Development.json ├── appsettings.json ├── bundleconfig.json └── wwwroot │ ├── css │ ├── site.css │ └── site.min.css │ ├── favicon.ico │ ├── images │ ├── banner1.svg │ ├── banner2.svg │ ├── banner3.svg │ └── banner4.svg │ ├── js │ ├── site.js │ └── site.min.js │ └── lib │ ├── bootstrap │ ├── .bower.json │ ├── LICENSE │ └── dist │ │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ └── bootstrap.min.css.map │ │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ │ └── js │ │ ├── bootstrap.js │ │ └── npm.js │ ├── jquery-validation-unobtrusive │ ├── .bower.json │ ├── jquery.validate.unobtrusive.js │ └── jquery.validate.unobtrusive.min.js │ ├── jquery-validation │ ├── .bower.json │ ├── LICENSE.md │ └── dist │ │ ├── additional-methods.js │ │ └── jquery.validate.js │ └── jquery │ ├── .bower.json │ ├── LICENSE.txt │ └── dist │ ├── jquery.js │ └── jquery.min.map ├── TripTrackerLive.sln ├── docker-compose.dcproj ├── docker-compose.override.yml └── docker-compose.yml /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | .env 3 | .git 4 | .gitignore 5 | .vs 6 | .vscode 7 | docker-compose.yml 8 | docker-compose.*.yml 9 | */bin 10 | */obj 11 | -------------------------------------------------------------------------------- /.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 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | rleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | 290 | JeffsTrips.db 291 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TripTracker -------------------------------------------------------------------------------- /TripTracker.BackService/Controllers/TripsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.EntityFrameworkCore; 7 | using TripTracker.BackService.Data; 8 | using TripTracker.BackService.Models; 9 | 10 | namespace TripTracker.BackService.Controllers 11 | { 12 | 13 | [Route("api/[controller]")] 14 | public class TripsController : Controller 15 | { 16 | 17 | TripContext _context; 18 | public TripsController(TripContext context) 19 | { 20 | _context = context; 21 | // _context.ChangeTracker.QueryTrackingBehavior=QueryTrackingBehavior.NoTracking; 22 | } 23 | 24 | 25 | // GET api/Trips 26 | [HttpGet] 27 | public async Task GetAsync() 28 | { 29 | 30 | var trips = await _context.Trips 31 | .AsNoTracking() 32 | .ToListAsync(); 33 | return Ok(trips); 34 | 35 | } 36 | 37 | // GET api/Trips/5 38 | [HttpGet("{id}")] 39 | public Trip Get(int id) 40 | { 41 | return _context.Trips.Find(id); 42 | } 43 | 44 | // POST api/Trips 45 | [HttpPost] 46 | public IActionResult Post([FromBody]Trip value) 47 | { 48 | 49 | if (!ModelState.IsValid) 50 | { 51 | return BadRequest(ModelState); 52 | } 53 | 54 | _context.Trips.Add(value); 55 | _context.SaveChanges(); 56 | 57 | return Ok(); 58 | 59 | } 60 | 61 | // PUT api/Trips/5 62 | [HttpPut("{id}")] 63 | public async Task PutAsync(int id, [FromBody]Trip value) 64 | { 65 | 66 | if (!_context.Trips.Any(t => t.Id == id)) 67 | { 68 | return NotFound(); 69 | } 70 | 71 | if (!ModelState.IsValid) 72 | { 73 | return BadRequest(ModelState); 74 | } 75 | 76 | //what about nulls? 77 | _context.Trips.Update(value); 78 | await _context.SaveChangesAsync(); 79 | 80 | return Ok(); 81 | 82 | } 83 | 84 | // DELETE api/Trips/5 85 | [HttpDelete("{id}")] 86 | public IActionResult Delete(int id) 87 | { 88 | 89 | var myTrip = _context.Trips.Find(id); 90 | 91 | if (myTrip == null) 92 | { 93 | return NotFound(); 94 | } 95 | 96 | _context.Trips.Remove(myTrip); 97 | _context.SaveChanges(); 98 | 99 | // DELETE FROM Trips WHERE id=? 100 | 101 | return NoContent(); 102 | 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /TripTracker.BackService/Data/TripContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Linq; 5 | using TripTracker.BackService.Models; 6 | 7 | namespace TripTracker.BackService.Data 8 | { 9 | 10 | public class TripContext : DbContext 11 | { 12 | 13 | public TripContext(DbContextOptions options) 14 | : base(options){} 15 | 16 | public TripContext(){} 17 | 18 | public DbSet Trips { get; set; } 19 | 20 | //http://thedatafarm.com/uncategorized/seeding-ef-with-json-data/ 21 | public static void SeedData(IServiceProvider serviceProvider) 22 | { 23 | 24 | using (var serviceScope = serviceProvider 25 | .GetRequiredService().CreateScope()) 26 | { 27 | var context = serviceScope 28 | .ServiceProvider.GetService(); 29 | 30 | context.Database.EnsureCreated(); 31 | 32 | if (context.Trips.Any()) return; 33 | 34 | context.Trips.AddRange(new Trip[] 35 | { 36 | new Trip 37 | { 38 | Id = 1, 39 | Name = "MVP Summit", 40 | StartDate = new DateTime(2018, 3, 5), 41 | EndDate = new DateTime(2018, 3, 8) 42 | }, 43 | new Trip 44 | { 45 | Id = 2, 46 | Name = "DevIntersection Orlando 2018", 47 | StartDate = new DateTime(2018, 3, 25), 48 | EndDate = new DateTime(2018, 3, 27) 49 | }, 50 | new Trip 51 | { 52 | Id = 3, 53 | Name = "Build 2018", 54 | StartDate = new DateTime(2018, 5, 7), 55 | EndDate = new DateTime(2018, 5, 9) 56 | } 57 | } 58 | ); 59 | context.SaveChanges(); 60 | 61 | 62 | } 63 | 64 | } 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /TripTracker.BackService/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.1 AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | 5 | FROM microsoft/aspnetcore-build:2.1 AS build 6 | WORKDIR /src 7 | COPY TripTrackerLive.sln ./ 8 | COPY TripTracker.BackService/TripTracker.BackService.csproj TripTracker.BackService/ 9 | RUN dotnet restore -nowarn:msb3202,nu1503 10 | COPY . . 11 | WORKDIR /src/TripTracker.BackService 12 | RUN dotnet build -c Release -o /app 13 | 14 | FROM build AS publish 15 | RUN dotnet publish -c Release -o /app 16 | 17 | FROM base AS final 18 | WORKDIR /app 19 | COPY --from=publish /app . 20 | ENTRYPOINT ["dotnet", "TripTracker.BackService.dll"] 21 | -------------------------------------------------------------------------------- /TripTracker.BackService/Models/Repository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TripTracker.BackService.Models 7 | { 8 | public class Repository 9 | { 10 | 11 | private List MyTrips = new List 12 | { 13 | new Trip { 14 | Id = 1, 15 | Name = "MVP Summit", 16 | StartDate = new DateTime(2018, 3, 5), 17 | EndDate = new DateTime(2018, 3, 8) 18 | }, 19 | new Trip 20 | { 21 | Id=2, 22 | Name="DevIntersection Orlando 2018", 23 | StartDate = new DateTime(2018, 3, 25), 24 | EndDate = new DateTime(2018, 3, 27) 25 | }, 26 | new Trip 27 | { 28 | Id=3, 29 | Name="Build 2018", 30 | StartDate = new DateTime(2018, 5, 7), 31 | EndDate = new DateTime(2018, 5, 9) 32 | } 33 | }; 34 | 35 | public List Get() 36 | { 37 | return MyTrips; 38 | } 39 | 40 | public Trip Get(int id) 41 | { 42 | return MyTrips.First(t => t.Id == id); 43 | } 44 | 45 | public void Add(Trip newTrip ) 46 | { 47 | 48 | MyTrips.Add(newTrip); 49 | 50 | } 51 | 52 | public void Update(Trip tripToUpdate) 53 | { 54 | 55 | MyTrips.Remove(MyTrips.First(t => t.Id == tripToUpdate.Id)); 56 | Add(tripToUpdate); 57 | 58 | } 59 | 60 | public void Remove(int id) 61 | { 62 | MyTrips.Remove(MyTrips.First(t => t.Id == id)); 63 | } 64 | 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /TripTracker.BackService/Models/Segment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TripTracker.BackService.Models 7 | { 8 | public class Segment 9 | { 10 | 11 | public int Id { get; set; } 12 | 13 | public int TripId { get; set; } 14 | 15 | public string Name { get; set; } 16 | 17 | public string Description { get; set; } 18 | 19 | public DateTimeOffset StartDateTime { get; set; } 20 | 21 | public DateTimeOffset EndDateTime { get; set; } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TripTracker.BackService/Models/Trip.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 TripTracker.BackService.Models 8 | { 9 | public class Trip 10 | { 11 | 12 | [Key] 13 | public int Id { get; set; } 14 | 15 | [Required] 16 | public string Name { get; set; } 17 | 18 | [Required] 19 | public DateTime StartDate { get; set; } 20 | 21 | [Required] 22 | public DateTime EndDate { get; set; } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TripTracker.BackService/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 TripTracker.BackService 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TripTracker.BackService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:61173/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "launchUrl": "swagger", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "TripTracker.BackService": { 20 | "commandName": "Project", 21 | "launchBrowser": true, 22 | "launchUrl": "swagger", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | }, 26 | "applicationUrl": "http://localhost:61174/" 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /TripTracker.BackService/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.EntityFrameworkCore; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using Swashbuckle.AspNetCore.Swagger; 13 | using TripTracker.BackService.Data; 14 | 15 | namespace TripTracker.BackService 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.AddTransient(); 30 | services.AddMvc(); 31 | 32 | services.AddDbContext(options=> options.UseSqlite("Data Source=JeffsTrips.db")); 33 | 34 | services.AddSwaggerGen(options => 35 | options.SwaggerDoc("v1", new Info { Title = "Trip Tracker", Version = "v1" }) 36 | ); 37 | } 38 | 39 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 40 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 41 | { 42 | 43 | app.UseSwagger(); 44 | 45 | if (env.IsDevelopment() || env.IsStaging()) 46 | { 47 | 48 | app.UseSwaggerUI(options => 49 | options.SwaggerEndpoint("/swagger/v1/swagger.json", "Trip Tracker v1") 50 | ); 51 | 52 | } 53 | 54 | if (env.IsDevelopment()) 55 | { 56 | app.UseDeveloperExceptionPage(); 57 | } 58 | 59 | app.UseMvc(); 60 | 61 | TripContext.SeedData(app.ApplicationServices); 62 | 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /TripTracker.BackService/TripTracker.BackService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | ..\docker-compose.dcproj 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /TripTracker.BackService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TripTracker.BackService/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "Debug": { 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "Console": { 10 | "LogLevel": { 11 | "Default": "Warning" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /TripTracker.UI/Controllers/AccountController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Logging; 8 | using TripTracker.UI.Data; 9 | 10 | namespace TripTracker.UI.Controllers 11 | { 12 | 13 | [Route("[controller]/[action]")] 14 | public class AccountController : Controller 15 | { 16 | private readonly SignInManager _signInManager; 17 | private readonly ILogger _logger; 18 | 19 | public AccountController(SignInManager signInManager, ILogger logger) 20 | { 21 | _signInManager = signInManager; 22 | _logger = logger; 23 | } 24 | 25 | [HttpPost] 26 | [ValidateAntiForgeryToken] 27 | public async Task Logout() 28 | { 29 | await _signInManager.SignOutAsync(); 30 | _logger.LogInformation("User logged out."); 31 | return RedirectToPage("/Index"); 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TripTracker.UI/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore; 7 | using TripTracker.BackService.Models; 8 | 9 | namespace TripTracker.UI.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 | // Customize the ASP.NET Identity model and override the defaults if needed. 22 | // For example, you can rename the ASP.NET Identity table names and more. 23 | // Add your customizations after calling base.OnModelCreating(builder); 24 | } 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TripTracker.UI/Data/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace TripTracker.UI.Data 8 | { 9 | // Add profile data for application users by adding properties to the ApplicationUser class 10 | public class ApplicationUser : IdentityUser 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /TripTracker.UI/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Infrastructure; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Migrations; 9 | 10 | namespace TripTracker.UI.Data.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | [Migration("00000000000000_CreateIdentitySchema")] 14 | partial class CreateIdentitySchema 15 | { 16 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 17 | { 18 | modelBuilder 19 | .HasAnnotation("ProductVersion", "1.0.0-rc3") 20 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 21 | 22 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => 23 | { 24 | b.Property("Id"); 25 | 26 | b.Property("ConcurrencyStamp") 27 | .IsConcurrencyToken(); 28 | 29 | b.Property("Name") 30 | .HasAnnotation("MaxLength", 256); 31 | 32 | b.Property("NormalizedName") 33 | .HasAnnotation("MaxLength", 256); 34 | 35 | b.HasKey("Id"); 36 | 37 | b.HasIndex("NormalizedName") 38 | .HasName("RoleNameIndex"); 39 | 40 | b.ToTable("AspNetRoles"); 41 | }); 42 | 43 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 44 | { 45 | b.Property("Id") 46 | .ValueGeneratedOnAdd(); 47 | 48 | b.Property("ClaimType"); 49 | 50 | b.Property("ClaimValue"); 51 | 52 | b.Property("RoleId") 53 | .IsRequired(); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("RoleId"); 58 | 59 | b.ToTable("AspNetRoleClaims"); 60 | }); 61 | 62 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 63 | { 64 | b.Property("Id") 65 | .ValueGeneratedOnAdd(); 66 | 67 | b.Property("ClaimType"); 68 | 69 | b.Property("ClaimValue"); 70 | 71 | b.Property("UserId") 72 | .IsRequired(); 73 | 74 | b.HasKey("Id"); 75 | 76 | b.HasIndex("UserId"); 77 | 78 | b.ToTable("AspNetUserClaims"); 79 | }); 80 | 81 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 82 | { 83 | b.Property("LoginProvider"); 84 | 85 | b.Property("ProviderKey"); 86 | 87 | b.Property("ProviderDisplayName"); 88 | 89 | b.Property("UserId") 90 | .IsRequired(); 91 | 92 | b.HasKey("LoginProvider", "ProviderKey"); 93 | 94 | b.HasIndex("UserId"); 95 | 96 | b.ToTable("AspNetUserLogins"); 97 | }); 98 | 99 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 100 | { 101 | b.Property("UserId"); 102 | 103 | b.Property("RoleId"); 104 | 105 | b.HasKey("UserId", "RoleId"); 106 | 107 | b.HasIndex("RoleId"); 108 | 109 | b.HasIndex("UserId"); 110 | 111 | b.ToTable("AspNetUserRoles"); 112 | }); 113 | 114 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => 115 | { 116 | b.Property("UserId"); 117 | 118 | b.Property("LoginProvider"); 119 | 120 | b.Property("Name"); 121 | 122 | b.Property("Value"); 123 | 124 | b.HasKey("UserId", "LoginProvider", "Name"); 125 | 126 | b.ToTable("AspNetUserTokens"); 127 | }); 128 | 129 | modelBuilder.Entity("TripTracker.UI.Models.ApplicationUser", b => 130 | { 131 | b.Property("Id"); 132 | 133 | b.Property("AccessFailedCount"); 134 | 135 | b.Property("ConcurrencyStamp") 136 | .IsConcurrencyToken(); 137 | 138 | b.Property("Email") 139 | .HasAnnotation("MaxLength", 256); 140 | 141 | b.Property("EmailConfirmed"); 142 | 143 | b.Property("LockoutEnabled"); 144 | 145 | b.Property("LockoutEnd"); 146 | 147 | b.Property("NormalizedEmail") 148 | .HasAnnotation("MaxLength", 256); 149 | 150 | b.Property("NormalizedUserName") 151 | .HasAnnotation("MaxLength", 256); 152 | 153 | b.Property("PasswordHash"); 154 | 155 | b.Property("PhoneNumber"); 156 | 157 | b.Property("PhoneNumberConfirmed"); 158 | 159 | b.Property("SecurityStamp"); 160 | 161 | b.Property("TwoFactorEnabled"); 162 | 163 | b.Property("UserName") 164 | .HasAnnotation("MaxLength", 256); 165 | 166 | b.HasKey("Id"); 167 | 168 | b.HasIndex("NormalizedEmail") 169 | .HasName("EmailIndex"); 170 | 171 | b.HasIndex("NormalizedUserName") 172 | .IsUnique() 173 | .HasName("UserNameIndex"); 174 | 175 | b.ToTable("AspNetUsers"); 176 | }); 177 | 178 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => 179 | { 180 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 181 | .WithMany("Claims") 182 | .HasForeignKey("RoleId") 183 | .OnDelete(DeleteBehavior.Cascade); 184 | }); 185 | 186 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => 187 | { 188 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 189 | .WithMany("Claims") 190 | .HasForeignKey("UserId") 191 | .OnDelete(DeleteBehavior.Cascade); 192 | }); 193 | 194 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => 195 | { 196 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 197 | .WithMany("Logins") 198 | .HasForeignKey("UserId") 199 | .OnDelete(DeleteBehavior.Cascade); 200 | }); 201 | 202 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => 203 | { 204 | b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") 205 | .WithMany("Users") 206 | .HasForeignKey("RoleId") 207 | .OnDelete(DeleteBehavior.Cascade); 208 | 209 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 210 | .WithMany("Roles") 211 | .HasForeignKey("UserId") 212 | .OnDelete(DeleteBehavior.Cascade); 213 | }); 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /TripTracker.UI/Data/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.EntityFrameworkCore; 6 | using Microsoft.EntityFrameworkCore.Infrastructure; 7 | using Microsoft.EntityFrameworkCore.Metadata; 8 | using Microsoft.EntityFrameworkCore.Migrations; 9 | 10 | namespace TripTracker.UI.Data.Migrations 11 | { 12 | [DbContext(typeof(ApplicationDbContext))] 13 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 14 | { 15 | protected override void BuildModel(ModelBuilder modelBuilder) 16 | { 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "1.0.0-rc3") 19 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 20 | 21 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole", b => 22 | { 23 | b.Property("Id"); 24 | 25 | b.Property("ConcurrencyStamp") 26 | .IsConcurrencyToken(); 27 | 28 | b.Property("Name") 29 | .HasAnnotation("MaxLength", 256); 30 | 31 | b.Property("NormalizedName") 32 | .HasAnnotation("MaxLength", 256); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.HasIndex("NormalizedName") 37 | .HasName("RoleNameIndex"); 38 | 39 | b.ToTable("AspNetRoles"); 40 | }); 41 | 42 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 43 | { 44 | b.Property("Id") 45 | .ValueGeneratedOnAdd(); 46 | 47 | b.Property("ClaimType"); 48 | 49 | b.Property("ClaimValue"); 50 | 51 | b.Property("RoleId") 52 | .IsRequired(); 53 | 54 | b.HasKey("Id"); 55 | 56 | b.HasIndex("RoleId"); 57 | 58 | b.ToTable("AspNetRoleClaims"); 59 | }); 60 | 61 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 62 | { 63 | b.Property("Id") 64 | .ValueGeneratedOnAdd(); 65 | 66 | b.Property("ClaimType"); 67 | 68 | b.Property("ClaimValue"); 69 | 70 | b.Property("UserId") 71 | .IsRequired(); 72 | 73 | b.HasKey("Id"); 74 | 75 | b.HasIndex("UserId"); 76 | 77 | b.ToTable("AspNetUserClaims"); 78 | }); 79 | 80 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 81 | { 82 | b.Property("LoginProvider"); 83 | 84 | b.Property("ProviderKey"); 85 | 86 | b.Property("ProviderDisplayName"); 87 | 88 | b.Property("UserId") 89 | .IsRequired(); 90 | 91 | b.HasKey("LoginProvider", "ProviderKey"); 92 | 93 | b.HasIndex("UserId"); 94 | 95 | b.ToTable("AspNetUserLogins"); 96 | }); 97 | 98 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 99 | { 100 | b.Property("UserId"); 101 | 102 | b.Property("RoleId"); 103 | 104 | b.HasKey("UserId", "RoleId"); 105 | 106 | b.HasIndex("RoleId"); 107 | 108 | b.HasIndex("UserId"); 109 | 110 | b.ToTable("AspNetUserRoles"); 111 | }); 112 | 113 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserToken", b => 114 | { 115 | b.Property("UserId"); 116 | 117 | b.Property("LoginProvider"); 118 | 119 | b.Property("Name"); 120 | 121 | b.Property("Value"); 122 | 123 | b.HasKey("UserId", "LoginProvider", "Name"); 124 | 125 | b.ToTable("AspNetUserTokens"); 126 | }); 127 | 128 | modelBuilder.Entity("TripTracker.UI.Models.ApplicationUser", b => 129 | { 130 | b.Property("Id"); 131 | 132 | b.Property("AccessFailedCount"); 133 | 134 | b.Property("ConcurrencyStamp") 135 | .IsConcurrencyToken(); 136 | 137 | b.Property("Email") 138 | .HasAnnotation("MaxLength", 256); 139 | 140 | b.Property("EmailConfirmed"); 141 | 142 | b.Property("LockoutEnabled"); 143 | 144 | b.Property("LockoutEnd"); 145 | 146 | b.Property("NormalizedEmail") 147 | .HasAnnotation("MaxLength", 256); 148 | 149 | b.Property("NormalizedUserName") 150 | .HasAnnotation("MaxLength", 256); 151 | 152 | b.Property("PasswordHash"); 153 | 154 | b.Property("PhoneNumber"); 155 | 156 | b.Property("PhoneNumberConfirmed"); 157 | 158 | b.Property("SecurityStamp"); 159 | 160 | b.Property("TwoFactorEnabled"); 161 | 162 | b.Property("UserName") 163 | .HasAnnotation("MaxLength", 256); 164 | 165 | b.HasKey("Id"); 166 | 167 | b.HasIndex("NormalizedEmail") 168 | .HasName("EmailIndex"); 169 | 170 | b.HasIndex("NormalizedUserName") 171 | .IsUnique() 172 | .HasName("UserNameIndex"); 173 | 174 | b.ToTable("AspNetUsers"); 175 | }); 176 | 177 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRoleClaim", b => 178 | { 179 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 180 | .WithMany("Claims") 181 | .HasForeignKey("RoleId") 182 | .OnDelete(DeleteBehavior.Cascade); 183 | }); 184 | 185 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserClaim", b => 186 | { 187 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 188 | .WithMany("Claims") 189 | .HasForeignKey("UserId") 190 | .OnDelete(DeleteBehavior.Cascade); 191 | }); 192 | 193 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserLogin", b => 194 | { 195 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 196 | .WithMany("Logins") 197 | .HasForeignKey("UserId") 198 | .OnDelete(DeleteBehavior.Cascade); 199 | }); 200 | 201 | modelBuilder.Entity("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityUserRole", b => 202 | { 203 | b.HasOne("Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityRole") 204 | .WithMany("Users") 205 | .HasForeignKey("RoleId") 206 | .OnDelete(DeleteBehavior.Cascade); 207 | 208 | b.HasOne("TripTracker.UI.Models.ApplicationUser") 209 | .WithMany("Roles") 210 | .HasForeignKey("UserId") 211 | .OnDelete(DeleteBehavior.Cascade); 212 | }); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /TripTracker.UI/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/aspnetcore:2.1 AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | 5 | FROM microsoft/aspnetcore-build:2.1 AS build 6 | WORKDIR /src 7 | COPY TripTrackerLive.sln ./ 8 | COPY TripTracker.UI/TripTracker.UI.csproj TripTracker.UI/ 9 | COPY TripTracker.BackService/TripTracker.BackService.csproj TripTracker.BackService/ 10 | RUN dotnet restore -nowarn:msb3202,nu1503 11 | COPY . . 12 | WORKDIR /src/TripTracker.UI 13 | RUN dotnet build -c Release -o /app 14 | 15 | FROM build AS publish 16 | RUN dotnet publish -c Release -o /app 17 | 18 | FROM base AS final 19 | WORKDIR /app 20 | COPY --from=publish /app . 21 | ENTRYPOINT ["dotnet", "TripTracker.UI.dll"] 22 | -------------------------------------------------------------------------------- /TripTracker.UI/Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using TripTracker.UI.Services; 7 | 8 | namespace TripTracker.UI.Services 9 | { 10 | public static class EmailSenderExtensions 11 | { 12 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 13 | { 14 | return emailSender.SendEmailAsync(email, "Confirm your email", 15 | $"Please confirm your account by clicking here."); 16 | } 17 | 18 | public static Task SendResetPasswordAsync(this IEmailSender emailSender, string email, string callbackUrl) 19 | { 20 | return emailSender.SendEmailAsync(email, "Reset Password", 21 | $"Please reset your password by clicking here."); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TripTracker.UI/Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Microsoft.AspNetCore.Mvc 7 | { 8 | public static class UrlHelperExtensions 9 | { 10 | public static string GetLocalUrl(this IUrlHelper urlHelper, string localUrl) 11 | { 12 | if (!urlHelper.IsLocalUrl(localUrl)) 13 | { 14 | return urlHelper.Page("/Index"); 15 | } 16 | 17 | return localUrl; 18 | } 19 | 20 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 21 | { 22 | return urlHelper.Page( 23 | "/Account/ConfirmEmail", 24 | pageHandler: null, 25 | values: new { userId, code }, 26 | protocol: scheme); 27 | } 28 | 29 | public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 30 | { 31 | return urlHelper.Page( 32 | "/Account/ResetPassword", 33 | pageHandler: null, 34 | values: new { userId, code }, 35 | protocol: scheme); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /TripTracker.UI/HttpClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System.IO; 3 | using System.Net.Http; 4 | using System.Threading.Tasks; 5 | 6 | namespace TripTracker.UI.Services 7 | { 8 | public static class HttpClientExtensions 9 | { 10 | private static readonly JsonSerializer _jsonSerializer = new JsonSerializer(); 11 | 12 | public static async Task ReadAsJsonAsync(this HttpContent httpContent) 13 | { 14 | using (var stream = await httpContent.ReadAsStreamAsync()) 15 | { 16 | var jsonReader = new JsonTextReader(new StreamReader(stream)); 17 | 18 | return _jsonSerializer.Deserialize(jsonReader); 19 | } 20 | } 21 | 22 | public static Task PostJsonAsync(this HttpClient client, string url, T value) 23 | { 24 | return SendJsonAsync(client, HttpMethod.Post, url, value); 25 | } 26 | public static Task PutJsonAsync(this HttpClient client, string url, T value) 27 | { 28 | return SendJsonAsync(client, HttpMethod.Put, url, value); 29 | } 30 | 31 | public static Task SendJsonAsync(this HttpClient client, HttpMethod method, string url, T value) 32 | { 33 | var stream = new MemoryStream(); 34 | var jsonWriter = new JsonTextWriter(new StreamWriter(stream)); 35 | 36 | _jsonSerializer.Serialize(jsonWriter, value); 37 | 38 | jsonWriter.Flush(); 39 | 40 | stream.Position = 0; 41 | 42 | var request = new HttpRequestMessage(method, url) 43 | { 44 | Content = new StreamContent(stream) 45 | }; 46 | 47 | request.Content.Headers.TryAddWithoutValidation("Content-Type", "application/json"); 48 | 49 | return client.SendAsync(request); 50 | } 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AccessDeniedModel 3 | @{ 4 | ViewData["Title"] = "Access denied"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You do not have access to this resource.

10 |
11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/AccessDenied.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace TripTracker.UI.Pages.Account 8 | { 9 | public class AccessDeniedModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ConfirmEmailModel 3 | @{ 4 | ViewData["Title"] = "Confirm email"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | Thank you for confirming your email. 11 |

12 |
13 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ConfirmEmail.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using TripTracker.UI.Data; 9 | 10 | namespace TripTracker.UI.Pages.Account 11 | { 12 | public class ConfirmEmailModel : PageModel 13 | { 14 | private readonly UserManager _userManager; 15 | 16 | public ConfirmEmailModel(UserManager userManager) 17 | { 18 | _userManager = userManager; 19 | } 20 | 21 | public async Task OnGetAsync(string userId, string code) 22 | { 23 | if (userId == null || code == null) 24 | { 25 | return RedirectToPage("/Index"); 26 | } 27 | 28 | var user = await _userManager.FindByIdAsync(userId); 29 | if (user == null) 30 | { 31 | throw new ApplicationException($"Unable to load user with ID '{userId}'."); 32 | } 33 | 34 | var result = await _userManager.ConfirmEmailAsync(user, code); 35 | if (!result.Succeeded) 36 | { 37 | throw new ApplicationException($"Error confirming email for user with ID '{userId}':"); 38 | } 39 | 40 | return Page(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Associate your @Model.LoginProvider account.

9 |
10 | 11 |

12 | You've successfully authenticated with @Model.LoginProvider. 13 | Please enter an email address for this site below and click the Register button to finish 14 | logging in. 15 |

16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | @section Scripts { 32 | @await Html.PartialAsync("_ValidationScriptsPartial") 33 | } 34 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ExternalLogin.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Security.Claims; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | using TripTracker.UI.Data; 12 | 13 | namespace TripTracker.UI.Pages.Account 14 | { 15 | public class ExternalLoginModel : PageModel 16 | { 17 | private readonly SignInManager _signInManager; 18 | private readonly UserManager _userManager; 19 | private readonly ILogger _logger; 20 | 21 | public ExternalLoginModel( 22 | SignInManager signInManager, 23 | UserManager userManager, 24 | ILogger logger) 25 | { 26 | _signInManager = signInManager; 27 | _userManager = userManager; 28 | _logger = logger; 29 | } 30 | 31 | [BindProperty] 32 | public InputModel Input { get; set; } 33 | 34 | public string LoginProvider { get; set; } 35 | 36 | public string ReturnUrl { get; set; } 37 | 38 | [TempData] 39 | public string ErrorMessage { get; set; } 40 | 41 | public class InputModel 42 | { 43 | [Required] 44 | [EmailAddress] 45 | public string Email { get; set; } 46 | } 47 | 48 | public IActionResult OnGetAsync() 49 | { 50 | return RedirectToPage("./Login"); 51 | } 52 | 53 | public IActionResult OnPost(string provider, string returnUrl = null) 54 | { 55 | // Request a redirect to the external login provider. 56 | var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); 57 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); 58 | return new ChallengeResult(provider, properties); 59 | } 60 | 61 | public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) 62 | { 63 | if (remoteError != null) 64 | { 65 | ErrorMessage = $"Error from external provider: {remoteError}"; 66 | return RedirectToPage("./Login"); 67 | } 68 | var info = await _signInManager.GetExternalLoginInfoAsync(); 69 | if (info == null) 70 | { 71 | return RedirectToPage("./Login"); 72 | } 73 | 74 | // Sign in the user with this external login provider if the user already has a login. 75 | var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor : true); 76 | if (result.Succeeded) 77 | { 78 | _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); 79 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 80 | } 81 | if (result.IsLockedOut) 82 | { 83 | return RedirectToPage("./Lockout"); 84 | } 85 | else 86 | { 87 | // If the user does not have an account, then ask the user to create an account. 88 | ReturnUrl = returnUrl; 89 | LoginProvider = info.LoginProvider; 90 | if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) 91 | { 92 | Input = new InputModel 93 | { 94 | Email = info.Principal.FindFirstValue(ClaimTypes.Email) 95 | }; 96 | } 97 | return Page(); 98 | } 99 | } 100 | 101 | public async Task OnPostConfirmationAsync(string returnUrl = null) 102 | { 103 | if (ModelState.IsValid) 104 | { 105 | // Get the information about the user from the external login provider 106 | var info = await _signInManager.GetExternalLoginInfoAsync(); 107 | if (info == null) 108 | { 109 | throw new ApplicationException("Error loading external login information during confirmation."); 110 | } 111 | var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email }; 112 | var result = await _userManager.CreateAsync(user); 113 | if (result.Succeeded) 114 | { 115 | result = await _userManager.AddLoginAsync(user, info); 116 | if (result.Succeeded) 117 | { 118 | await _signInManager.SignInAsync(user, isPersistent: false); 119 | _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); 120 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 121 | } 122 | } 123 | foreach (var error in result.Errors) 124 | { 125 | ModelState.AddModelError(string.Empty, error.Description); 126 | } 127 | } 128 | 129 | ReturnUrl = returnUrl; 130 | return Page(); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordModel 3 | @{ 4 | ViewData["Title"] = "Forgot your password?"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Enter your email.

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | @section Scripts { 25 | @await Html.PartialAsync("_ValidationScriptsPartial") 26 | } 27 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ForgotPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using TripTracker.UI.Data; 9 | using TripTracker.UI.Services; 10 | 11 | namespace TripTracker.UI.Pages.Account 12 | { 13 | public class ForgotPasswordModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly IEmailSender _emailSender; 17 | 18 | public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) 19 | { 20 | _userManager = userManager; 21 | _emailSender = emailSender; 22 | } 23 | 24 | [BindProperty] 25 | public InputModel Input { get; set; } 26 | 27 | public class InputModel 28 | { 29 | [Required] 30 | [EmailAddress] 31 | public string Email { get; set; } 32 | } 33 | 34 | public async Task OnPostAsync() 35 | { 36 | if (ModelState.IsValid) 37 | { 38 | var user = await _userManager.FindByEmailAsync(Input.Email); 39 | if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) 40 | { 41 | // Don't reveal that the user does not exist or is not confirmed 42 | return RedirectToPage("./ForgotPasswordConfirmation"); 43 | } 44 | 45 | // For more information on how to enable account confirmation and password reset please 46 | // visit https://go.microsoft.com/fwlink/?LinkID=532713 47 | var code = await _userManager.GeneratePasswordResetTokenAsync(user); 48 | var callbackUrl = Url.ResetPasswordCallbackLink(user.Id, code, Request.Scheme); 49 | await _emailSender.SendResetPasswordAsync(Input.Email, callbackUrl); 50 | return RedirectToPage("./ForgotPasswordConfirmation"); 51 | } 52 | 53 | return Page(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordConfirmation 3 | @{ 4 | ViewData["Title"] = "Forgot password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Please check your email to reset your password. 10 |

11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ForgotPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace TripTracker.UI.Pages.Account 8 | { 9 | public class ForgotPasswordConfirmation : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LockoutModel 3 | @{ 4 | ViewData["Title"] = "Locked out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

This account has been locked out, please try again later.

10 |
11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Lockout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace TripTracker.UI.Pages.Account 8 | { 9 | public class LockoutModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginModel 3 | 4 | @{ 5 | ViewData["Title"] = "Log in"; 6 | } 7 | 8 |

@ViewData["Title"]

9 |
10 |
11 |
12 |
13 |

Use a local account to log in.

14 |
15 |
16 |
17 | 18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 |
28 | 32 |
33 |
34 |
35 | 36 |
37 |
38 |

39 | Forgot your password? 40 |

41 |

42 | Register as a new user 43 |

44 |
45 |
46 |
47 |
48 |
49 |
50 |

Use another service to log in.

51 |
52 | @{ 53 | if ((Model.ExternalLogins?.Count ?? 0) == 0) 54 | { 55 |
56 |

57 | There are no external authentication services configured. See this article 58 | for details on setting up this ASP.NET application to support logging in via external services. 59 |

60 |
61 | } 62 | else 63 | { 64 |
65 |
66 |

67 | @foreach (var provider in Model.ExternalLogins) 68 | { 69 | 70 | } 71 |

72 |
73 |
74 | } 75 | } 76 |
77 |
78 |
79 | 80 | @section Scripts { 81 | @await Html.PartialAsync("_ValidationScriptsPartial") 82 | } 83 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Login.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Authentication; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | using TripTracker.UI.Data; 12 | 13 | namespace TripTracker.UI.Pages.Account 14 | { 15 | public class LoginModel : PageModel 16 | { 17 | private readonly SignInManager _signInManager; 18 | private readonly ILogger _logger; 19 | 20 | public LoginModel(SignInManager signInManager, ILogger logger) 21 | { 22 | _signInManager = signInManager; 23 | _logger = logger; 24 | } 25 | 26 | [BindProperty] 27 | public InputModel Input { get; set; } 28 | 29 | public IList ExternalLogins { get; set; } 30 | 31 | public string ReturnUrl { get; set; } 32 | 33 | [TempData] 34 | public string ErrorMessage { get; set; } 35 | 36 | public class InputModel 37 | { 38 | [Required] 39 | [EmailAddress] 40 | public string Email { get; set; } 41 | 42 | [Required] 43 | [DataType(DataType.Password)] 44 | public string Password { get; set; } 45 | 46 | [Display(Name = "Remember me?")] 47 | public bool RememberMe { get; set; } 48 | } 49 | 50 | public async Task OnGetAsync(string returnUrl = null) 51 | { 52 | if (!string.IsNullOrEmpty(ErrorMessage)) 53 | { 54 | ModelState.AddModelError(string.Empty, ErrorMessage); 55 | } 56 | 57 | // Clear the existing external cookie to ensure a clean login process 58 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 59 | 60 | ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); 61 | 62 | ReturnUrl = returnUrl; 63 | } 64 | 65 | public async Task OnPostAsync(string returnUrl = null) 66 | { 67 | ReturnUrl = returnUrl; 68 | 69 | if (ModelState.IsValid) 70 | { 71 | // This doesn't count login failures towards account lockout 72 | // To enable password failures to trigger account lockout, set lockoutOnFailure: true 73 | var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true); 74 | if (result.Succeeded) 75 | { 76 | _logger.LogInformation("User logged in."); 77 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 78 | } 79 | if (result.RequiresTwoFactor) 80 | { 81 | return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); 82 | } 83 | if (result.IsLockedOut) 84 | { 85 | _logger.LogWarning("User account locked out."); 86 | return RedirectToPage("./Lockout"); 87 | } 88 | else 89 | { 90 | ModelState.AddModelError(string.Empty, "Invalid login attempt."); 91 | return Page(); 92 | } 93 | } 94 | 95 | // If we got this far, something failed, redisplay form 96 | return Page(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWith2faModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

Your login is protected with an authenticator app. Enter your authenticator code below.

10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | 26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |

35 | Don't have access to your authenticator device? You can 36 | log in with a recovery code. 37 |

38 | 39 | @section Scripts { 40 | @await Html.PartialAsync("_ValidationScriptsPartial") 41 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/LoginWith2fa.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | using TripTracker.UI.Data; 11 | 12 | namespace TripTracker.UI.Pages.Account 13 | { 14 | public class LoginWith2faModel : PageModel 15 | { 16 | private readonly SignInManager _signInManager; 17 | private readonly ILogger _logger; 18 | 19 | public LoginWith2faModel(SignInManager signInManager, ILogger logger) 20 | { 21 | _signInManager = signInManager; 22 | _logger = logger; 23 | } 24 | 25 | [BindProperty] 26 | public InputModel Input { get; set; } 27 | 28 | public bool RememberMe { get; set; } 29 | 30 | public string ReturnUrl { get; set; } 31 | 32 | public class InputModel 33 | { 34 | [Required] 35 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 36 | [DataType(DataType.Text)] 37 | [Display(Name = "Authenticator code")] 38 | public string TwoFactorCode { get; set; } 39 | 40 | [Display(Name = "Remember this machine")] 41 | public bool RememberMachine { get; set; } 42 | } 43 | 44 | public async Task OnGetAsync(bool rememberMe, string returnUrl = null) 45 | { 46 | // Ensure the user has gone through the username & password screen first 47 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 48 | 49 | if (user == null) 50 | { 51 | throw new ApplicationException($"Unable to load two-factor authentication user."); 52 | } 53 | 54 | ReturnUrl = returnUrl; 55 | RememberMe = rememberMe; 56 | 57 | return Page(); 58 | } 59 | 60 | public async Task OnPostAsync(bool rememberMe, string returnUrl = null) 61 | { 62 | if (!ModelState.IsValid) 63 | { 64 | return Page(); 65 | } 66 | 67 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 68 | if (user == null) 69 | { 70 | throw new ApplicationException($"Unable to load two-factor authentication user."); 71 | } 72 | 73 | var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); 74 | 75 | var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine); 76 | 77 | if (result.Succeeded) 78 | { 79 | _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id); 80 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 81 | } 82 | else if (result.IsLockedOut) 83 | { 84 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); 85 | return RedirectToPage("./Lockout"); 86 | } 87 | else 88 | { 89 | _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id); 90 | ModelState.AddModelError(string.Empty, "Invalid authenticator code."); 91 | return Page(); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWithRecoveryCodeModel 3 | @{ 4 | ViewData["Title"] = "Recovery code verification"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | You have requested to log in with a recovery code. This login will not be remembered until you provide 11 | an authenticator app code at log in or disable 2FA and log in again. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | @section Scripts { 28 | @await Html.PartialAsync("_ValidationScriptsPartial") 29 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/LoginWithRecoveryCode.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | using TripTracker.UI.Data; 11 | 12 | namespace TripTracker.UI.Pages.Account 13 | { 14 | public class LoginWithRecoveryCodeModel : PageModel 15 | { 16 | private readonly SignInManager _signInManager; 17 | private readonly ILogger _logger; 18 | 19 | public LoginWithRecoveryCodeModel(SignInManager signInManager, ILogger logger) 20 | { 21 | _signInManager = signInManager; 22 | _logger = logger; 23 | } 24 | 25 | [BindProperty] 26 | public InputModel Input { get; set; } 27 | 28 | public string ReturnUrl { get; set; } 29 | 30 | public class InputModel 31 | { 32 | [BindProperty] 33 | [Required] 34 | [DataType(DataType.Text)] 35 | [Display(Name = "Recovery Code")] 36 | public string RecoveryCode { get; set; } 37 | } 38 | 39 | public async Task OnGetAsync(string returnUrl = null) 40 | { 41 | // Ensure the user has gone through the username & password screen first 42 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 43 | if (user == null) 44 | { 45 | throw new ApplicationException($"Unable to load two-factor authentication user."); 46 | } 47 | 48 | ReturnUrl = returnUrl; 49 | 50 | return Page(); 51 | } 52 | 53 | public async Task OnPostAsync(string returnUrl = null) 54 | { 55 | if (!ModelState.IsValid) 56 | { 57 | return Page(); 58 | } 59 | 60 | var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); 61 | if (user == null) 62 | { 63 | throw new ApplicationException($"Unable to load two-factor authentication user."); 64 | } 65 | 66 | var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); 67 | 68 | var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); 69 | 70 | if (result.Succeeded) 71 | { 72 | _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id); 73 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 74 | } 75 | if (result.IsLockedOut) 76 | { 77 | _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); 78 | return RedirectToPage("./Lockout"); 79 | } 80 | else 81 | { 82 | _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id); 83 | ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); 84 | return Page(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ChangePasswordModel 3 | @{ 4 | ViewData["Title"] = "Change password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | @await Html.PartialAsync("_ValidationScriptsPartial") 35 | } 36 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ChangePassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | using TripTracker.UI.Data; 11 | 12 | namespace TripTracker.UI.Pages.Account.Manage 13 | { 14 | public class ChangePasswordModel : PageModel 15 | { 16 | private readonly UserManager _userManager; 17 | private readonly SignInManager _signInManager; 18 | private readonly ILogger _logger; 19 | 20 | public ChangePasswordModel( 21 | UserManager userManager, 22 | SignInManager signInManager, 23 | ILogger logger) 24 | { 25 | _userManager = userManager; 26 | _signInManager = signInManager; 27 | _logger = logger; 28 | } 29 | 30 | [BindProperty] 31 | public InputModel Input { get; set; } 32 | 33 | [TempData] 34 | public string StatusMessage { get; set; } 35 | 36 | public class InputModel 37 | { 38 | [Required] 39 | [DataType(DataType.Password)] 40 | [Display(Name = "Current password")] 41 | public string OldPassword { get; set; } 42 | 43 | [Required] 44 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 45 | [DataType(DataType.Password)] 46 | [Display(Name = "New password")] 47 | public string NewPassword { get; set; } 48 | 49 | [DataType(DataType.Password)] 50 | [Display(Name = "Confirm new password")] 51 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 52 | public string ConfirmPassword { get; set; } 53 | } 54 | 55 | public async Task OnGetAsync() 56 | { 57 | var user = await _userManager.GetUserAsync(User); 58 | if (user == null) 59 | { 60 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 61 | } 62 | 63 | var hasPassword = await _userManager.HasPasswordAsync(user); 64 | if (!hasPassword) 65 | { 66 | return RedirectToPage("./SetPassword"); 67 | } 68 | 69 | return Page(); 70 | } 71 | 72 | public async Task OnPostAsync() 73 | { 74 | if (!ModelState.IsValid) 75 | { 76 | return Page(); 77 | } 78 | 79 | var user = await _userManager.GetUserAsync(User); 80 | if (user == null) 81 | { 82 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 83 | } 84 | 85 | var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); 86 | if (!changePasswordResult.Succeeded) 87 | { 88 | foreach (var error in changePasswordResult.Errors) 89 | { 90 | ModelState.AddModelError(string.Empty, error.Description); 91 | } 92 | return Page(); 93 | } 94 | 95 | await _signInManager.SignInAsync(user, isPersistent: false); 96 | _logger.LogInformation("User changed their password successfully."); 97 | StatusMessage = "Your password has been changed."; 98 | 99 | return RedirectToPage(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Disable2faModel 3 | @{ 4 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/Disable2fa.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class Disable2faModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly ILogger _logger; 17 | 18 | public Disable2faModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | public async Task OnGet() 27 | { 28 | var user = await _userManager.GetUserAsync(User); 29 | if (user == null) 30 | { 31 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 32 | } 33 | 34 | if (!await _userManager.GetTwoFactorEnabledAsync(user)) 35 | { 36 | throw new ApplicationException($"Cannot disable 2FA for user with ID '{_userManager.GetUserId(User)}' as it's not currently enabled."); 37 | } 38 | 39 | return Page(); 40 | } 41 | 42 | public async Task OnPostAsync() 43 | { 44 | var user = await _userManager.GetUserAsync(User); 45 | if (user == null) 46 | { 47 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 48 | } 49 | 50 | var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); 51 | if (!disable2faResult.Succeeded) 52 | { 53 | throw new ApplicationException($"Unexpected error occurred disabling 2FA for user with ID '{_userManager.GetUserId(User)}'."); 54 | } 55 | 56 | _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); 57 | 58 | return RedirectToPage("./TwoFactorAuthentication"); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/EnableAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model EnableAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Configure authenticator app"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 |
10 |

To use an authenticator app go through the following steps:

11 |
    12 |
  1. 13 |

    14 | Download a two-factor authenticator app like Microsoft Authenticator for 15 | Windows Phone, 16 | Android and 17 | iOS or 18 | Google Authenticator for 19 | Android and 20 | iOS. 21 |

    22 |
  2. 23 |
  3. 24 |

    Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.

    25 |
    To enable QR code generation please read our documentation.
    26 |
    27 |
    28 |
  4. 29 |
  5. 30 |

    31 | Once you have scanned the QR code or input the key above, your two factor authentication app will provide you 32 | with a unique code. Enter the code in the confirmation box below. 33 |

    34 |
    35 |
    36 |
    37 |
    38 | 39 | 40 | 41 |
    42 | 43 |
    44 |
    45 |
    46 |
    47 |
  6. 48 |
49 |
50 | 51 | @section Scripts { 52 | @await Html.PartialAsync("_ValidationScriptsPartial") 53 | } 54 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/EnableAuthenticator.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Text.Encodings.Web; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using Microsoft.AspNetCore.Identity; 10 | using Microsoft.AspNetCore.Mvc; 11 | using Microsoft.AspNetCore.Mvc.RazorPages; 12 | using Microsoft.Extensions.Logging; 13 | using TripTracker.UI.Data; 14 | 15 | namespace TripTracker.UI.Pages.Account.Manage 16 | { 17 | public class EnableAuthenticatorModel : PageModel 18 | { 19 | private readonly UserManager _userManager; 20 | private readonly ILogger _logger; 21 | private readonly UrlEncoder _urlEncoder; 22 | 23 | private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; 24 | 25 | public EnableAuthenticatorModel( 26 | UserManager userManager, 27 | ILogger logger, 28 | UrlEncoder urlEncoder) 29 | { 30 | _userManager = userManager; 31 | _logger = logger; 32 | _urlEncoder = urlEncoder; 33 | } 34 | 35 | public string SharedKey { get; set; } 36 | 37 | public string AuthenticatorUri { get; set; } 38 | 39 | [BindProperty] 40 | public InputModel Input { get; set; } 41 | 42 | public class InputModel 43 | { 44 | [Required] 45 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 46 | [DataType(DataType.Text)] 47 | [Display(Name = "Verification Code")] 48 | public string Code { get; set; } 49 | } 50 | 51 | public async Task OnGetAsync() 52 | { 53 | var user = await _userManager.GetUserAsync(User); 54 | if (user == null) 55 | { 56 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 57 | } 58 | 59 | await LoadSharedKeyAndQrCodeUriAsync(user); 60 | 61 | return Page(); 62 | } 63 | 64 | public async Task OnPostAsync() 65 | { 66 | var user = await _userManager.GetUserAsync(User); 67 | if (user == null) 68 | { 69 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 70 | } 71 | 72 | if (!ModelState.IsValid) 73 | { 74 | await LoadSharedKeyAndQrCodeUriAsync(user); 75 | return Page(); 76 | } 77 | 78 | // Strip spaces and hypens 79 | var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); 80 | 81 | var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( 82 | user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); 83 | 84 | if (!is2faTokenValid) 85 | { 86 | ModelState.AddModelError("Input.Code", "Verification code is invalid."); 87 | await LoadSharedKeyAndQrCodeUriAsync(user); 88 | return Page(); 89 | } 90 | 91 | await _userManager.SetTwoFactorEnabledAsync(user, true); 92 | _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", user.Id); 93 | 94 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); 95 | TempData["RecoveryCodes"] = recoveryCodes.ToArray(); 96 | return RedirectToPage("./ShowRecoveryCodes"); 97 | } 98 | 99 | private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) 100 | { 101 | // Load the authenticator key & QR code URI to display on the form 102 | var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); 103 | if (string.IsNullOrEmpty(unformattedKey)) 104 | { 105 | await _userManager.ResetAuthenticatorKeyAsync(user); 106 | unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); 107 | } 108 | 109 | SharedKey = FormatKey(unformattedKey); 110 | AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey); 111 | } 112 | 113 | private string FormatKey(string unformattedKey) 114 | { 115 | var result = new StringBuilder(); 116 | int currentPosition = 0; 117 | while (currentPosition + 4 < unformattedKey.Length) 118 | { 119 | result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" "); 120 | currentPosition += 4; 121 | } 122 | if (currentPosition < unformattedKey.Length) 123 | { 124 | result.Append(unformattedKey.Substring(currentPosition)); 125 | } 126 | 127 | return result.ToString().ToLowerInvariant(); 128 | } 129 | 130 | private string GenerateQrCodeUri(string email, string unformattedKey) 131 | { 132 | return string.Format( 133 | AuthenticatorUriFormat, 134 | _urlEncoder.Encode("TripTracker.UI"), 135 | _urlEncoder.Encode(email), 136 | unformattedKey); 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ExternalLogins.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginsModel 3 | @{ 4 | ViewData["Title"] = "Manage your external logins"; 5 | } 6 | 7 | @Html.Partial("_StatusMessage", Model.StatusMessage) 8 | @if (Model.CurrentLogins?.Count > 0) 9 | { 10 |

Registered Logins

11 | 12 | 13 | @foreach (var login in Model.CurrentLogins) 14 | { 15 | 16 | 17 | 33 | 34 | } 35 | 36 |
@login.LoginProvider 18 | @if (Model.ShowRemoveButton) 19 | { 20 |
21 |
22 | 23 | 24 | 25 |
26 |
27 | } 28 | else 29 | { 30 | @:   31 | } 32 |
37 | } 38 | @if (Model.OtherLogins?.Count > 0) 39 | { 40 |

Add another service to log in.

41 |
42 |
43 |
44 |

45 | @foreach (var provider in Model.OtherLogins) 46 | { 47 | 48 | } 49 |

50 |
51 |
52 | } 53 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ExternalLogins.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class ExternalLoginsModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly SignInManager _signInManager; 17 | 18 | public ExternalLoginsModel( 19 | UserManager userManager, 20 | SignInManager signInManager) 21 | { 22 | _userManager = userManager; 23 | _signInManager = signInManager; 24 | } 25 | 26 | public IList CurrentLogins { get; set; } 27 | 28 | public IList OtherLogins { get; set; } 29 | 30 | public bool ShowRemoveButton { get; set; } 31 | 32 | [TempData] 33 | public string StatusMessage { get; set; } 34 | 35 | public async Task OnGetAsync() 36 | { 37 | var user = await _userManager.GetUserAsync(User); 38 | if (user == null) 39 | { 40 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 41 | } 42 | 43 | CurrentLogins = await _userManager.GetLoginsAsync(user); 44 | OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()) 45 | .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider)) 46 | .ToList(); 47 | ShowRemoveButton = user.PasswordHash != null || CurrentLogins.Count > 1; 48 | return Page(); 49 | } 50 | 51 | public async Task OnPostRemoveLoginAsync(string loginProvider, string providerKey) 52 | { 53 | var user = await _userManager.GetUserAsync(User); 54 | if (user == null) 55 | { 56 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 57 | } 58 | 59 | var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey); 60 | if (!result.Succeeded) 61 | { 62 | throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'."); 63 | } 64 | 65 | await _signInManager.SignInAsync(user, isPersistent: false); 66 | StatusMessage = "The external login was removed."; 67 | return RedirectToPage(); 68 | } 69 | 70 | public async Task OnPostLinkLoginAsync(string provider) 71 | { 72 | // Clear the existing external cookie to ensure a clean login process 73 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 74 | 75 | // Request a redirect to the external login provider to link a login for the current user 76 | var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback"); 77 | var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); 78 | return new ChallengeResult(provider, properties); 79 | } 80 | 81 | public async Task OnGetLinkLoginCallbackAsync() 82 | { 83 | var user = await _userManager.GetUserAsync(User); 84 | if (user == null) 85 | { 86 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 87 | } 88 | 89 | var info = await _signInManager.GetExternalLoginInfoAsync(await _userManager.GetUserIdAsync(user)); 90 | if (info == null) 91 | { 92 | throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'."); 93 | } 94 | 95 | var result = await _userManager.AddLoginAsync(user, info); 96 | if (!result.Succeeded) 97 | { 98 | throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'."); 99 | } 100 | 101 | // Clear the existing external cookie to ensure a clean login process 102 | await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); 103 | 104 | StatusMessage = "The external login was added."; 105 | return RedirectToPage(); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model GenerateRecoveryCodesModel 3 | @{ 4 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class GenerateRecoveryCodesModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly ILogger _logger; 17 | 18 | public GenerateRecoveryCodesModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | 26 | public async Task OnGetAsync() 27 | { 28 | var user = await _userManager.GetUserAsync(User); 29 | if (user == null) 30 | { 31 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 32 | } 33 | 34 | if (!user.TwoFactorEnabled) 35 | { 36 | throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' because they do not have 2FA enabled."); 37 | } 38 | 39 | return Page(); 40 | } 41 | 42 | public async Task OnPostAsync() 43 | { 44 | var user = await _userManager.GetUserAsync(User); 45 | if (user == null) 46 | { 47 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 48 | } 49 | 50 | if (!user.TwoFactorEnabled) 51 | { 52 | throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled."); 53 | } 54 | 55 | var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); 56 | TempData["RecoveryCodes"] = recoveryCodes.ToArray(); 57 | 58 | _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", user.Id); 59 | 60 | return RedirectToPage("./ShowRecoveryCodes"); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Profile"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @Html.Partial("_StatusMessage", Model.StatusMessage) 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 |
17 |
18 | 19 | @if (Model.IsEmailConfirmed) 20 | { 21 |
22 | 23 | 24 |
25 | } 26 | else 27 | { 28 | 29 | 30 | } 31 | 32 |
33 |
34 | 35 | 36 | 37 |
38 | 39 |
40 |
41 |
42 | 43 | @section Scripts { 44 | @await Html.PartialAsync("_ValidationScriptsPartial") 45 | } 46 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Text.Encodings.Web; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using TripTracker.UI.Data; 11 | using TripTracker.UI.Services; 12 | 13 | namespace TripTracker.UI.Pages.Account.Manage 14 | { 15 | public partial class IndexModel : PageModel 16 | { 17 | private readonly UserManager _userManager; 18 | private readonly SignInManager _signInManager; 19 | private readonly IEmailSender _emailSender; 20 | 21 | public IndexModel( 22 | UserManager userManager, 23 | SignInManager signInManager, 24 | IEmailSender emailSender) 25 | { 26 | _userManager = userManager; 27 | _signInManager = signInManager; 28 | _emailSender = emailSender; 29 | } 30 | 31 | public string Username { get; set; } 32 | 33 | public bool IsEmailConfirmed { get; set; } 34 | 35 | [TempData] 36 | public string StatusMessage { get; set; } 37 | 38 | [BindProperty] 39 | public InputModel Input { get; set; } 40 | 41 | public class InputModel 42 | { 43 | [Required] 44 | [EmailAddress] 45 | public string Email { get; set; } 46 | 47 | [Phone] 48 | [Display(Name = "Phone number")] 49 | public string PhoneNumber { get; set; } 50 | } 51 | 52 | public async Task OnGetAsync() 53 | { 54 | var user = await _userManager.GetUserAsync(User); 55 | if (user == null) 56 | { 57 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 58 | } 59 | 60 | Username = user.UserName; 61 | 62 | Input = new InputModel 63 | { 64 | Email = user.Email, 65 | PhoneNumber = user.PhoneNumber 66 | }; 67 | 68 | IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); 69 | 70 | return Page(); 71 | } 72 | 73 | public async Task OnPostAsync() 74 | { 75 | if (!ModelState.IsValid) 76 | { 77 | return Page(); 78 | } 79 | 80 | var user = await _userManager.GetUserAsync(User); 81 | if (user == null) 82 | { 83 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 84 | } 85 | 86 | if (Input.Email != user.Email) 87 | { 88 | var setEmailResult = await _userManager.SetEmailAsync(user, Input.Email); 89 | if (!setEmailResult.Succeeded) 90 | { 91 | throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'."); 92 | } 93 | } 94 | 95 | if (Input.PhoneNumber != user.PhoneNumber) 96 | { 97 | var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); 98 | if (!setPhoneResult.Succeeded) 99 | { 100 | throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'."); 101 | } 102 | } 103 | 104 | StatusMessage = "Your profile has been updated"; 105 | return RedirectToPage(); 106 | } 107 | public async Task OnPostSendVerificationEmailAsync() 108 | { 109 | if (!ModelState.IsValid) 110 | { 111 | return Page(); 112 | } 113 | 114 | var user = await _userManager.GetUserAsync(User); 115 | if (user == null) 116 | { 117 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 118 | } 119 | 120 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); 121 | var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); 122 | await _emailSender.SendEmailConfirmationAsync(user.Email, callbackUrl); 123 | 124 | StatusMessage = "Verification email sent. Please check your email."; 125 | return RedirectToPage(); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ManageNavPages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | namespace TripTracker.UI.Pages.Account.Manage 8 | { 9 | public static class ManageNavPages 10 | { 11 | public static string Index => "Index"; 12 | 13 | public static string ChangePassword => "ChangePassword"; 14 | 15 | public static string ExternalLogins => "ExternalLogins"; 16 | 17 | public static string TwoFactorAuthentication => "TwoFactorAuthentication"; 18 | 19 | public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); 20 | 21 | public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); 22 | 23 | public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); 24 | 25 | public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); 26 | 27 | public static string PageNavClass(ViewContext viewContext, string page) 28 | { 29 | var activePage = viewContext.ViewData["ActivePage"] as string 30 | ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); 31 | return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Reset authenticator key"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 19 |
20 |
21 | 22 |
23 |
-------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ResetAuthenticator.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class ResetAuthenticatorModel : PageModel 14 | { 15 | UserManager _userManager; 16 | ILogger _logger; 17 | 18 | public ResetAuthenticatorModel( 19 | UserManager userManager, 20 | ILogger logger) 21 | { 22 | _userManager = userManager; 23 | _logger = logger; 24 | } 25 | public async Task OnGet() 26 | { 27 | var user = await _userManager.GetUserAsync(User); 28 | if (user == null) 29 | { 30 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 31 | } 32 | 33 | return Page(); 34 | } 35 | 36 | public async Task OnPostAsync() 37 | { 38 | var user = await _userManager.GetUserAsync(User); 39 | if (user == null) 40 | { 41 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 42 | } 43 | 44 | await _userManager.SetTwoFactorEnabledAsync(user, false); 45 | await _userManager.ResetAuthenticatorKeyAsync(user); 46 | _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); 47 | 48 | return RedirectToPage("./EnableAuthenticator"); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Set password"; 5 | ViewData["ActivePage"] = "ChangePassword"; 6 | } 7 | 8 |

Set your password

9 | @Html.Partial("_StatusMessage", Model.StatusMessage) 10 |

11 | You do not have a local username/password for this site. Add a local 12 | account so you can log in without an external login. 13 |

14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | @await Html.PartialAsync("_ValidationScriptsPartial") 35 | } 36 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/SetPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class SetPasswordModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | private readonly SignInManager _signInManager; 17 | 18 | public SetPasswordModel( 19 | UserManager userManager, 20 | SignInManager signInManager) 21 | { 22 | _userManager = userManager; 23 | _signInManager = signInManager; 24 | } 25 | 26 | [BindProperty] 27 | public InputModel Input { get; set; } 28 | 29 | [TempData] 30 | public string StatusMessage { get; set; } 31 | 32 | public class InputModel 33 | { 34 | [Required] 35 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 36 | [DataType(DataType.Password)] 37 | [Display(Name = "New password")] 38 | public string NewPassword { get; set; } 39 | 40 | [DataType(DataType.Password)] 41 | [Display(Name = "Confirm new password")] 42 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 43 | public string ConfirmPassword { get; set; } 44 | } 45 | 46 | public async Task OnGetAsync() 47 | { 48 | var user = await _userManager.GetUserAsync(User); 49 | if (user == null) 50 | { 51 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 52 | } 53 | 54 | var hasPassword = await _userManager.HasPasswordAsync(user); 55 | 56 | if (hasPassword) 57 | { 58 | return RedirectToPage("./ChangePassword"); 59 | } 60 | 61 | return Page(); 62 | } 63 | 64 | public async Task OnPostAsync() 65 | { 66 | if (!ModelState.IsValid) 67 | { 68 | return Page(); 69 | } 70 | 71 | var user = await _userManager.GetUserAsync(User); 72 | if (user == null) 73 | { 74 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 75 | } 76 | 77 | var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword); 78 | if (!addPasswordResult.Succeeded) 79 | { 80 | foreach (var error in addPasswordResult.Errors) 81 | { 82 | ModelState.AddModelError(string.Empty, error.Description); 83 | } 84 | return Page(); 85 | } 86 | 87 | await _signInManager.SignInAsync(user, isPersistent: false); 88 | StatusMessage = "Your password has been set."; 89 | 90 | return RedirectToPage(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ShowRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ShowRecoveryCodesModel 3 | @{ 4 | ViewData["Title"] = "Recovery codes"; 5 | ViewData["ActivePage"] = "TwoFactorAuthentication"; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 18 |
19 |
20 | @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) 21 | { 22 | @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
23 | } 24 |
25 |
-------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace TripTracker.UI.Pages.Account.Manage 9 | { 10 | public class ShowRecoveryCodesModel : PageModel 11 | { 12 | public string[] RecoveryCodes { get; private set; } 13 | 14 | public IActionResult OnGet() 15 | { 16 | RecoveryCodes = (string[])TempData["RecoveryCodes"]; 17 | if (RecoveryCodes == null) 18 | { 19 | return RedirectToPage("TwoFactorAuthentication"); 20 | } 21 | 22 | return Page(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/TwoFactorAuthentication.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TwoFactorAuthenticationModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication (2FA)"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | @if (Model.Is2faEnabled) 9 | { 10 | if (Model.RecoveryCodesLeft == 0) 11 | { 12 |
13 | You have no recovery codes left. 14 |

You must generate a new set of recovery codes before you can log in with a recovery code.

15 |
16 | } 17 | else if (Model.RecoveryCodesLeft == 1) 18 | { 19 |
20 | You have 1 recovery code left. 21 |

You can generate a new set of recovery codes.

22 |
23 | } 24 | else if (Model.RecoveryCodesLeft <= 3) 25 | { 26 |
27 | You have @Model.RecoveryCodesLeft recovery codes left. 28 |

You should generate a new set of recovery codes.

29 |
30 | } 31 | 32 | Disable 2FA 33 | Reset recovery codes 34 | } 35 | 36 |
Authenticator app
37 | @if (!Model.HasAuthenticator) 38 | { 39 | Add authenticator app 40 | } 41 | else 42 | { 43 | Configure authenticator app 44 | Reset authenticator app 45 | } 46 | 47 | @section Scripts { 48 | @await Html.PartialAsync("_ValidationScriptsPartial") 49 | } 50 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.Extensions.Logging; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account.Manage 12 | { 13 | public class TwoFactorAuthenticationModel : PageModel 14 | { 15 | private const string AuthenicatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}"; 16 | 17 | private readonly UserManager _userManager; 18 | private readonly SignInManager _signInManager; 19 | private readonly ILogger _logger; 20 | 21 | public TwoFactorAuthenticationModel( 22 | UserManager userManager, 23 | SignInManager signInManager, 24 | ILogger logger) 25 | { 26 | _userManager = userManager; 27 | _signInManager = signInManager; 28 | _logger = logger; 29 | } 30 | 31 | public bool HasAuthenticator { get; set; } 32 | 33 | public int RecoveryCodesLeft { get; set; } 34 | 35 | [BindProperty] 36 | public bool Is2faEnabled { get; set; } 37 | 38 | public async Task OnGet() 39 | { 40 | var user = await _userManager.GetUserAsync(User); 41 | if (user == null) 42 | { 43 | throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 44 | } 45 | 46 | HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null; 47 | Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user); 48 | RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user); 49 | 50 | return Page(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | @await Html.PartialAsync("_ManageNav") 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @inject SignInManager SignInManager 2 | @{ 3 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 4 | } 5 | 6 | 15 | 16 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using TripTracker.UI.Pages.Account.Manage 2 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model RegisterModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | 9 |
10 |
11 |
12 |

Create a new account.

13 |
14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | @section Scripts { 36 | @await Html.PartialAsync("_ValidationScriptsPartial") 37 | } 38 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/Register.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using Microsoft.Extensions.Logging; 10 | using TripTracker.UI.Data; 11 | using TripTracker.UI.Services; 12 | 13 | namespace TripTracker.UI.Pages.Account 14 | { 15 | public class RegisterModel : PageModel 16 | { 17 | private readonly SignInManager _signInManager; 18 | private readonly UserManager _userManager; 19 | private readonly ILogger _logger; 20 | private readonly IEmailSender _emailSender; 21 | 22 | public RegisterModel( 23 | UserManager userManager, 24 | SignInManager signInManager, 25 | ILogger logger, 26 | IEmailSender emailSender) 27 | { 28 | _userManager = userManager; 29 | _signInManager = signInManager; 30 | _logger = logger; 31 | _emailSender = emailSender; 32 | } 33 | 34 | [BindProperty] 35 | public InputModel Input { get; set; } 36 | 37 | public string ReturnUrl { get; set; } 38 | 39 | public class InputModel 40 | { 41 | [Required] 42 | [EmailAddress] 43 | [Display(Name = "Email")] 44 | public string Email { get; set; } 45 | 46 | [Required] 47 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 48 | [DataType(DataType.Password)] 49 | [Display(Name = "Password")] 50 | public string Password { get; set; } 51 | 52 | [DataType(DataType.Password)] 53 | [Display(Name = "Confirm password")] 54 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 55 | public string ConfirmPassword { get; set; } 56 | } 57 | 58 | public void OnGet(string returnUrl = null) 59 | { 60 | ReturnUrl = returnUrl; 61 | } 62 | 63 | public async Task OnPostAsync(string returnUrl = null) 64 | { 65 | ReturnUrl = returnUrl; 66 | if (ModelState.IsValid) 67 | { 68 | var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email }; 69 | var result = await _userManager.CreateAsync(user, Input.Password); 70 | if (result.Succeeded) 71 | { 72 | _logger.LogInformation("User created a new account with password."); 73 | 74 | var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); 75 | var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme); 76 | await _emailSender.SendEmailConfirmationAsync(Input.Email, callbackUrl); 77 | 78 | await _signInManager.SignInAsync(user, isPersistent: false); 79 | return LocalRedirect(Url.GetLocalUrl(returnUrl)); 80 | } 81 | foreach (var error in result.Errors) 82 | { 83 | ModelState.AddModelError(string.Empty, error.Description); 84 | } 85 | } 86 | 87 | // If we got this far, something failed, redisplay form 88 | return Page(); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Reset password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Reset your password.

9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | @section Scripts { 36 | @await Html.PartialAsync("_ValidationScriptsPartial") 37 | } 38 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ResetPassword.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.AspNetCore.Mvc.RazorPages; 9 | using TripTracker.UI.Data; 10 | 11 | namespace TripTracker.UI.Pages.Account 12 | { 13 | public class ResetPasswordModel : PageModel 14 | { 15 | private readonly UserManager _userManager; 16 | 17 | public ResetPasswordModel(UserManager userManager) 18 | { 19 | _userManager = userManager; 20 | } 21 | 22 | [BindProperty] 23 | public InputModel Input { get; set; } 24 | 25 | public class InputModel 26 | { 27 | [Required] 28 | [EmailAddress] 29 | public string Email { get; set; } 30 | 31 | [Required] 32 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 33 | [DataType(DataType.Password)] 34 | public string Password { get; set; } 35 | 36 | [DataType(DataType.Password)] 37 | [Display(Name = "Confirm password")] 38 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 39 | public string ConfirmPassword { get; set; } 40 | 41 | public string Code { get; set; } 42 | } 43 | 44 | public IActionResult OnGet(string code = null) 45 | { 46 | if (code == null) 47 | { 48 | throw new ApplicationException("A code must be supplied for password reset."); 49 | } 50 | else 51 | { 52 | Input = new InputModel 53 | { 54 | Code = code 55 | }; 56 | return Page(); 57 | } 58 | } 59 | 60 | public async Task OnPostAsync() 61 | { 62 | if (!ModelState.IsValid) 63 | { 64 | return Page(); 65 | } 66 | 67 | var user = await _userManager.FindByEmailAsync(Input.Email); 68 | if (user == null) 69 | { 70 | // Don't reveal that the user does not exist 71 | return RedirectToPage("./ResetPasswordConfirmation"); 72 | } 73 | 74 | var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password); 75 | if (result.Succeeded) 76 | { 77 | return RedirectToPage("./ResetPasswordConfirmation"); 78 | } 79 | 80 | foreach (var error in result.Errors) 81 | { 82 | ModelState.AddModelError(string.Empty, error.Description); 83 | } 84 | return Page(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordConfirmationModel 3 | @{ 4 | ViewData["Title"] = "Reset password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Your password has been reset. Please click here to log in. 10 |

11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/ResetPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace TripTracker.UI.Pages.Account 8 | { 9 | public class ResetPasswordConfirmationModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/SignedOut.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SignedOutModel 3 | @{ 4 | ViewData["Title"] = "Signed out"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | You have successfully signed out. 10 |

11 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/SignedOut.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | public class SignedOutModel : PageModel 9 | { 10 | public IActionResult OnGet() 11 | { 12 | if (User.Identity.IsAuthenticated) 13 | { 14 | // Redirect to home page if the user is authenticated. 15 | return RedirectToPage("/Index"); 16 | } 17 | 18 | return Page(); 19 | } 20 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using TripTracker.UI.Pages.Account 2 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Components/EditTrip/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model TripTracker.BackService.Models.Trip 2 | 3 | @if (Model != null) 4 | { 5 | 6 | } 7 | 8 |
9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
-------------------------------------------------------------------------------- /TripTracker.UI/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 | -------------------------------------------------------------------------------- /TripTracker.UI/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.RazorPages; 7 | 8 | namespace TripTracker.UI.Pages 9 | { 10 | public class ErrorModel : PageModel 11 | { 12 | public string RequestId { get; set; } 13 | 14 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 15 | 16 | public void OnGet() 17 | { 18 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model IndexModel 3 | @{ 4 | ViewData["Title"] = "Home page"; 5 | } 6 | 7 | 69 | 70 | 109 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace TripTracker.UI.Pages 9 | { 10 | public class IndexModel : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Create.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TripTracker.UI.Pages.Trips.CreateModel 3 | 4 | @{ 5 | ViewData["Title"] = "Create"; 6 | } 7 | 8 |

Create

9 | 10 |

Trip

11 |
12 |
13 |
14 |
15 |
16 | 17 | @await Component.InvokeAsync("EditTrip") 18 | 19 |
20 | 21 |
22 |
23 |
24 |
25 | 26 |
27 | Back to List 28 |
29 | 30 | @section Scripts { 31 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 32 | } 33 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Create.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.AspNetCore.Mvc.Rendering; 9 | using TripTracker.BackService.Models; 10 | using TripTracker.UI.Data; 11 | using TripTracker.UI.Services; 12 | 13 | namespace TripTracker.UI.Pages.Trips 14 | { 15 | 16 | [Authorize] 17 | public class CreateModel : PageModel 18 | { 19 | private readonly IApiClient _client; 20 | 21 | public CreateModel(IApiClient client) 22 | { 23 | _client = client; 24 | } 25 | 26 | public IActionResult OnGet() 27 | { 28 | return Page(); 29 | } 30 | 31 | [BindProperty] 32 | public Trip Trip { get; set; } 33 | 34 | public async Task OnPostAsync() 35 | { 36 | if (!ModelState.IsValid) 37 | { 38 | return Page(); 39 | } 40 | 41 | await _client.AddTripAsync(Trip); 42 | 43 | return RedirectToPage("./Index"); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Delete.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TripTracker.UI.Pages.Trips.DeleteModel 3 | 4 | @{ 5 | ViewData["Title"] = "Delete"; 6 | } 7 | 8 |

Delete

9 | 10 |

Are you sure you want to delete this?

11 |
12 |

Trip

13 |
14 |
15 |
16 | @Html.DisplayNameFor(model => model.Trip.Name) 17 |
18 |
19 | @Html.DisplayFor(model => model.Trip.Name) 20 |
21 |
22 | @Html.DisplayNameFor(model => model.Trip.StartDate) 23 |
24 |
25 | @Html.DisplayFor(model => model.Trip.StartDate) 26 |
27 |
28 | @Html.DisplayNameFor(model => model.Trip.EndDate) 29 |
30 |
31 | @Html.DisplayFor(model => model.Trip.EndDate) 32 |
33 |
34 | 35 |
36 | 37 | | 38 | Back to List 39 |
40 |
41 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Delete.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.EntityFrameworkCore; 9 | using TripTracker.BackService.Models; 10 | using TripTracker.UI.Data; 11 | using TripTracker.UI.Services; 12 | 13 | 14 | namespace TripTracker.UI.Pages.Trips 15 | { 16 | 17 | [Authorize] 18 | public class DeleteModel : PageModel 19 | { 20 | private readonly IApiClient _Client; 21 | 22 | public DeleteModel(IApiClient client) 23 | { 24 | _Client = client; 25 | } 26 | 27 | [BindProperty] 28 | public Trip Trip { get; set; } 29 | 30 | public async Task OnGetAsync(int? id) 31 | { 32 | if (id == null) 33 | { 34 | return NotFound(); 35 | } 36 | 37 | Trip = await _Client.GetTripAsync(id.Value); 38 | 39 | if (Trip == null) 40 | { 41 | return NotFound(); 42 | } 43 | return Page(); 44 | } 45 | 46 | public async Task OnPostAsync(int? id) 47 | { 48 | if (id == null) 49 | { 50 | return NotFound(); 51 | } 52 | 53 | Trip = await _Client.GetTripAsync(id.Value); 54 | 55 | if (Trip != null) 56 | { 57 | await _Client.RemoveTripAsync(id.Value); 58 | } 59 | 60 | return RedirectToPage("./Index"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Details.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TripTracker.UI.Pages.Trips.DetailsModel 3 | 4 | @{ 5 | ViewData["Title"] = "Details"; 6 | } 7 | 8 |

Details

9 | 10 |
11 |

Trip

12 |
13 |
14 |
15 | @Html.DisplayNameFor(model => model.Trip.Name) 16 |
17 |
18 | @Html.DisplayFor(model => model.Trip.Name) 19 |
20 |
21 | @Html.DisplayNameFor(model => model.Trip.StartDate) 22 |
23 |
24 | @Html.DisplayFor(model => model.Trip.StartDate) 25 |
26 |
27 | @Html.DisplayNameFor(model => model.Trip.EndDate) 28 |
29 |
30 | @Html.DisplayFor(model => model.Trip.EndDate) 31 |
32 |
33 |
34 |
35 | Edit | 36 | Back to List 37 |
38 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Details.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.EntityFrameworkCore; 8 | using TripTracker.BackService.Models; 9 | using TripTracker.UI.Data; 10 | using TripTracker.UI.Services; 11 | 12 | namespace TripTracker.UI.Pages.Trips 13 | { 14 | public class DetailsModel : PageModel 15 | { 16 | private readonly IApiClient _Client; 17 | 18 | public DetailsModel(IApiClient client) 19 | { 20 | _Client = client; 21 | } 22 | 23 | public Trip Trip { get; set; } 24 | 25 | public async Task OnGetAsync(int? id) 26 | { 27 | if (id == null) 28 | { 29 | return NotFound(); 30 | } 31 | 32 | Trip = await _Client.GetTripAsync(id.Value); 33 | 34 | if (Trip == null) 35 | { 36 | return NotFound(); 37 | } 38 | return Page(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TripTracker.UI.Pages.Trips.EditModel 3 | 4 | @{ 5 | ViewData["Title"] = "Edit"; 6 | } 7 | 8 |

Edit

9 | 10 |

Trip

11 |
12 |
13 |
14 |
15 |
16 | 17 | @await Component.InvokeAsync("EditTrip", Model.Trip) 18 | 19 |
20 | 21 |
22 |
23 |
24 |
25 | 26 |
27 | Back to List 28 |
29 | 30 | @section Scripts { 31 | @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} 32 | } 33 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Edit.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.AspNetCore.Mvc.Rendering; 9 | using Microsoft.EntityFrameworkCore; 10 | using TripTracker.BackService.Models; 11 | using TripTracker.UI.Data; 12 | using TripTracker.UI.Services; 13 | 14 | namespace TripTracker.UI.Pages.Trips 15 | { 16 | 17 | [Authorize] 18 | public class EditModel : PageModel 19 | { 20 | private readonly IApiClient _client; 21 | 22 | public EditModel(IApiClient client) 23 | { 24 | _client = client; 25 | } 26 | 27 | [BindProperty] 28 | public Trip Trip { get; set; } 29 | 30 | public async Task OnGetAsync(int? id) 31 | { 32 | if (id == null) 33 | { 34 | return NotFound(); 35 | } 36 | 37 | Trip = await _client.GetTripAsync(id.Value); 38 | 39 | if (Trip == null) 40 | { 41 | return NotFound(); 42 | } 43 | return Page(); 44 | } 45 | 46 | public async Task OnPostAsync() 47 | { 48 | if (!ModelState.IsValid) 49 | { 50 | return Page(); 51 | } 52 | 53 | await _client.PutTripAsync(Trip); 54 | 55 | return RedirectToPage("./Index"); 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Index.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model TripTracker.UI.Pages.Trips.IndexModel 3 | @inject SignInManager SignInManager 4 | @inject Microsoft.AspNetCore.Authorization.IAuthorizationService Auth 5 | @using Microsoft.AspNetCore.Authorization 6 | 7 | @{ 8 | ViewData["Title"] = "Index"; 9 | } 10 | 11 |

Index

12 | 13 | @* await Auth.AuthorizeAsync(User, null, "CreateTrips") == AuthorizationResult.Success()) //( *@ 14 | 15 | @if ((await Auth.AuthorizeAsync(User, null, "CreateTrips")).Succeeded) // (SignInManager.IsSignedIn(User)) 16 | { 17 |

18 | Create New 19 |

20 | } 21 | 22 | 23 | 24 | 27 | 30 | 33 | 34 | 35 | 36 | 37 | @foreach (var item in Model.Trips) 38 | { 39 | 40 | 43 | 46 | 49 | 60 | 61 | } 62 | 63 |
25 | @Html.DisplayNameFor(model => model.Trips[0].Name) 26 | 28 | @Html.DisplayNameFor(model => model.Trips[0].StartDate) 29 | 31 | @Html.DisplayNameFor(model => model.Trips[0].EndDate) 32 |
41 | @Html.DisplayFor(modelItem => item.Name) 42 | 44 | @Html.DisplayFor(modelItem => item.StartDate) 45 | 47 | @Html.DisplayFor(modelItem => item.EndDate) 48 | 50 | @if (SignInManager.IsSignedIn(User)) 51 | { 52 | Edit @:| 53 | } 54 | Details @if (SignInManager.IsSignedIn(User)) 55 | {@:| 56 | Delete 57 | 58 | } 59 |
64 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/Trips/Index.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.EntityFrameworkCore; 8 | using TripTracker.BackService.Models; 9 | using TripTracker.UI.Data; 10 | using TripTracker.UI.Services; 11 | 12 | namespace TripTracker.UI.Pages.Trips 13 | { 14 | public class IndexModel : PageModel 15 | { 16 | private readonly IApiClient _Client; 17 | 18 | public IndexModel(IApiClient client) 19 | { 20 | _Client = client; 21 | } 22 | 23 | public IList Trips { get; set; } 24 | 25 | public async Task OnGetAsync() 26 | { 27 | Trips = await _Client.GetTripsAsync(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - TripTracker.UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 42 |
43 | @RenderBody() 44 |
45 |
46 |

© 2017 - TripTracker.UI

47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 68 | 69 | 70 | 71 | @RenderSection("Scripts", required: false) 72 | 73 | 74 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using TripTracker.UI.Data 3 | @inject SignInManager SignInManager 4 | @inject UserManager UserManager 5 | 6 | @if (SignInManager.IsSignedIn(User)) 7 | { 8 | 18 | } 19 | else 20 | { 21 | 25 | } 26 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /TripTracker.UI/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using TripTracker.UI 3 | @using TripTracker.UI.Data 4 | @using TripTracker.UI.ViewComponents 5 | @namespace TripTracker.UI.Pages 6 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 7 | @addTagHelper *, TripTracker.UI -------------------------------------------------------------------------------- /TripTracker.UI/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /TripTracker.UI/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 TripTracker.UI 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TripTracker.UI/Security.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace TripTracker.UI 9 | { 10 | public class Security 11 | { 12 | 13 | public static void Configure(RazorPagesOptions options) 14 | { 15 | options.Conventions.AuthorizeFolder("/Account/Manage"); 16 | options.Conventions.AuthorizePage("/Account/Logout"); 17 | 18 | } 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TripTracker.UI/Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TripTracker.UI.Services 7 | { 8 | // This class is used by the application to send email for account confirmation and password reset. 9 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 10 | public class EmailSender : IEmailSender 11 | { 12 | public Task SendEmailAsync(string email, string subject, string message) 13 | { 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /TripTracker.UI/Services/IApiClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using TripTracker.BackService.Models; 7 | 8 | namespace TripTracker.UI.Services 9 | { 10 | public interface IApiClient 11 | { 12 | 13 | Task> GetTripsAsync(); 14 | Task GetTripAsync(int id); 15 | Task PutTripAsync(Trip tripToUpdate); 16 | Task AddTripAsync(Trip tripToAdd); 17 | Task RemoveTripAsync(int id); 18 | 19 | } 20 | 21 | public class ApiClient : IApiClient 22 | { 23 | 24 | private readonly HttpClient _HttpClient; 25 | 26 | public ApiClient(HttpClient httpClient) 27 | { 28 | _HttpClient = httpClient; 29 | } 30 | 31 | public async Task AddTripAsync(Trip tripToAdd) 32 | { 33 | 34 | var response = await _HttpClient.PostJsonAsync("/api/Trips", tripToAdd); 35 | 36 | response.EnsureSuccessStatusCode(); 37 | 38 | 39 | } 40 | 41 | public async Task GetTripAsync(int id) 42 | { 43 | var response = await _HttpClient.GetAsync($"/api/Trips/{id}"); 44 | 45 | response.EnsureSuccessStatusCode(); 46 | 47 | return await response.Content.ReadAsJsonAsync(); 48 | 49 | } 50 | 51 | public async Task> GetTripsAsync() 52 | { 53 | 54 | var response = await _HttpClient.GetAsync("/api/Trips"); 55 | 56 | response.EnsureSuccessStatusCode(); 57 | 58 | return await response.Content.ReadAsJsonAsync>(); 59 | 60 | } 61 | 62 | public async Task PutTripAsync(Trip tripToUpdate) 63 | { 64 | 65 | var response = await _HttpClient.PutJsonAsync($"/api/Trips/{tripToUpdate.Id}", tripToUpdate); 66 | 67 | response.EnsureSuccessStatusCode(); 68 | 69 | } 70 | 71 | public async Task RemoveTripAsync(int id) 72 | { 73 | 74 | var response = await _HttpClient.DeleteAsync($"/api/Trips/{id}"); 75 | 76 | response.EnsureSuccessStatusCode(); 77 | 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /TripTracker.UI/Services/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TripTracker.UI.Services 7 | { 8 | public interface IEmailSender 9 | { 10 | Task SendEmailAsync(string email, string subject, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TripTracker.UI/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.Identity; 7 | using Microsoft.AspNetCore.Http; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | using TripTracker.UI.Data; 13 | using TripTracker.UI.Services; 14 | using System.Net.Http; 15 | 16 | namespace TripTracker.UI 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.AddDbContext(options => 31 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 32 | 33 | services.AddIdentity() 34 | .AddEntityFrameworkStores() 35 | .AddDefaultTokenProviders(); 36 | 37 | #region API Client Configuration 38 | 39 | services.AddScoped(_ => 40 | new HttpClient 41 | { 42 | BaseAddress = new Uri(Configuration["serviceUrl"]) 43 | }); 44 | services.AddScoped(); 45 | 46 | #endregion 47 | 48 | services.AddMvc() 49 | .AddRazorPagesOptions(Security.Configure); 50 | 51 | services.AddAuthorization(configure => 52 | { 53 | 54 | configure.AddPolicy("CreateTrips", policy => 55 | { 56 | policy.RequireAuthenticatedUser() 57 | .Build(); 58 | }); 59 | 60 | }); 61 | 62 | // Register no-op EmailSender used by account confirmation and password reset during development 63 | // For more information on how to enable account confirmation and password reset please visit https://go.microsoft.com/fwlink/?LinkID=532713 64 | services.AddSingleton(); 65 | } 66 | 67 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 68 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 69 | { 70 | if (env.IsDevelopment()) 71 | { 72 | app.UseBrowserLink(); 73 | app.UseDeveloperExceptionPage(); 74 | app.UseDatabaseErrorPage(); 75 | } 76 | else 77 | { 78 | app.UseExceptionHandler("/Error"); 79 | } 80 | 81 | app.UseStaticFiles(); 82 | 83 | app.UseAuthentication(); 84 | 85 | app.UseMvcWithDefaultRoute(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /TripTracker.UI/TagHelpers/DangerTagHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; 6 | using Microsoft.AspNetCore.Razor.TagHelpers; 7 | 8 | namespace TripTracker.UI.TagHelpers 9 | { 10 | // You may need to install the Microsoft.AspNetCore.Razor.Runtime package into your project 11 | [HtmlTargetElement(Attributes = "danger")] 12 | public class DangerTagHelper : TagHelper 13 | { 14 | public override void Process(TagHelperContext context, TagHelperOutput output) 15 | { 16 | 17 | var cssDangerClassName = "text-danger"; 18 | 19 | if (context.AllAttributes.ContainsName("class")) 20 | { 21 | output.Attributes.SetAttribute("class", string.Concat(output.Attributes["class"].Value, " ", cssDangerClassName)); 22 | } else 23 | { 24 | output.Attributes.SetAttribute("class", cssDangerClassName); 25 | } 26 | 27 | output.Attributes.Remove(output.Attributes["danger"]); 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /TripTracker.UI/TripTracker.UI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netcoreapp2.1 4 | aspnet-TripTracker.UI-294C2A07-8F58-4C42-9A51-DC41CEB8A36E 5 | ..\docker-compose.dcproj 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /TripTracker.UI/ViewComponents/EditViewComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using TripTracker.BackService.Models; 7 | 8 | namespace TripTracker.UI.ViewComponents 9 | { 10 | public class EditTripViewComponent : ViewComponent 11 | { 12 | 13 | public async Task InvokeAsync(Trip trip) 14 | { 15 | 16 | 17 | await Task.Delay(0); 18 | return View("Edit", trip); 19 | 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /TripTracker.UI/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /TripTracker.UI/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "serviceUrl": "http://localhost:61174/", 3 | "ConnectionStrings": { 4 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-TripTracker.UI-53bc9b9d-9d6a-45d4-8429-2a2761773502;Trusted_Connection=True;MultipleActiveResultSets=true" 5 | }, 6 | "Logging": { 7 | "IncludeScopes": false, 8 | "LogLevel": { 9 | "Default": "Warning" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /TripTracker.UI/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/css/site.min.css", 6 | // An array of relative input file paths. Globbing patterns supported 7 | "inputFiles": [ 8 | "wwwroot/css/site.css" 9 | ] 10 | }, 11 | { 12 | "outputFileName": "wwwroot/js/site.min.js", 13 | "inputFiles": [ 14 | "wwwroot/js/site.js" 15 | ], 16 | // Optionally specify minification options 17 | "minify": { 18 | "enabled": true, 19 | "renameLocals": true 20 | }, 21 | // Optionally generate .map file 22 | "sourceMap": false 23 | } 24 | ] 25 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Carousel */ 14 | .carousel-caption p { 15 | font-size: 20px; 16 | line-height: 1.4; 17 | } 18 | 19 | /* Make .svg files in the carousel display properly in older browsers */ 20 | .carousel-inner .item img[src$=".svg"] { 21 | width: 100%; 22 | } 23 | 24 | /* QR code generator */ 25 | #qrCode { 26 | margin: 15px; 27 | } 28 | 29 | /* Hide/rearrange for smaller screens */ 30 | @media screen and (max-width: 767px) { 31 | /* Hide captions */ 32 | .carousel-caption { 33 | display: none; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/favicon.ico -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/images/banner1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/images/banner2.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 3" 33 | }, 34 | "version": "3.3.7", 35 | "_release": "3.3.7", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.7", 39 | "commit": "0b9c4a4007c44201dce9a6cc1a38407005c26c86" 40 | }, 41 | "_source": "https://github.com/twbs/bootstrap.git", 42 | "_target": "v3.3.7", 43 | "_originalSource": "bootstrap", 44 | "_direct": true 45 | } -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Twitter, Inc. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/TripTracker/df84f704bde8bb880475f8c7d16e575c0d518398/TripTracker.UI/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "version": "3.2.6", 4 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 5 | "description": "Add-on to jQuery Validation to enable unobtrusive validation options in data-* attributes.", 6 | "main": [ 7 | "jquery.validate.unobtrusive.js" 8 | ], 9 | "ignore": [ 10 | "**/.*", 11 | "*.json", 12 | "*.md", 13 | "*.txt", 14 | "gulpfile.js" 15 | ], 16 | "keywords": [ 17 | "jquery", 18 | "asp.net", 19 | "mvc", 20 | "validation", 21 | "unobtrusive" 22 | ], 23 | "authors": [ 24 | "Microsoft" 25 | ], 26 | "license": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/aspnet/jquery-validation-unobtrusive.git" 30 | }, 31 | "dependencies": { 32 | "jquery-validation": ">=1.8", 33 | "jquery": ">=1.8" 34 | }, 35 | "_release": "3.2.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.2.6", 39 | "commit": "13386cd1b5947d8a5d23a12b531ce3960be1eba7" 40 | }, 41 | "_source": "git://github.com/aspnet/jquery-validation-unobtrusive.git", 42 | "_target": "3.2.6", 43 | "_originalSource": "jquery-validation-unobtrusive" 44 | } -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ** Unobtrusive validation support library for jQuery and jQuery Validate 3 | ** Copyright (C) Microsoft Corporation. All rights reserved. 4 | */ 5 | !function(a){function e(a,e,n){a.rules[e]=n,a.message&&(a.messages[e]=a.message)}function n(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function t(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function r(a){return a.substr(0,a.lastIndexOf(".")+1)}function i(a,e){return 0===a.indexOf("*.")&&(a=a.replace("*.",e)),a}function o(e,n){var r=a(this).find("[data-valmsg-for='"+t(n[0].name)+"']"),i=r.attr("data-valmsg-replace"),o=i?a.parseJSON(i)!==!1:null;r.removeClass("field-validation-valid").addClass("field-validation-error"),e.data("unobtrusiveContainer",r),o?(r.empty(),e.removeClass("input-validation-error").appendTo(r)):e.hide()}function d(e,n){var t=a(this).find("[data-valmsg-summary=true]"),r=t.find("ul");r&&r.length&&n.errorList.length&&(r.empty(),t.addClass("validation-summary-errors").removeClass("validation-summary-valid"),a.each(n.errorList,function(){a("
  • ").html(this.message).appendTo(r)}))}function s(e){var n=e.data("unobtrusiveContainer");if(n){var t=n.attr("data-valmsg-replace"),r=t?a.parseJSON(t):null;n.addClass("field-validation-valid").removeClass("field-validation-error"),e.removeData("unobtrusiveContainer"),r&&n.empty()}}function l(e){var n=a(this),t="__jquery_unobtrusive_validation_form_reset";if(!n.data(t)){n.data(t,!0);try{n.data("validator").resetForm()}finally{n.removeData(t)}n.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors"),n.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}}function m(e){var n=a(e),t=n.data(v),r=a.proxy(l,e),i=p.unobtrusive.options||{},m=function(n,t){var r=i[n];r&&a.isFunction(r)&&r.apply(e,t)};return t||(t={options:{errorClass:i.errorClass||"input-validation-error",errorElement:i.errorElement||"span",errorPlacement:function(){o.apply(e,arguments),m("errorPlacement",arguments)},invalidHandler:function(){d.apply(e,arguments),m("invalidHandler",arguments)},messages:{},rules:{},success:function(){s.apply(e,arguments),m("success",arguments)}},attachValidation:function(){n.off("reset."+v,r).on("reset."+v,r).validate(this.options)},validate:function(){return n.validate(),n.valid()}},n.data(v,t)),t}var u,p=a.validator,v="unobtrusiveValidation";p.unobtrusive={adapters:[],parseElement:function(e,n){var t,r,i,o=a(e),d=o.parents("form")[0];d&&(t=m(d),t.options.rules[e.name]=r={},t.options.messages[e.name]=i={},a.each(this.adapters,function(){var n="data-val-"+this.name,t=o.attr(n),s={};void 0!==t&&(n+="-",a.each(this.params,function(){s[this]=o.attr(n+this)}),this.adapt({element:e,form:d,message:t,params:s,rules:r,messages:i}))}),a.extend(r,{__dummy__:!0}),n||t.attachValidation())},parse:function(e){var n=a(e),t=n.parents().addBack().filter("form").add(n.find("form")).has("[data-val=true]");n.find("[data-val=true]").each(function(){p.unobtrusive.parseElement(this,!0)}),t.each(function(){var a=m(this);a&&a.attachValidation()})}},u=p.unobtrusive.adapters,u.add=function(a,e,n){return n||(n=e,e=[]),this.push({name:a,params:e,adapt:n}),this},u.addBool=function(a,n){return this.add(a,function(t){e(t,n||a,!0)})},u.addMinMax=function(a,n,t,r,i,o){return this.add(a,[i||"min",o||"max"],function(a){var i=a.params.min,o=a.params.max;i&&o?e(a,r,[i,o]):i?e(a,n,i):o&&e(a,t,o)})},u.addSingleVal=function(a,n,t){return this.add(a,[n||"val"],function(r){e(r,t||a,r.params[n])})},p.addMethod("__dummy__",function(a,e,n){return!0}),p.addMethod("regex",function(a,e,n){var t;return this.optional(e)?!0:(t=new RegExp(n).exec(a),t&&0===t.index&&t[0].length===a.length)}),p.addMethod("nonalphamin",function(a,e,n){var t;return n&&(t=a.match(/\W/g),t=t&&t.length>=n),t}),p.methods.extension?(u.addSingleVal("accept","mimtype"),u.addSingleVal("extension","extension")):u.addSingleVal("extension","extension","accept"),u.addSingleVal("regex","pattern"),u.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"),u.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range"),u.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength"),u.add("equalto",["other"],function(n){var o=r(n.element.name),d=n.params.other,s=i(d,o),l=a(n.form).find(":input").filter("[name='"+t(s)+"']")[0];e(n,"equalTo",l)}),u.add("required",function(a){("INPUT"!==a.element.tagName.toUpperCase()||"CHECKBOX"!==a.element.type.toUpperCase())&&e(a,"required",!0)}),u.add("remote",["url","type","additionalfields"],function(o){var d={url:o.params.url,type:o.params.type||"GET",data:{}},s=r(o.element.name);a.each(n(o.params.additionalfields||o.element.name),function(e,n){var r=i(n,s);d.data[r]=function(){var e=a(o.form).find(":input").filter("[name='"+t(r)+"']");return e.is(":checkbox")?e.filter(":checked").val()||e.filter(":hidden").val()||"":e.is(":radio")?e.filter(":checked").val()||"":e.val()}}),e(o,"remote",d)}),u.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&e(a,"minlength",a.params.min),a.params.nonalphamin&&e(a,"nonalphamin",a.params.nonalphamin),a.params.regex&&e(a,"regex",a.params.regex)}),a(function(){p.unobtrusive.parse(document)})}(jQuery); -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "http://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jzaefferer/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.14.0", 31 | "_release": "1.14.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.14.0", 35 | "commit": "c1343fb9823392aa9acbe1c3ffd337b8c92fed48" 36 | }, 37 | "_source": "git://github.com/jzaefferer/jquery-validation.git", 38 | "_target": ">=1.8", 39 | "_originalSource": "jquery-validation" 40 | } -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "2.2.0", 16 | "_release": "2.2.0", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "2.2.0", 20 | "commit": "6fc01e29bdad0964f62ef56d01297039cdcadbe5" 21 | }, 22 | "_source": "git://github.com/jquery/jquery-dist.git", 23 | "_target": "2.2.0", 24 | "_originalSource": "jquery" 25 | } -------------------------------------------------------------------------------- /TripTracker.UI/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /TripTrackerLive.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27413.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TripTracker.BackService", "TripTracker.BackService\TripTracker.BackService.csproj", "{D5C3E1BD-FAA0-4C01-A653-2D2E2211214B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TripTracker.UI", "TripTracker.UI\TripTracker.UI.csproj", "{8BD88936-8ABA-44E9-A312-8DBDC910CB03}" 9 | EndProject 10 | Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{1AE91BE0-6F92-421F-BE2E-CB9903AA1C51}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {D5C3E1BD-FAA0-4C01-A653-2D2E2211214B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {D5C3E1BD-FAA0-4C01-A653-2D2E2211214B}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {D5C3E1BD-FAA0-4C01-A653-2D2E2211214B}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {D5C3E1BD-FAA0-4C01-A653-2D2E2211214B}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {8BD88936-8ABA-44E9-A312-8DBDC910CB03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {8BD88936-8ABA-44E9-A312-8DBDC910CB03}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {8BD88936-8ABA-44E9-A312-8DBDC910CB03}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {8BD88936-8ABA-44E9-A312-8DBDC910CB03}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {1AE91BE0-6F92-421F-BE2E-CB9903AA1C51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {1AE91BE0-6F92-421F-BE2E-CB9903AA1C51}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {1AE91BE0-6F92-421F-BE2E-CB9903AA1C51}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {1AE91BE0-6F92-421F-BE2E-CB9903AA1C51}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {0E12C5F5-80DF-4EC3-BCD6-770CB25B88E9} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /docker-compose.dcproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2.0 5 | Linux 6 | 1ae91be0-6f92-421f-be2e-cb9903aa1c51 7 | LaunchBrowser 8 | http://localhost:{ServicePort}/swagger 9 | triptracker.backservice 10 | 11 | 12 | 13 | docker-compose.yml 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | backservice: 5 | environment: 6 | - ASPNETCORE_ENVIRONMENT=Development 7 | ports: 8 | - "5003:80" 9 | 10 | ui: 11 | environment: 12 | ASPNETCORE_ENVIRONMENT: "Development" 13 | ConnectionStrings__DefaultConnection: "Server=db;Database=TripTracker;user=sa;password=TripTracker1234$$;MultipleActiveResultSets=true" 14 | ports: 15 | - "5004:80" 16 | 17 | db: 18 | environment: 19 | SA_PASSWORD: "TripTracker1234$$" 20 | ACCEPT_EULA: "Y" -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | backservice: 5 | image: triptrackerbackservice 6 | build: 7 | context: . 8 | dockerfile: TripTracker.BackService/Dockerfile 9 | 10 | ui: 11 | image: triptrackerui 12 | environment: 13 | - serviceUrl=http://backservice 14 | build: 15 | context: . 16 | dockerfile: TripTracker.UI/Dockerfile 17 | links: 18 | - backservice 19 | 20 | db: 21 | image: "microsoft/mssql-server-linux" --------------------------------------------------------------------------------