Building a safety audit website 
with Wolfram Application Server

Jon McLoone, CEO, Staaff Ltd

(RowDefault[, (Spacer1), , (Spacer1), ])  #WolframTechConf
  @jonmcloone
http://staaff.co.uk

Background
UK DSE legislation

Your employer is responsible for safe use of ‘display screen equipment’

DSE includes computer, furniture & environment

Must be evaluated by a ‘competent person’

Identified risks must be addressed

Solution:
‘DSE Manager’ website

Organizations set their own policies for rigorousness

Deliver and track training

Perform and track assessments

Track remediation

Provide management reporting

Demo: http://localhost/dse/home

Wolfram Technology Choices

yes=Style["✓",Darker@Green];
yesish=Style["✓",LightGray];
no=Style["×",Darker@Red];
noish=Style["×",LightGray];
TextGrid[{
Style[#,Bold]&/@{"","Web Engine\nFor Python","webMathematica","Application Server","Private Cloud"},
{"Purpose","Trivial","Single server\n(Deprecated)","High availability","Full feature"},
{"Rapid deployment",Row[{yes,yes}],no,yes,no},
{"MSP Support",no,yes, yesish, no},
{"APIFunction",yes,noish, yes, yes},
{"FormFunction",yes,no, yes, yes},
{"Notebooks",no,no, no, yes},
{"Multi-kernel",no,yes, yes ,yes},
{"Multi-node" ,no, no, yes, yesish},
{"Permissions", no,no, no, yes},
{"Scheduled tasks", no,no, no, yes},
{"Latency","<20 ms", "<20 ms","< 20ms","> 80ms"},
{"Size", "5-10Gb", "5-10Gb","~8Gb","~50Gb"}
},Alignment->{{Left,{Center}},{Center}},Dividers->{{Darker[Gray,.6],{Lighter[Gray,.5]},Darker[Gray,.6]},{Darker[Gray,.6],Darker[Gray,.6],{False},Darker[Gray,.6]}},
Frame->Darker[Gray,.6],
Background->{None,{Lighter[Gray,.85],{White,Lighter[Blend[{Blue,Green}],.9]}}},
BaseStyle->{FontSize->Scaled[0.015]}]

Architecture
Software

With[{was=Panel[Style["Wolfram\nApplication\nServer",FontSize->Scaled[0.03]]]},Graphics[

Flatten@Join[
Table[Text[was,{i,1}],{i,0,2}],
{Text[Panel[Style["mySQL",FontSize->Scaled[0.04]]],{1,0}],
Gray,
Line[{{-0.4,-0.15},{-0.4,1.5},{2.4,1.5},{2.4,-0.15},{-0.4,-0.15}}],
Arrowheads[{{-.04,0.2},{.04,0.8}}]},
Table[Arrow[{{i,0.77},{1+i/20-1/20,0.11}}],{i,0,2}],
{
Arrow[{{0,1.8},{0,1.23}}],
Text[Panel[Style["Browser",FontSize->Scaled[0.04]]],{0,1.9}],

Text[Panel[Style["cron\nwolframscript",FontSize->Scaled[0.014]]],{2,0}],
Arrowheads[{{-.02,0.2},{.02,0.8}}],
LightGray,
Arrow[{{1.22,0},{1.83,0}}]
}
]
,ImageSize->1600,PlotRange->{{-0.5,2.5},{-0.3,2}}]
]

Architecture
Data flow

Graphics[{
Text[Panel[Style["Request",FontSize->Scaled[0.04]]],{1,4}],
Text[Panel[Style["Extract parameters",FontSize->Scaled[0.04]]],{4,4}],
Text[Panel[Style["Fetch user data",FontSize->Scaled[0.04]]],{4,3}],
Text[Panel[Style["Business logic",FontSize->Scaled[0.04]]],{4,2}],
Text[Panel[Style["Construct HTML",FontSize->Scaled[0.04]]],{4,1}],
Text[Panel[Style["Response",FontSize->Scaled[0.04]]],{1,1}],
Arrow[{{1.5,4},{3.1,4}}],
Arrow[{{4,3.8},{4,3.2}}],
Arrow[{{4,2.8},{4,2.2}}],
Arrow[{{4,1.8},{4,1.2}}],
Arrow[{{3.2,1},{1.5,1}}]
},
PlotRange->{{0.3,5},{0.7,4.5}},ImageSize->1600]

Key components
Page construction

HTML is generated in Wolfram Language

Components built with XMLElement, w3.css and w3.js

PacletDirectoryLoad["/Users/jonm/git/WorkstationReview/DSE/"];
<<DSE`;
DSE`Deployment`$DeploymentConfiguration["DatabaseURL"]="localhost:3306";

Names["h*"]

grid=hGrid[RandomInteger[10,{4,4}],"Headers"->{"one","two","three","four"}]

First@w3HTML[grid]

EmbeddedHTML@First@w3HTML[grid]

EmbeddedHTML@First@w3HTML@
 hAccordion[
			"show more",
			hTabView[{"one"->1,"two"->2}],
			"AutoClose"->False
			]

Functional programming page construction

EmbeddedHTML@First@w3HTML@
Map[
hAccordion[#[[2]],hText[StringTake[ExampleData[#],1000],"w3-container"]]&,
ExampleData["Text"]
]

Key components
DatabaseTools

Extend DatabaseLink to use Associations

KeyDrop["PasswordHash"]@
getRecords["Users",<|"Email"->"jonm@wolfram.com"|>]

updateRecord["Users",<|"Email"->"jonm@wolfram.com","Phone"->"00000"|>,"Email"]

getRecords["Users",<|"Email"->"jonm@wolfram.com"|>][[1]]["Phone"]

Key components
Authentication

Enter credentials

hForm[
	"PasswordCheck",
	"Submit",
		{
			If[#error === "",Nothing,hErrorText[#error]],
			hInput["user", "text", "Email:", ""],
			hInput["pass", "password", "Password:", ""],
			hInput["redirect", "hidden", "", #redirect],
			hTooltip["Start a new user account for an existing or new organization."]@hLink["Create account","createaccount"],
			hBreak[],
			hTooltip["Send a password reset link"]@hLink["Forgotten password?","passwordReset"]
		}
]

Verify password against database hash

Store Wolfram Application Server SessionID

login[email_, password_]:=
    Block[
        {userData = getRecords["Users", "Email" -> email]},
        If[
            FailureQ[userData] || Length[userData] < 1, Failure["BadLogin", <|"UpstreamFailure"->userData|>],
            (*else*)
            userData=First[userData];
            If[
                userData["PasswordHash"] =!= passwordHash[email, password], (*Password is correct*)
                Failure["BadLogin", <||>],
                (*else*)
                updateRecord[
                    "Users",
                    <|
                      "Email" -> email,
                      "LastLoginDate" -> Now,
                      "SessionIDHash" -> 
                         Hash[(*salt<>*)ToString[HTTPRequestData["SessionID"]], "SHA512", "Base64Encoding"]
                    |>,
                    "Email"
                ];
                KeyTake[userData, {"UserName", "UserStatus", "Role", "Email"}]
            ]
        ]
    ];

Use SessionID for identifying user in all future transactions

getUserSummaryData[]:=
    SQLSessionBlock[
        con,
        Block[
            {
                userRecord,
                sessionID = ToString[HTTPRequestData["SessionID"]]
            },

           userRecord =
              AssociationThread[
                    Join[getColumnNames["Users"],getColumnNames["Organizations"]],
                    SQLSelect[
                            con,
                            {"Users", "Organizations"},
                            Join[
                                {"Users",#}& /@ getColumnNames["Users"],
                                {"Organizations",#}& /@ getColumnNames["Organizations"]
                            ],
                            SQLColumn[{"Users","SessionIDHash"}] == 
                                   Hash[(*salt<>*)sessionID, "SHA512", "Base64Encoding"]
                              && SQLColumn[{"Users","OrganizationID"}] == 
                                    SQLColumn[{"Organizations","OrganizationID"}]
                        ]];
                If[
                    FailureQ[userRecord],
                    userRecord,
                    KeyDrop[userRecord, {"PasswordHash","SessionIDHash", "PasswordResetHash"}]
             ]]];

Key components
Access control

Wolfram Application Server  does not support Permissions

So I created my own

AuthenticatedAPI[parameters, function, permissions]

Fetch current user info

Check against permissions

UserAuthorizedQ["ViewBilling", "9b6da931-e2fe-4a7f-9848-491b92b80a4e"]

Behave like APIFunction if True

AuthenticatedAPI[
{"message"->"String"}, 
#message<>userName[#UserID]&, 
UserAuthorizedQ["Active",#UserID]&]

Key components
Deployment

Connect through ServiceConnect

<<WolframApplicationServer`
was=ServiceConnect["WolframApplicationServer","http://localhost"]

APIFunctions deployed as “ActiveWebElement”

ServiceDeploy[
	was,
	<|
		"Contents"->APIFunction[...],
		"Resource"->"dse/pageName.wl",
		"Endpoint"->"dse/pageName",
		"ResourceType" -> "ActiveWebElement"
	|>
]

Video, CSS etc deployed as “StaticWebElement”

Scripted deployment of whole site

deployAll[
	was, 
	<|
		"BaseURL"->"http://localhost/dse/",
		"MailSettings"-><|redacted|>,
		"DatabaseURL"->"host.docker.internal:3306"
	|>
]

Summary

Whole site developed in Wolfram Application Server

Some advantages…

Single language for content, logic and data

Build re-usable components (similar to web frameworks)

Assemble components with functional programming

Pass data around carelessly (its all safely on the server side)

I know Wolfram Language!

Plan to release the DSE Application in the New Year

Init

(*Copy css etc to $TemporaryDirectory so that the EmbeddedHTML examples work
The w3.js and w3.css are also available from 
https://www.w3schools.com/w3css
https://www.w3schools.com/w3js
*)
Quiet[CreateDirectory[FileNameJoin[{$TemporaryDirectory,"resources"}]];
CopyFile["/Users/jonm/git/WorkstationReview/DSE/Resources/TextAssets/dse.js",FileNameJoin[{$TemporaryDirectory,"resources","dse.js"}],OverwriteTarget->True];
CopyFile["/Users/jonm/git/WorkstationReview/DSE/Resources/TextAssets/w3.js",FileNameJoin[{$TemporaryDirectory,"resources","w3.js"}],OverwriteTarget->True];
CopyFile["/Users/jonm/git/WorkstationReview/DSE/Resources/TextAssets/w3.css",FileNameJoin[{$TemporaryDirectory,"resources","w3.css"}],OverwriteTarget->True];
CopyFile["/Users/jonm/git/WorkstationReview/DSE/Resources/TextAssets/staaff.css",FileNameJoin[{$TemporaryDirectory,"resources","staaff.css"}],OverwriteTarget->True]];