Version 3 #67

Merged
danieljsummers merged 53 commits from version-3 into master 2021-10-26 23:39:59 +00:00
5 changed files with 171 additions and 207 deletions
Showing only changes of commit c0c5709194 - Show all commits

View File

@ -6,18 +6,44 @@ module MyPrayerJournal.Handlers
open Giraffe
open Giraffe.Htmx
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Http
open System
open System.Security.Claims
/// Helper function to be able to split out log on
[<AutoOpen>]
module private LogOnHelpers =
/// Log on, optionally specifying a redirected URL once authentication is complete
let logOn url : HttpHandler =
fun next ctx -> task {
match url with
| Some it ->
do! ctx.ChallengeAsync ("Auth0", AuthenticationProperties (RedirectUri = it))
return! next ctx
| None -> return! challenge "Auth0" next ctx
}
/// Handlers for error conditions
module Error =
open Microsoft.Extensions.Logging
open System.Threading.Tasks
/// Handle errors
let error (ex : Exception) (log : ILogger) =
log.LogError (EventId(), ex, "An unhandled exception has occurred while executing the request.")
clearResponse >=> setStatusCode 500 >=> json ex.Message
/// Handle unauthorized actions, redirecting to log on for GETs, otherwise returning a 401 Not Authorized reponse
let notAuthorized : HttpHandler =
fun next ctx ->
(next, ctx)
||> match ctx.Request.Method with
| "GET" -> logOn None
| _ -> setStatusCode 401 >=> fun _ _ -> Task.FromResult<HttpContext option> None
/// Handle 404s from the API, sending known URL paths to the Vue app so that they can be handled there
let notFound : HttpHandler =
setStatusCode 404 >=> text "Not found"
@ -28,36 +54,14 @@ module Error =
module private Helpers =
open LiteDB
open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Http.Features.Authentication
open Microsoft.Extensions.Logging
open Microsoft.Net.Http.Headers
open System.Security.Claims
open System.Threading.Tasks
let debug (ctx : HttpContext) message =
let fac = ctx.GetService<ILoggerFactory>()
let log = fac.CreateLogger "Debug"
log.LogInformation message
/// This type is internal in ASP.NET Core. :(
type AuthFeatures (result : AuthenticateResult) =
let mutable _user : ClaimsPrincipal = match result with null -> null | r -> r.Principal
let mutable _result : AuthenticateResult = result
interface IAuthenticateResultFeature with
member __.AuthenticateResult
with get () = _result
and set v =
_result <- v
_user <- match _result with null -> null | rslt -> rslt.Principal
interface IHttpAuthenticationFeature with
member __.User
with get () = _user
and set v =
_user <- v
_result <- null
/// Get the LiteDB database
let db (ctx : HttpContext) = ctx.GetService<LiteDatabase>()
@ -67,11 +71,12 @@ module private Helpers =
|> Option.ofObj
|> Option.map (fun user -> user.Claims |> Seq.tryFind (fun u -> u.Type = ClaimTypes.NameIdentifier))
|> Option.flatten
|> Option.map (fun claim -> claim.Value)
/// Get the current user's ID
// NOTE: this may raise if you don't run the request through the authorize handler first
// NOTE: this may raise if you don't run the request through the requiresAuthentication handler first
let userId ctx =
((user >> Option.get) ctx).Value |> UserId
(user >> Option.get) ctx |> UserId
/// Return a 201 CREATED response
let created =
@ -87,54 +92,6 @@ module private Helpers =
let jsNow () =
DateTime.UtcNow.Subtract(DateTime (1970, 1, 1, 0, 0, 0)).TotalSeconds |> (int64 >> ( * ) 1_000L >> Ticks)
/// Handler to return a 401 Not Authorized reponse
let notAuthorized : HttpHandler =
setStatusCode 401 >=> fun _ _ -> Task.FromResult<HttpContext option> None
/// Handler to require authorization
// NOTE: This is cribbed from ASP.NET Core's `AuthenticationMiddleware#Invoke`
// https://github.com/dotnet/aspnetcore/blob/main/src/Security/Authentication/Core/src/AuthenticationMiddleware.cs
let authorize : HttpHandler =
fun next ctx -> task {
let schemes = ctx.GetService<IAuthenticationSchemeProvider> ()
ctx.Features.Set<IAuthenticationFeature>
(AuthenticationFeature (OriginalPath = ctx.Request.Path, OriginalPathBase = ctx.Request.PathBase))
// Give any IAuthenticationRequestHandler schemes a chance to handle the request
let handlers = ctx.GetService<IAuthenticationHandlerProvider> ()
let! schms = schemes.GetRequestHandlerSchemesAsync ()
let mutable handled = false
for schm in schms do
match handled with
| true -> ()
| false ->
match! handlers.GetHandlerAsync (ctx, schm.Name) with
| null -> ()
| :? IAuthenticationRequestHandler as handler ->
match! handler.HandleRequestAsync () with true -> handled <- true | _ -> ()
| _ -> ()
match handled with
| true -> return None
| false ->
match! schemes.GetDefaultAuthenticateSchemeAsync () with
| null -> ()
| auth ->
match! ctx.AuthenticateAsync auth.Name with
| null -> ()
| result ->
match result.Principal with null -> () | _ -> ctx.User <- result.Principal
match result.Succeeded with
| true ->
let authFeatures = AuthFeatures result
ctx.Features.Set<IHttpAuthenticationFeature> authFeatures
ctx.Features.Set<IAuthenticateResultFeature> authFeatures
| false -> ()
return! match user ctx with Some _ -> next ctx | None -> notAuthorized next ctx
}
/// Render a component result
let renderComponent nodes : HttpHandler =
fun next ctx -> task {
@ -212,7 +169,7 @@ module Components =
// GET /components/journal-items
let journalItems : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let shouldShow now r = now > Ticks.toLong r.snoozedUntil && now > Ticks.toLong r.showAfter
let! jrnl = Data.journalByUserId (userId ctx) (db ctx)
@ -222,7 +179,7 @@ module Components =
// GET /components/request/[req-id]/edit
let requestEdit requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
match requestId with
| "new" ->
@ -230,22 +187,17 @@ module Components =
(Views.Request.edit (JournalRequest.ofRequestLite Request.empty) false) next ctx
| _ ->
match! Data.tryJournalById (RequestId.ofString requestId) (userId ctx) (db ctx) with
| Some req ->
return! partialIfNotRefresh "Edit Prayer Request" (Views.Request.edit req false) next ctx
| Some req -> return! partialIfNotRefresh "Edit Prayer Request" (Views.Request.edit req false) next ctx
| None -> return! Error.notFound next ctx
}
// GET /components/request-item/[req-id]
let requestItem reqId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
match! Data.tryJournalById (RequestId.ofString reqId) (userId ctx) (db ctx) with
| Some req ->
debug ctx "Found the item"
return! renderComponent [ Views.Request.reqListItem req ] next ctx
| None ->
debug ctx "Did not find the item"
return! Error.notFound next ctx
| Some req -> return! renderComponent [ Views.Request.reqListItem req ] next ctx
| None -> return! Error.notFound next ctx
}
@ -256,20 +208,20 @@ module Home =
let home : HttpHandler =
partialIfNotRefresh "Welcome!" Views.Home.home
// GET /user/log-on
let logOn : HttpHandler =
partialIfNotRefresh "Logging on..." Views.Home.logOn
/// /journal URL
module Journal =
// GET /journal
let journal : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let usr = ctx.Request.Headers.["X-Given-Name"].[0]
return! partialIfNotRefresh "Your Prayer Journal" (Views.Journal.journal usr) next ctx
let usr =
ctx.User.Claims
|> Seq.tryFind (fun c -> c.Type = ClaimTypes.GivenName)
|> Option.map (fun c -> c.Value)
|> Option.defaultValue "Your"
return! partialIfNotRefresh (sprintf "%s Prayer Journal" usr) (Views.Journal.journal usr) next ctx
}
@ -294,7 +246,7 @@ module Request =
// PATCH /request/[req-id]/prayed
let prayed requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let db = db ctx
let usrId = userId ctx
@ -315,7 +267,7 @@ module Request =
/// POST /api/request/[req-id]/note
let addNote requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let db = db ctx
let usrId = userId ctx
@ -329,17 +281,17 @@ module Request =
| None -> return! Error.notFound next ctx
}
/// GET /requests/active
// GET /requests/active
let active : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
return! partialIfNotRefresh "Active Requests" (Views.Request.active reqs) next ctx
}
/// GET /requests/snoozed
// GET /requests/snoozed
let snoozed : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let! reqs = Data.journalByUserId (userId ctx) (db ctx)
let now = (jsNow >> Ticks.toLong) ()
@ -347,9 +299,9 @@ module Request =
return! partialIfNotRefresh "Active Requests" (Views.Request.snoozed snoozed) next ctx
}
/// GET /requests/answered
// GET /requests/answered
let answered : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let! reqs = Data.answeredRequests (userId ctx) (db ctx)
return! partialIfNotRefresh "Answered Requests" (Views.Request.answered reqs) next ctx
@ -357,7 +309,7 @@ module Request =
/// GET /api/request/[req-id]
let get requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
match! Data.tryJournalById (RequestId.ofString requestId) (userId ctx) (db ctx) with
| Some req -> return! json req next ctx
@ -366,7 +318,7 @@ module Request =
// GET /request/[req-id]/full
let getFull requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
match! Data.tryFullRequestById (RequestId.ofString requestId) (userId ctx) (db ctx) with
| Some req -> return! partialIfNotRefresh "Prayer Request" (Views.Request.full req) next ctx
@ -375,7 +327,7 @@ module Request =
/// GET /api/request/[req-id]/notes
let getNotes requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let! notes = Data.notesById (RequestId.ofString requestId) (userId ctx) (db ctx)
return! json notes next ctx
@ -383,7 +335,7 @@ module Request =
// PATCH /request/[req-id]/show
let show requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let db = db ctx
let usrId = userId ctx
@ -398,7 +350,7 @@ module Request =
/// PATCH /api/request/[req-id]/snooze
let snooze requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let db = db ctx
let usrId = userId ctx
@ -414,7 +366,7 @@ module Request =
// PATCH /request/[req-id]/cancel-snooze
let cancelSnooze requestId : HttpHandler =
authorize
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let db = db ctx
let usrId = userId ctx
@ -434,7 +386,8 @@ module Request =
// POST /request
let add : HttpHandler =
fun next ctx -> task {
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
let! form = ctx.BindModelAsync<Models.Request> ()
let db = db ctx
let usrId = userId ctx
@ -462,7 +415,8 @@ module Request =
// PATCH /request
let update : HttpHandler =
fun next ctx -> Ply.task {
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> Ply.task {
let! form = ctx.BindModelAsync<Models.Request> ()
let db = db ctx
let usrId = userId ctx
@ -489,6 +443,25 @@ module Request =
}
/// Handlers for /user URLs
module User =
open Microsoft.AspNetCore.Authentication.Cookies
// GET /user/log-on
let logOn : HttpHandler =
logOn (Some "/journal")
// GET /user/log-off
let logOff : HttpHandler =
requiresAuthentication Error.notAuthorized
>=> fun next ctx -> task {
do! ctx.SignOutAsync ("Auth0", AuthenticationProperties (RedirectUri = "/"))
do! ctx.SignOutAsync CookieAuthenticationDefaults.AuthenticationScheme
return! next ctx
}
open Giraffe.EndpointRouting
/// The routes for myPrayerJournal
@ -526,7 +499,12 @@ let routes =
route "" Request.add
]
]
GET_HEAD [ route "/user/log-on" Home.logOn ]
subRoute "/user/" [
GET_HEAD [
route "log-off" User.logOff
route "log-on" User.logOn
]
]
subRoute "/api/" [
GET [
subRoute "request" [

View File

@ -16,7 +16,7 @@
<PackageReference Include="FunctionalCuid" Version="1.0.0" />
<PackageReference Include="Giraffe" Version="5.0.0" />
<PackageReference Include="LiteDB" Version="5.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../../../../Giraffe.Htmx/src/Htmx/Giraffe.Htmx.fsproj" />

View File

@ -56,27 +56,75 @@ module Configure =
open Giraffe
open LiteDB
open Microsoft.AspNetCore.Authentication.JwtBearer
open Microsoft.AspNetCore.Authentication.Cookies
open Microsoft.AspNetCore.Authentication.OpenIdConnect
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.DependencyInjection
open Microsoft.IdentityModel.Protocols.OpenIdConnect
open System
open System.Text.Json
open System.Text.Json.Serialization
open System.Threading.Tasks
/// Configure dependency injection
let services (bldr : WebApplicationBuilder) =
let sameSite (opts : CookieOptions) =
match opts.SameSite, opts.Secure with
| SameSiteMode.None, false -> opts.SameSite <- SameSiteMode.Unspecified
| _, _ -> ()
bldr.Services
.AddRouting()
.AddGiraffe()
.Configure<CookiePolicyOptions>(
fun (opts : CookiePolicyOptions) ->
opts.MinimumSameSitePolicy <- SameSiteMode.Unspecified
opts.OnAppendCookie <- fun ctx -> sameSite ctx.CookieOptions
opts.OnDeleteCookie <- fun ctx -> sameSite ctx.CookieOptions)
.AddAuthentication(
/// Use HTTP "Bearer" authentication with JWTs
fun opts ->
opts.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
opts.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(
/// Configure JWT options with Auth0 options from configuration
opts.DefaultAuthenticateScheme <- CookieAuthenticationDefaults.AuthenticationScheme
opts.DefaultSignInScheme <- CookieAuthenticationDefaults.AuthenticationScheme
opts.DefaultChallengeScheme <- CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie()
.AddOpenIdConnect("Auth0",
/// Configure OIDC with Auth0 options from configuration
fun opts ->
let jwtCfg = bldr.Configuration.GetSection "Auth0"
opts.Authority <- sprintf "https://%s/" jwtCfg.["Domain"]
opts.Audience <- jwtCfg.["Audience"])
let cfg = bldr.Configuration.GetSection "Auth0"
opts.Authority <- sprintf "https://%s/" cfg.["Domain"]
opts.ClientId <- cfg.["Id"]
opts.ClientSecret <- cfg.["Secret"]
opts.ResponseType <- OpenIdConnectResponseType.Code
opts.Scope.Clear ()
opts.Scope.Add "openid"
opts.Scope.Add "profile"
opts.CallbackPath <- PathString "/user/log-on/success"
opts.ClaimsIssuer <- "Auth0"
opts.SaveTokens <- true
opts.Events <- OpenIdConnectEvents ()
opts.Events.OnRedirectToIdentityProviderForSignOut <- fun ctx ->
let returnTo =
match ctx.Properties.RedirectUri with
| it when isNull it || it = "" -> ""
| redirUri ->
let finalRedirUri =
match redirUri.StartsWith "/" with
| true ->
// transform to absolute
let request = ctx.Request
sprintf "%s://%s%s%s" request.Scheme request.Host.Value request.PathBase.Value redirUri
| false -> redirUri
Uri.EscapeDataString finalRedirUri |> sprintf "&returnTo=%s"
sprintf "https://%s/v2/logout?client_id=%s%s" cfg.["Domain"] cfg.["Id"] returnTo
|> ctx.Response.Redirect
ctx.HandleResponse ()
Task.CompletedTask
)
|> ignore
let jsonOptions = JsonSerializerOptions ()
jsonOptions.Converters.Add (JsonFSharpConverter ())
@ -97,11 +145,14 @@ module Configure =
| true -> app.UseDeveloperExceptionPage ()
| false -> app.UseGiraffeErrorHandler Handlers.Error.error
|> ignore
app.UseAuthentication()
.UseStaticFiles()
app.UseStaticFiles()
.UseCookiePolicy()
.UseRouting()
.UseAuthentication()
// .UseAuthorization()
.UseEndpoints (fun e ->
e.MapGiraffeEndpoints Handlers.routes
// TODO: fallback to 404
e.MapFallbackToFile "index.html" |> ignore)
|> ignore
app

View File

@ -9,7 +9,10 @@ open System
module Helpers =
/// Create a link that targets the `main` element and pushes a URL to history
let pageLink href attrs = a (attrs |> List.append [ _href href; _hxBoost; _hxTarget "main"; _hxPushUrl ])
let pageLink href attrs =
attrs
|> List.append [ _href href; _hxBoost; _hxTarget "main"; _hxPushUrl ]
|> a
/// Create a Material icon
let icon name = span [ _class "material-icons" ] [ str name ]
@ -235,18 +238,18 @@ module Navigation =
/// Generate the navigation items based on the current state
let currentNav isAuthenticated hasSnoozed (url : Uri option) =
seq {
let currUrl = match url with Some u -> (u.PathAndQuery.Split '?').[0] | None -> ""
let navLink (matchUrl : string) =
match currUrl.StartsWith matchUrl with true -> [ _class "is-active-route" ] | false -> []
|> pageLink matchUrl
match isAuthenticated with
| true ->
let currUrl = match url with Some u -> (u.PathAndQuery.Split '?').[0] | None -> ""
let navLink (matchUrl : string) =
match currUrl.StartsWith matchUrl with true -> [ _class "is-active-route" ] | false -> []
|> pageLink matchUrl
li [ _class "nav-item" ] [ navLink "/journal" [ str "Journal" ] ]
li [ _class "nav-item" ] [ navLink "/requests/active" [ str "Active" ] ]
if hasSnoozed then li [ _class "nav-item" ] [ navLink "/requests/snoozed" [ str "Snoozed" ] ]
li [ _class "nav-item" ] [ navLink "/requests/answered" [ str "Answered" ] ]
li [ _class "nav-item" ] [ a [ _href "/user/log-off"; _onclick "mpj.logOff(event)" ] [ str "Log Off" ] ]
| false -> li [ _class "nav-item"] [ a [ _href "/user/log-on"; _onclick "mpj.logOn(event)"] [ str "Log On" ] ]
li [ _class "nav-item" ] [ a [ _href "/user/log-off" ] [ str "Log Off" ] ]
| false -> li [ _class "nav-item"] [ a [ _href "/user/log-on" ] [ str "Log On" ] ]
li [ _class "nav-item" ] [ a [ _href "https://docs.prayerjournal.me"; _target "_blank" ] [ str "Docs" ] ]
}
|> List.ofSeq
@ -290,7 +293,13 @@ module Journal =
/// The journal loading page
let journal user = article [ _class "container-fluid mt-3" ] [
h2 [ _class "pb-3" ] [ str user; rawText "&rsquo;s Prayer Journal" ]
h2 [ _class "pb-3" ] [
str user
match user with
| "Your" -> ()
| _ -> rawText "&rsquo;s"
str " Prayer Journal"
]
p [
_hxGet "/components/journal-items"
_hxSwap HxSwap.OuterHtml
@ -571,7 +580,8 @@ module Layout =
/// The HTML `head` element
let htmlHead pageTitle =
head [] [
head [ _lang "en" ] [
meta [ _name "viewport"; _content "width=device-width, initial-scale=1" ]
title [] [ str pageTitle; rawText " &#xab; myPrayerJournal" ]
link [
_href "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
@ -581,11 +591,6 @@ module Layout =
]
link [ _href "https://fonts.googleapis.com/icon?family=Material+Icons"; _rel "stylesheet" ]
link [ _href "/style/style.css"; _rel "stylesheet" ]
script [
_src "https://unpkg.com/htmx.org@1.5.0"
_integrity "sha384-oGA+prIp5Vchu6we2YkI51UtVzN9Jpx2Z7PnR1I78PnZlN8LkrCT4lqqqmDkyrvI"
_crossorigin "anonymous"
] []
]
/// Element used to display toasts
@ -612,13 +617,17 @@ module Layout =
]
]
]
script [
_src "https://unpkg.com/htmx.org@1.5.0"
_integrity "sha384-oGA+prIp5Vchu6we2YkI51UtVzN9Jpx2Z7PnR1I78PnZlN8LkrCT4lqqqmDkyrvI"
_crossorigin "anonymous"
] []
script [
_async
_src "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
_integrity "sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
_crossorigin "anonymous"
] []
script [ _src "https://cdn.auth0.com/js/auth0-spa-js/1.13/auth0-spa-js.production.js" ] []
script [ _src "/script/mpj.js" ] []
]

View File

@ -2,41 +2,6 @@
/** myPrayerJournal script */
const mpj = {
/** Auth0 configuration */
auth: {
/** The Auth0 client */
auth0: null,
/** Configure the Auth0 client */
async configureClient () {
const response = await fetch("/auth-config.json")
const config = await response.json()
mpj.auth.auth0 = await createAuth0Client({
domain: config.domain,
client_id: config.clientId,
audience: config.audience
})
}
},
/** Whether the user is currently authenticated */
isAuthenticated: false,
/** Whether we should redirect to the journal the next time the menu items are refreshed */
redirToJournal: false,
/**
* Process a log on request
* @param {Event} e The HTML event from the `onclick` event
*/
async logOn (e) {
e.preventDefault()
await mpj.auth.auth0.loginWithRedirect({ redirect_uri: `${window.location.origin}/user/log-on` })
},
/**
* Log the user off
* @param {Event} e The HTML event from the `onclick` event
*/
logOff (e) {
e.preventDefault()
mpj.auth.auth0.logout({ returnTo: window.location.origin })
},
/**
* Show a message via toast
* @param {string} message The message to show
@ -89,37 +54,6 @@ const mpj = {
},
}
window.onload = async () => {
/** If the user is authenticated, set the JWT on the `body` tag */
const establishAuth = async () => {
mpj.isAuthenticated = await mpj.auth.auth0.isAuthenticated()
if (mpj.isAuthenticated) {
const token = await mpj.auth.auth0.getTokenSilently()
const user = await mpj.auth.auth0.getUser()
document.querySelector("body")
.setAttribute("hx-headers", `{ "Authorization": "Bearer ${token}", "X-Given-Name": "${user.given_name}" }`)
htmx.trigger(htmx.find(".navbar-nav"), "menu-refresh")
}
}
// Set up Auth0
await mpj.auth.configureClient()
await establishAuth()
if (mpj.isAuthenticated) return
// Handle log on code, if present
const query = window.location.search
if (query.includes("code=") && query.includes("state=")) {
await mpj.auth.auth0.handleRedirectCallback()
await establishAuth()
if (window.location.pathname === "/user/log-on") {
mpj.redirToJournal = true
} else {
window.history.replaceState({}, document.title, "/")
}
}
}
htmx.on("htmx:afterOnLoad", function (evt) {
const hdrs = evt.detail.xhr.getAllResponseHeaders()
// Set the page title if a header was in the response
@ -133,11 +67,3 @@ htmx.on("htmx:afterOnLoad", function (evt) {
mpj.showToast(evt.detail.xhr.getResponseHeader("x-toast"))
}
})
htmx.on("htmx:afterSettle", function (evt) {
// Redirect to the journal (once menu items load after log on)
if (mpj.redirToJournal
&& ([...evt.target.attributes].find(it => it.name === "hx-target")?.value ?? "") === ".navbar-nav") {
mpj.redirToJournal = false
document.querySelector(`a[href="/journal"]`).click()
}
})