Authentication / Authorization

Who are you, anyway?
This commit is contained in:
Daniel J. Summers 2016-09-27 22:19:43 -05:00
parent 5235e5a5db
commit c0237e1433
7 changed files with 54 additions and 15 deletions

View File

@ -1,6 +1,8 @@
module MyPrayerJournal.App
open Microsoft.AspNetCore.Builder open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Localization open Microsoft.AspNetCore.Localization
open Microsoft.Extensions.Configuration open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.DependencyInjection
@ -31,13 +33,17 @@ type Startup(env : IHostingEnvironment) =
ignore <| services.AddLocalization (fun options -> options.ResourcesPath <- "Resources") ignore <| services.AddLocalization (fun options -> options.ResourcesPath <- "Resources")
ignore <| services.AddMvc () ignore <| services.AddMvc ()
ignore <| services.AddDistributedMemoryCache () ignore <| services.AddDistributedMemoryCache ()
ignore <| services.AddSession ()
// RethinkDB connection // RethinkDB connection
async { async {
let cfg = services.BuildServiceProvider().GetService<IOptions<AppConfig>>().Value let cfg = services.BuildServiceProvider().GetService<IOptions<AppConfig>>().Value
let! conn = DataConfig.Connect cfg.DataConfig let! conn = DataConfig.Connect cfg.DataConfig
do! conn.EstablishEnvironment cfg do! conn.EstablishEnvironment cfg
ignore <| services.AddSingleton conn 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 } |> Async.RunSynchronously
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. // 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 () 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 <| app.UseMvc(fun routes ->
ignore <| routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}")) ignore <| routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}"))

View File

@ -2,6 +2,9 @@
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
module MyPrayerJournal.Keys module MyPrayerJournal.Keys
/// Instance name for cookie authentication
let Authentication = "mpj-authentication"
/// The current user /// The current user
let CurrentUser = "mpj-user" let CurrentUser = "mpj-user"

View File

@ -1,12 +1,21 @@
namespace MyPrayerJournal.Controllers namespace MyPrayerJournal.Controllers
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Mvc open Microsoft.AspNetCore.Mvc
open MyPrayerJournal
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
/// Home controller /// Home controller
[<Authorize>]
[<Route("")>] [<Route("")>]
type HomeController(data : IConnection) = type HomeController(data : IConnection) =
inherit ApplicationController(data) inherit ApplicationController(data)
[<AllowAnonymous>]
[<HttpGet("")>] [<HttpGet("")>]
member this.Index() = this.View() member this.Index() =
async {
match this.HttpContext.User with
| :? AppUser as user -> return this.View "Dashboard" :> IActionResult
| _ -> return upcast this.View ()
} |> Async.StartAsTask

View File

@ -1,5 +1,6 @@
namespace MyPrayerJournal.Controllers namespace MyPrayerJournal.Controllers
open Microsoft.AspNetCore.Authorization
open Microsoft.AspNetCore.Mvc open Microsoft.AspNetCore.Mvc
open Microsoft.Extensions.Options open Microsoft.Extensions.Options
open MyPrayerJournal open MyPrayerJournal
@ -7,34 +8,42 @@ open MyPrayerJournal.ViewModels
open RethinkDb.Driver.Net open RethinkDb.Driver.Net
/// Controller for all /user URLs /// Controller for all /user URLs
[<Authorize>]
[<Route("user")>] [<Route("user")>]
type UserController(data : IConnection, cfg : IOptions<AppConfig>) = type UserController(data : IConnection, cfg : IOptions<AppConfig>) =
inherit ApplicationController(data) inherit ApplicationController(data)
[<AllowAnonymous>]
[<HttpGet("log-on")>] [<HttpGet("log-on")>]
member this.ShowLogOn () = member this.ShowLogOn () =
this.View(LogOnViewModel()) this.View(LogOnViewModel())
[<AllowAnonymous>]
[<HttpPost("log-on")>] [<HttpPost("log-on")>]
[<ValidateAntiForgeryToken>] [<ValidateAntiForgeryToken>]
member this.DoLogOn (form : LogOnViewModel) = member this.DoLogOn (form : LogOnViewModel) =
async { async {
let! user = data.LogOnUser form.Email (User.HashPassword form.Password cfg.Value.PasswordSaltBytes) let! user = data.LogOnUser form.Email (User.HashPassword form.Password cfg.Value.PasswordSaltBytes)
match user with 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 { UserMessage.Empty with Level = Level.Info
Message = Strings.get "LogOnSuccess" } Message = Strings.get "LogOnSuccess" }
|> model.AddMessage |> 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
*)
return this.Redirect "/" :> IActionResult return this.Redirect "/" :> IActionResult
| _ -> (*{ UserMessage.Empty with Level = Level.Error | _ -> (*{ UserMessage.Empty with Level = Level.Error
Message = Strings.get "LogOnFailure" } Message = Strings.get "LogOnFailure" }
|> model.AddMessage |> model.AddMessage
return this.Redirect "/user/log-on" model *) return this.Redirect "/user/log-on" model *)
return upcast this.RedirectToAction("ShowLogOn") return upcast this.RedirectToAction "ShowLogOn"
//return this.View() //return this.View()
} |> Async.StartAsTask } |> Async.StartAsTask
[<HttpGet("log-off")>]
member this.LogOff () =
async {
do! this.HttpContext.Authentication.SignOutAsync(Keys.Authentication)
// TODO: goodbye message
return this.LocalRedirect "/"
} |> Async.StartAsTask

View File

@ -1,6 +1,7 @@
namespace MyPrayerJournal namespace MyPrayerJournal
open Newtonsoft.Json open Newtonsoft.Json
open System.Security.Claims
open System.Security.Cryptography open System.Security.Cryptography
/// A user /// A user
@ -81,4 +82,11 @@ type Request = {
|> List.sortBy (fun item -> -item.AsOf) |> List.sortBy (fun item -> -item.AsOf)
|> List.map (fun item -> item.AsOf) |> List.map (fun item -> item.AsOf)
|> List.head |> List.head
/// The user for use with identity
[<AllowNullLiteral>]
type AppUser(user : User option) =
inherit ClaimsPrincipal()
/// The current user
member val User = user with get

View File

@ -3,7 +3,6 @@ namespace MyPrayerJournal.ViewModels
//open MyPrayerJournal //open MyPrayerJournal
/// Parent view model for all myPrayerJournal view models /// Parent view model for all myPrayerJournal view models
[<AllowNullLiteral>]
type AppViewModel() = type AppViewModel() =
member this.Q = "X" member this.Q = "X"
(* (*

View File

@ -2,7 +2,6 @@ namespace MyPrayerJournal.ViewModels
open System.ComponentModel.DataAnnotations open System.ComponentModel.DataAnnotations
[<AllowNullLiteral>]
type LogOnViewModel() = type LogOnViewModel() =
inherit AppViewModel() inherit AppViewModel()