From c0237e1433be9fed77bac891b0ca92f147fb87ac Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Tue, 27 Sep 2016 22:19:43 -0500 Subject: [PATCH] Authentication / Authorization Who are you, anyway? --- src/MyPrayerJournal/App.fs | 18 ++++++++++--- src/MyPrayerJournal/Configuration/Keys.fs | 3 +++ .../Controllers/HomeController.fs | 11 +++++++- .../Controllers/UserController.fs | 25 +++++++++++++------ src/MyPrayerJournal/Data/Entities.fs | 10 +++++++- .../Models/ViewModels/AppViewModel.fs | 1 - .../Models/ViewModels/User/LogOnViewModel.fs | 1 - 7 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/MyPrayerJournal/App.fs b/src/MyPrayerJournal/App.fs index 2da040c..3044717 100644 --- a/src/MyPrayerJournal/App.fs +++ b/src/MyPrayerJournal/App.fs @@ -1,6 +1,8 @@ +module MyPrayerJournal.App open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Hosting +open Microsoft.AspNetCore.Http open Microsoft.AspNetCore.Localization open Microsoft.Extensions.Configuration open Microsoft.Extensions.DependencyInjection @@ -31,13 +33,17 @@ type Startup(env : IHostingEnvironment) = ignore <| services.AddLocalization (fun options -> options.ResourcesPath <- "Resources") ignore <| services.AddMvc () ignore <| services.AddDistributedMemoryCache () - ignore <| services.AddSession () // RethinkDB connection async { let cfg = services.BuildServiceProvider().GetService>().Value let! conn = DataConfig.Connect cfg.DataConfig do! conn.EstablishEnvironment cfg ignore <| services.AddSingleton conn + //ignore <| services.AddDistributedRethinkDBCache (fun options -> + // options.Connection <- conn + // options.Database <- match cfg.DataConfig.Database with null -> "" | db -> db + // options.TableName <- "Session") + ignore <| services.AddSession () } |> Async.RunSynchronously // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -52,8 +58,14 @@ type Startup(env : IHostingEnvironment) = ignore <| app.UseStaticFiles () - // Add external authentication middleware below. To configure them please see https://go.microsoft.com/fwlink/?LinkID=532715 - + ignore <| app.UseCookieAuthentication( + CookieAuthenticationOptions( + AuthenticationScheme = Keys.Authentication, + LoginPath = PathString("/user/log-on"), + AutomaticAuthenticate = true, + AutomaticChallenge = true, + ExpireTimeSpan = TimeSpan(2, 0, 0), + SlidingExpiration = true)) ignore <| app.UseMvc(fun routes -> ignore <| routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}")) diff --git a/src/MyPrayerJournal/Configuration/Keys.fs b/src/MyPrayerJournal/Configuration/Keys.fs index 0b2dba0..a7658b1 100644 --- a/src/MyPrayerJournal/Configuration/Keys.fs +++ b/src/MyPrayerJournal/Configuration/Keys.fs @@ -2,6 +2,9 @@ [] module MyPrayerJournal.Keys +/// Instance name for cookie authentication +let Authentication = "mpj-authentication" + /// The current user let CurrentUser = "mpj-user" diff --git a/src/MyPrayerJournal/Controllers/HomeController.fs b/src/MyPrayerJournal/Controllers/HomeController.fs index 17dec6c..00b62a1 100644 --- a/src/MyPrayerJournal/Controllers/HomeController.fs +++ b/src/MyPrayerJournal/Controllers/HomeController.fs @@ -1,12 +1,21 @@ namespace MyPrayerJournal.Controllers +open Microsoft.AspNetCore.Authorization open Microsoft.AspNetCore.Mvc +open MyPrayerJournal open RethinkDb.Driver.Net /// Home controller +[] [] type HomeController(data : IConnection) = inherit ApplicationController(data) + [] [] - member this.Index() = this.View() \ No newline at end of file + member this.Index() = + async { + match this.HttpContext.User with + | :? AppUser as user -> return this.View "Dashboard" :> IActionResult + | _ -> return upcast this.View () + } |> Async.StartAsTask diff --git a/src/MyPrayerJournal/Controllers/UserController.fs b/src/MyPrayerJournal/Controllers/UserController.fs index cceffae..7d42384 100644 --- a/src/MyPrayerJournal/Controllers/UserController.fs +++ b/src/MyPrayerJournal/Controllers/UserController.fs @@ -1,5 +1,6 @@ namespace MyPrayerJournal.Controllers +open Microsoft.AspNetCore.Authorization open Microsoft.AspNetCore.Mvc open Microsoft.Extensions.Options open MyPrayerJournal @@ -7,34 +8,42 @@ open MyPrayerJournal.ViewModels open RethinkDb.Driver.Net /// Controller for all /user URLs +[] [] type UserController(data : IConnection, cfg : IOptions) = inherit ApplicationController(data) + [] [] member this.ShowLogOn () = this.View(LogOnViewModel()) - + [] [] [] member this.DoLogOn (form : LogOnViewModel) = async { let! user = data.LogOnUser form.Email (User.HashPassword form.Password cfg.Value.PasswordSaltBytes) match user with - | Some usr -> (* this.Session.[Keys.User] <- usr + | Some usr -> do! this.HttpContext.Authentication.SignInAsync(Keys.Authentication, AppUser(user)) + // TODO: welcome message + (* this.Session.[Keys.User] <- usr { UserMessage.Empty with Level = Level.Info Message = Strings.get "LogOnSuccess" } - |> model.AddMessage - this.Redirect "" model |> ignore // Save the messages in the session before the Nancy redirect - // TODO: investigate if addMessage should update the session when it's called - return this.LoginAndRedirect (System.Guid.Parse usr.Id, fallbackRedirectUrl = "/") :> obj - *) + |> model.AddMessage *) return this.Redirect "/" :> IActionResult | _ -> (*{ UserMessage.Empty with Level = Level.Error Message = Strings.get "LogOnFailure" } |> model.AddMessage return this.Redirect "/user/log-on" model *) - return upcast this.RedirectToAction("ShowLogOn") + return upcast this.RedirectToAction "ShowLogOn" //return this.View() } |> Async.StartAsTask + + [] + member this.LogOff () = + async { + do! this.HttpContext.Authentication.SignOutAsync(Keys.Authentication) + // TODO: goodbye message + return this.LocalRedirect "/" + } |> Async.StartAsTask \ No newline at end of file diff --git a/src/MyPrayerJournal/Data/Entities.fs b/src/MyPrayerJournal/Data/Entities.fs index 2d68481..38d6c66 100644 --- a/src/MyPrayerJournal/Data/Entities.fs +++ b/src/MyPrayerJournal/Data/Entities.fs @@ -1,6 +1,7 @@ namespace MyPrayerJournal open Newtonsoft.Json +open System.Security.Claims open System.Security.Cryptography /// A user @@ -81,4 +82,11 @@ type Request = { |> List.sortBy (fun item -> -item.AsOf) |> List.map (fun item -> item.AsOf) |> List.head - \ No newline at end of file + +/// The user for use with identity +[] +type AppUser(user : User option) = + inherit ClaimsPrincipal() + + /// The current user + member val User = user with get \ No newline at end of file diff --git a/src/MyPrayerJournal/Models/ViewModels/AppViewModel.fs b/src/MyPrayerJournal/Models/ViewModels/AppViewModel.fs index 543b6e0..45eab8e 100644 --- a/src/MyPrayerJournal/Models/ViewModels/AppViewModel.fs +++ b/src/MyPrayerJournal/Models/ViewModels/AppViewModel.fs @@ -3,7 +3,6 @@ namespace MyPrayerJournal.ViewModels //open MyPrayerJournal /// Parent view model for all myPrayerJournal view models -[] type AppViewModel() = member this.Q = "X" (* diff --git a/src/MyPrayerJournal/Models/ViewModels/User/LogOnViewModel.fs b/src/MyPrayerJournal/Models/ViewModels/User/LogOnViewModel.fs index ab98974..4454e7e 100644 --- a/src/MyPrayerJournal/Models/ViewModels/User/LogOnViewModel.fs +++ b/src/MyPrayerJournal/Models/ViewModels/User/LogOnViewModel.fs @@ -2,7 +2,6 @@ namespace MyPrayerJournal.ViewModels open System.ComponentModel.DataAnnotations -[] type LogOnViewModel() = inherit AppViewModel()