Authentication, take 2; take 3 forthcoming...

*cue sad trombone*
This commit is contained in:
Daniel J. Summers 2016-10-07 22:07:46 -05:00
parent c0237e1433
commit d3a80b9ceb
7 changed files with 63 additions and 49 deletions

View File

@ -8,6 +8,7 @@ open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging open Microsoft.Extensions.Logging
open Microsoft.Extensions.Options open Microsoft.Extensions.Options
open RethinkDB.DistributedCache
open System open System
open System.IO open System.IO
@ -28,46 +29,45 @@ type Startup(env : IHostingEnvironment) =
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
member this.ConfigureServices (services : IServiceCollection) = member this.ConfigureServices (services : IServiceCollection) =
ignore <| services.AddOptions () services.AddOptions () |> ignore
ignore <| services.Configure<AppConfig>(this.Configuration.GetSection("MyPrayerJournal")) services.Configure<AppConfig> (this.Configuration.GetSection "MyPrayerJournal") |> ignore
ignore <| services.AddLocalization (fun options -> options.ResourcesPath <- "Resources") services.AddLocalization (fun opt -> opt.ResourcesPath <- "Resources") |> ignore
ignore <| services.AddMvc () services.AddMvc () |> ignore
ignore <| services.AddDistributedMemoryCache () //ignore <| services.AddDistributedMemoryCache ()
// 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 services.AddSingleton conn |> ignore
//ignore <| services.AddDistributedRethinkDBCache (fun options -> services.AddDistributedRethinkDBCache (fun options ->
// options.Connection <- conn options.Database <- match cfg.DataConfig.Database with null -> "" | db -> db
// options.Database <- match cfg.DataConfig.Database with null -> "" | db -> db options.TableName <- "Session") |> ignore
// options.TableName <- "Session") services.AddSession () |> ignore
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.
member this.Configure (app : IApplicationBuilder, env : IHostingEnvironment, loggerFactory : ILoggerFactory) = member this.Configure (app : IApplicationBuilder, env : IHostingEnvironment, loggerFactory : ILoggerFactory) =
ignore <| loggerFactory.AddConsole(this.Configuration.GetSection "Logging") loggerFactory.AddConsole(this.Configuration.GetSection "Logging") |> ignore
ignore <| loggerFactory.AddDebug () loggerFactory.AddDebug () |> ignore
match env.IsDevelopment () with match env.IsDevelopment () with
| true -> ignore <| app.UseDeveloperExceptionPage () | true -> app.UseDeveloperExceptionPage () |> ignore
ignore <| app.UseBrowserLink () app.UseBrowserLink () |> ignore
| _ -> ignore <| app.UseExceptionHandler("/error") | _ -> app.UseExceptionHandler "/error" |> ignore
ignore <| app.UseStaticFiles () app.UseStaticFiles () |> ignore
ignore <| app.UseCookieAuthentication( app.UseCookieAuthentication(
CookieAuthenticationOptions( CookieAuthenticationOptions(
AuthenticationScheme = Keys.Authentication, AuthenticationScheme = Keys.Authentication,
LoginPath = PathString("/user/log-on"), LoginPath = PathString "/user/log-on",
AutomaticAuthenticate = true, AutomaticAuthenticate = true,
AutomaticChallenge = true, AutomaticChallenge = true,
ExpireTimeSpan = TimeSpan(2, 0, 0), ExpireTimeSpan = TimeSpan (2, 0, 0),
SlidingExpiration = true)) SlidingExpiration = true)) |> ignore
ignore <| app.UseMvc(fun routes -> app.UseMvc(fun routes ->
ignore <| routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}")) routes.MapRoute(name = "default", template = "{controller=Home}/{action=Index}/{id?}") |> ignore) |> ignore
/// Default to Development environment /// Default to Development environment
let defaults = seq { yield WebHostDefaults.EnvironmentKey, "Development" } let defaults = seq { yield WebHostDefaults.EnvironmentKey, "Development" }
@ -81,12 +81,12 @@ let main argv =
.AddEnvironmentVariables("ASPNETCORE_") .AddEnvironmentVariables("ASPNETCORE_")
.AddCommandLine(argv) .AddCommandLine(argv)
.Build() .Build()
use host =
WebHostBuilder() WebHostBuilder()
.UseConfiguration(cfg) .UseConfiguration(cfg)
.UseKestrel() .UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory()) .UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>() .UseStartup<Startup>()
.Build() .Build()
.Run() host.Run()
0 0

View File

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

View File

@ -25,7 +25,7 @@ type UserController(data : IConnection, cfg : IOptions<AppConfig>) =
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 -> do! this.HttpContext.Authentication.SignInAsync(Keys.Authentication, AppUser(user)) | Some usr -> do! this.HttpContext.Authentication.SignInAsync (Keys.Authentication, AppUser user)
// TODO: welcome message // TODO: welcome message
(* this.Session.[Keys.User] <- usr (* this.Session.[Keys.User] <- usr
{ UserMessage.Empty with Level = Level.Info { UserMessage.Empty with Level = Level.Info
@ -37,13 +37,13 @@ type UserController(data : IConnection, cfg : IOptions<AppConfig>) =
|> 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() }
} |> Async.StartAsTask |> Async.StartAsTask
[<HttpGet("log-off")>] [<HttpGet("log-off")>]
member this.LogOff () = member this.LogOff () =
async { async {
do! this.HttpContext.Authentication.SignOutAsync(Keys.Authentication) do! this.HttpContext.Authentication.SignOutAsync Keys.Authentication
// TODO: goodbye message // TODO: goodbye message
return this.LocalRedirect "/" return this.LocalRedirect "/"
} |> Async.StartAsTask } |> Async.StartAsTask

View File

@ -29,7 +29,7 @@ type IConnection with
.Filter(ReqlFunction1(fun usr -> upcast usr.["PasswordHash"].Eq(passwordHash))) .Filter(ReqlFunction1(fun usr -> upcast usr.["PasswordHash"].Eq(passwordHash)))
.RunResultAsync<User list>(this) .RunResultAsync<User list>(this)
return user |> List.tryHead return user |> List.tryHead
} }
/// Set up the environment for MyPrayerJournal /// Set up the environment for MyPrayerJournal
member this.EstablishEnvironment (cfg : AppConfig) = member this.EstablishEnvironment (cfg : AppConfig) =
@ -50,7 +50,7 @@ type IConnection with
| _ -> logStepStart " Database not found - creating..." | _ -> logStepStart " Database not found - creating..."
do! r.DbCreate("MyPrayerJournal").RunResultAsync(this) do! r.DbCreate("MyPrayerJournal").RunResultAsync(this)
logStepEnd () logStepEnd ()
} }
/// Ensure all tables exit /// Ensure all tables exit
let checkTables () = let checkTables () =
async { async {
@ -63,7 +63,7 @@ type IConnection with
logStepStart <| sprintf " %s table not found - creating..." tbl logStepStart <| sprintf " %s table not found - creating..." tbl
do! db().TableCreate(tbl).RunResultAsync(this) do! db().TableCreate(tbl).RunResultAsync(this)
logStepEnd() logStepEnd()
}) })
|> List.iter Async.RunSynchronously |> List.iter Async.RunSynchronously
// Seed the user table if it is empty // Seed the user table if it is empty
let! userCount = db().Table(DataTable.User).Count().RunResultAsync<int64>(this) let! userCount = db().Table(DataTable.User).Count().RunResultAsync<int64>(this)
@ -75,7 +75,8 @@ type IConnection with
Email = "test@example.com" Email = "test@example.com"
PasswordHash = User.HashPassword "password" cfg.PasswordSaltBytes PasswordHash = User.HashPassword "password" cfg.PasswordSaltBytes
Name = "Default User" Name = "Default User"
TimeZone = "America/Chicago" }).RunResultAsync(this) TimeZone = "America/Chicago"
}).RunResultAsync(this)
logStepEnd () logStepEnd ()
| _ -> () | _ -> ()
} }
@ -95,11 +96,11 @@ type IConnection with
| _ -> logStepStart <| sprintf " %s.Email index not found - creating..." DataTable.User | _ -> logStepStart <| sprintf " %s.Email index not found - creating..." DataTable.User
do! db().Table(DataTable.User).IndexCreate("Email").RunResultAsync(this) do! db().Table(DataTable.User).IndexCreate("Email").RunResultAsync(this)
logStepEnd () logStepEnd ()
} }
async { async {
logStep "Database checks starting" logStep "Database checks starting"
do! checkDatabase () do! checkDatabase ()
do! checkTables () do! checkTables ()
do! checkIndexes () do! checkIndexes ()
logStep "Database checks complete" logStep "Database checks complete"
} }

View File

@ -75,6 +75,6 @@
<script src="~/js/site.min.js" asp-append-version="true"></script> <script src="~/js/site.min.js" asp-append-version="true"></script>
</environment> </environment>
@RenderSection("scripts", required: false) @RenderSection("Scripts", required: false)
</body> </body>
</html> </html>

View File

@ -28,4 +28,13 @@
</p> </p>
</div> </div>
</div> </div>
</form> </form>
@section Scripts
{
<script type="text/javascript">
/* <![CDATA[ */
$(document).ready(function () { $("#Email").focus() })
/* ]]> */
</script>
}

View File

@ -37,6 +37,7 @@
}, },
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"NodaTime": "2.0.0-alpha20160729", "NodaTime": "2.0.0-alpha20160729",
"RethinkDB.DistributedCache": "0.9.0-alpha01",
"RethinkDb.Driver": "2.3.15" "RethinkDb.Driver": "2.3.15"
}, },