Below you'll find real world code samples that demonstrate my proficiency in PHP, JQuery, Bootstrap and Photoshop.
"Landing Pages" are what an employer that's partnered with Applied Health Analytics will use as a way to inform their employees about the wellness initiatives that are available and how to access them. They're a part of AHA's storefront and involve a fairly significant amount of backend code to facilitate logging in and listing the various screening events that are particular to that employer.
I was handed this project when I first started. Although it was understood that many of the users would be on a mobile device, the site itself was not mobile friendly. In addition, it was based on the "Blitz" framework for which there is very little documentation. Click on the images to the right to see the "before" and "after" versions. The "before" version is the one that is devoid of any color. The "after" version is the one that is blue.
I overhauled the mobile dynamic and and then proceeded to streamline the code which culminated in building an interface that allowed users to build the scaffolding of the Landing Page by engaging in a series of questions. I've edited the code as a whole so you can see an example of the PHP / OOP code and the JQuery that was used for the interface.
<!-- HTML code for one of the questions the user has to answer that will dictate how their content will be documented in the database -->
<tr>
<td>Does this page need to be categorized as a "customized" Landing Page?</td>
<td style="text-align:center; width:35px;"><input type="radio" name="customize" id="customize_yes" onchange="customizeYes(this)" value="yes"></td>
<td style="text-align:center; width:35px;"><input type="radio" name="customze" id="customize_no" onchange="customizeNo(this)" value="no"></td>
</tr>
//JQuery the displays the appropriate follow up questions based on the user's initial response
function spanishAlertYes(check)
{
if(check.checked)
{
$('.submit_class').show(100);
$('.employer_options').hide(100);
$('.employee_spouse_name').hide(100);
//zero-ing out any previously checked values should the user backtrack and change their mind about a Spanish version
$('#emp_spouse_yes_no').prop("checked", false);
$('#emp_spouse_no').prop("checked", false);
tinyMCE.getInstanceById('homepage_text_1').setContent(' ');
$('#homepage_text_2').val(' ');
$('#submit_explanation').html("<br><div style=\"width:95%; margin:auto; text-align:center; border:1px solid #cccccc; border-radius:10pt; padding:10px;\">When you click on \"Submit,\" you will be creating the basic aesthetics of your user's landing page. From here, you'll be directed to another interface where you'll being creating the user's Landing Page experience in Spanish.</div>");
}
}
<!--this is an example of some PHP / OOP code used to insert values into the database -->
<?php
$user="";
$host="";
$password="";
$database = "";
$conn = new PDO("mysql:host=$host;dbname=$database", $user, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
class InsertEmployer {
public function phaseOne ()
{
global $conn;
if($_POST['spanish_alert_yes_no']=="spanish")
{
/*
you're adding all of your fields except emp_spouse, emp_link, homepage_one and homepage_two as well as updating the "homepage_indicator" column to "1" to represent the fact that this is the homepage - the
first page a user will see when they access the Landing Page
*/
$empID=trim($_POST['empID']);
$cpID = trim($_POST['cpID']);
$homepage_indicator = 1;
$spanish_alert_yes_no = trim($_POST['spanish_alert_yes_no']);
$empName = trim($_POST['empName']);
$bodyTopColor = $_POST['bodyTopColor'];
$bodyBottomColor = $_POST['bodyBottomColor'];
$url = trim($_POST['url']);
$statement = $conn->prepare("INSERT into employers (empID, cpID, homepage_indicator, spanish_alert, empName, url, bodyTopColor, bodyBottomColor) VALUES (:empID, :cpID, :homepage_indicator, :spanish_alert_yes_no,
:empName, :url, :bodyTopColor, :bodyBottomColor)");
$statement->bindParam(':empID', $empID);
$statement->bindParam('cpID', $cpID);
$statement->bindParam('homepage_indicator', $homepage_indicator);
$statement->bindParam(':spanish_alert_yes_no', $spanish_alert_yes_no);
$statement->bindParam(':empName', $empName);
$statement->bindParam(':url', $url);
$statement->bindParam(':bodyTopColor', $bodyTopColor);
$statement->bindParam(':bodyBottomColor', $bodyBottomColor);
if($statement->execute())
{
$last_id = $conn->lastInsertId();
//return id of record just inserted
//upload logo and update record
$target = "../Photos/";
$target = $target . basename( $_FILES['custom_header']['name']) ;
$photo_url = basename($_FILES['custom_header']['name']);
$company_logo=$_POST['company_logo'];
if(!move_uploaded_file($_FILES['custom_header']['tmp_name'], $target))
{
return("photo didn't upload");
}
else
{
$sql=$conn->prepare("update employers set
header_graphic=:photo_url,
homepage_id=:last_id,
stock_header_graphic=:company_logo
where id=:id");
$sql->bindParam(':photo_url', $photo_url, PDO::PARAM_STR);
$sql->bindParam(':id', $last_id, PDO::PARAM_INT);
$sql->bindParam(':last_id', $last_id, PDO::PARAM_INT);
$sql->bindParam(':company_logo', $company_logo, PDO::PARAM_STR);
if($sql->execute())
{
return($last_id);
}
}
}
else
{
return ("fail");
}
}
elseif(isset($_POST['customize'])&& $_POST['customize']=="yes")
{
$empID=trim($_POST['empID']);
$cpID = trim($_POST['cpID']);
$homepage_indicator = 1;
$empName = trim($_POST['empName']);
$bodyTopColor = $_POST['bodyTopColor'];
$bodyBottomColor = $_POST['bodyBottomColor'];
$url = trim($_POST['url']);
$statement = $conn->prepare("INSERT into employers (empID, cpID, homepage_indicator, empName, url, bodyTopColor, bodyBottomColor, customize_url) VALUES (:empID, :cpID, :homepage_indicator, :empName, :url, :bodyTopColor,
:bodyBottomColor, :customize_url)");
$statement->bindParam(':empID', $empID);
$statement->bindParam('cpID', $cpID);
$statement->bindParam('homepage_indicator', $homepage_indicator);
$statement->bindParam(':empName', $empName);
$statement->bindParam(':url', $url);
$statement->bindParam(':bodyTopColor', $bodyTopColor);
$statement->bindParam(':bodyBottomColor', $bodyBottomColor);
$statement->bindParam(':customize_url', $url);
/* debug code
$sql_string="INSERT into employers (empID, cpID, spanish_alert, empName, bodyTopColor, bodyBottomColor) VALUES (:empID, :cpID, :spanish_alert_yes_no, :empName, :bodyTopColor, :bodyBottomColor)";
$sql_data=array('empID'=>$empID, 'cpID'=>$cpID, 'spanish_alert_yes_no'=>$spanish_alert_yes_no, 'empName'=>$empName, 'bodyTopColor'=>$bodyTopColor, 'bodyBottomColor'=>$bodyBottomColor);
$write_query=self::write_sql($sql_string, $sql_data);
*/
if($statement->execute())
{
$last_id = $conn->lastInsertId();
//return id of record just inserted
//upload logo and update record
$target = "../Photos/";
$target = $target . basename( $_FILES['custom_header']['name']) ;
$photo_url = basename($_FILES['custom_header']['name']);
$company_logo=$_POST['company_logo'];
if(!move_uploaded_file($_FILES['custom_header']['tmp_name'], $target))
{
return("photo didn't upload");
}
else
{
$sql=$conn->prepare("update employers set
header_graphic=:photo_url,
homepage_id=:last_id,
stock_header_graphic=:company_logo
where id=:id");
$sql->bindParam(':photo_url', $photo_url, PDO::PARAM_STR);
$sql->bindParam(':id', $last_id, PDO::PARAM_INT);
$sql->bindParam(':last_id', $last_id, PDO::PARAM_INT);
$sql->bindParam(':company_logo', $company_logo, PDO::PARAM_STR);
if($sql->execute())
{
return($last_id);
}
}
}
else
{
return ("fail");
}
}
else
{
return ("fail");
}
}
}
?>
This is a sample of the API that's used with the Landing Page dynamic as it currently exists...
<?php
require_once (WEBROOT . 'lib/functions.php'); // this is the portion of the code that provides the database connection
if ($_SERVER['REQUEST_METHOD'] == "POST") { // this grabs the criteria from the incoming URL and dictates the flow of the code
$action = isset($_GET['p']) ? $_GET['p'] : '';
$displayLinks = isset($_GET['displayLinks']) ? $_GET['displayLinks'] : '';
$linkType = isset($_GET['linkType']) ? $_GET['linkType'] : '';
$spanishAlert = isset($_GET['spanishAlert']) ? $_GET['spanishAlert'] : '';
//die(var_dump($_POST, $_GET));
switch ($action) {
case 'verify':
verifyUserImproved($_POST, $displayLinks, $linkType, $spanishAlert); // this is generally the starting point where the user is getting validated and then routed to the d11 function
break;
case 'd11':
d11($_POST);
break;
// there's more to the code, but this is an abbreviated example. Initially, the user has to be verified, which is the "verifyUserImproved" function
}
}
function d11($frmData)
{
include ('custom_colors.php');
$frmData['ui'] = decrypt(decryptVals($frmData['ui'])[0], decryptVals($frmData['ui'])[1]);
global $CN;
// ONE OFF needs FIXING USED for auto populating sub-survey groups
$oneOffEmpIDArray = [
6100,
7015,
7016,
5313
];
if($frmData['ui']=="")
{
$usrID=null;
}
else
{
$usrID=$frmData['ui'];
}
$survey = findSurvey($frmData['ei'], $usrID);
$survey_guid = buildGUID(array(
'empSurveyUsername' => $survey['credUsername'],
'empSurveyPassword' => decrypt($survey['credPassIV'], $survey['credPass']),
'usrLang' => '',
'empID' => $survey['empID'],
'hID' => $survey['hID'],
'usrVerified'=>1,
'usrID' => $frmData['ui']
));
$sql = "SELECT E.empID, Ev.evEnableSignup, E.empName, X.evID, Ev.evName, C.cmpSubDomain
FROM Events_X_Employers X
INNER JOIN
Employers E
ON E.empID=X.empID
INNER JOIN
Events Ev
ON Ev.evID=X.evID
INNER JOIN
Components C
ON E.cmpID=C.cmpID
WHERE E.empID=?
AND Ev.evSignupCutoff>GETDATE()
AND Ev.evtID = 2";
$stmt = $CN->prepare($sql);
$stmt->execute(array(
$frmData['ei']
));
$es = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (! empty($es))
{
$out .= "<br><span style='color: {$prettyColor}; font-weight:bold; font-size:10pt;'>Step Three: Take Your Personal Health Survey</span>";
$out .= "<br><br><span style=\"color:{$prettyColor}; font-size:10pt; font-weight:normal;\">Click</span> <a href='https://www.personalhealthsurvey.net/login.php?{$survey_guid}' style=\"color: {$prettyColor};
font-weight:bold; font-size:10pt;\">here</a> <span style=\"color:#000000; font-size:10pt; font-weight:normal;\">to take your Personal Health Survey.</span></div>";
}
else
{
$out="<script>$('#header_text').html('There are no Screening Events scheduled at this time.<br>Proceed to your Personal Health Survey.');</script>";
$out .="<div style=\"text-align:left; display:inline-block; width:100%;\"><br><b><span style='color: {$prettyColor}; font-weight:bold; font-size:10pt;'>Step Two: There are no Screening Events scheduled at this time...</span><br>";
$out .= "<br><span style='color: {$prettyColor}; font-weight:bold; font-size:10pt;'>Step Three: Take Your Personal Health Survey</span>";
$out .= "<br><br><span style=\"color:{$prettyColor}; font-size:10pt; font-weight:normal;\">Click</span> <a href='https://www.personalhealthsurvey.net/login.php?{$survey_guid}' style=\"color: {$prettyColor};
font-weight:bold; font-size:10pt;\">here</a> <span style=\"color:#000000; font-size:10pt; font-weight:normal;\">to take your Personal Health Survey.</span></div>";
}
echo $out;
}
function verifyUserImproved(Array $params, $displayLinks, $linkType, $spanishAlert) // here's where the user is getting validated and a JQuery form dynamic is placed in a hidden div
{
global $CN;
if (isset($params['empID'])) { // the SELECT statement is being built according to the type of parameters that are being sent by the Landing Page
$params['empID'] = (int) $params['empID'];
}
if (isset($params['cpID'])) {
$params['cpID'] = (int) $params['cpID'];
}
if (isset($params['empPID'])){
$params['empPID'] = (int) $params['empPID'];
}
include ('custom_colors.php'); // custom color page that dictates color scheme according to Employee ID
$out= <<<OUT // here's the JQuery form
<input type="hidden" name="usrID" id="usrID" value="{{usrID}}">
<input type="hidden" name="usrFN" id="usrFN" value="{{usrFN}}">
<input type="hidden" name="usrLN" id="usrLN" value="{{usrLN}}">
<input type="hidden" name="usrVerified" id="usrVerified" value="{{usrVerified}}">
<br><div style="display:inline-block; border:1px solid #cccccc; width:auto: height:35px; padding:10px; font-weight:bold;">{{usrFN}} {{usrLN}} | {{usrCity}} {{usrState}}, {{usrZip}}</div>
<br><br>
<div class="buttonWrapper"><input class="btnConfirmYes" type="button" name="verifyUseryes" id="verifyUseryes" value="Yes"></div>
<div class="buttonWrapper"><input class="btnConfirmNo" type="button" name="verifyUser" id="verifyUserno" value="No"></div><br><br>
</form>
OUT;
if (isset($params['test'])) {
$testing = true;
unset($params['test']);
echo $out;
}
$where = '';
$values = [];
foreach ($params as $key => $val) { // second stage of the SELECT construction
if (end($params) != $val) {
if ($key == 'usrSSN' || $key == 'usrEEID') {
$where .= " $key LIKE ? AND ";
} else {
$where .= " $key = ? AND ";
}
} else {
if ($key == 'usrSSN' || $key == 'usrEEID') {
$where .= " $key LIKE ?";
} else {
$where .= " $key = ?";
}
}
if ($key == 'usrSSN' || $key == 'usrEEID') {
array_push($values, "%{$val}");
} else {
array_push($values, $val);
}
}
try {
$sql = "SELECT
usrID
,E.empID
,usrEEID
,usrFN
,usrLN
,usrDOB
,usrCity
,usrState
,usrZip
,usrUsername
,usrPassword
FROM hraUsers U INNER JOIN Employers E on U.empID = E.empID INNER JOIN ChannelPartners CP on E.cpID = CP.cpID
WHERE $where";
$stmt = $CN->prepare($sql);
$stmt->execute($values);
$result = $stmt->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "There was an Error verifying this user. If this continue contact the helpdesk.";
}
if (! empty($result)) {
$replace = [
'{{usrID}}',
'{{usrFN}}',
'{{usrLN}}',
'{{usrCity}}',
'{{usrState}}',
'{{usrZip}}',
'{{usrVerified}}'
];
$values = [
$result['usrID'],
$result['usrFN'],
$result['usrLN'],
$result['usrCity'],
$result['usrState'],
$result['usrZip'],
'true'
];
$out .= "<div id='extras' style='display: none'>";
ob_start();
if ($displayLinks == 'true') {
getEventsByEmployerId($result['usrDOB'], $result['usrEEID']);
}
$usrID = in_array($result['empID'], $subSurveyGroupArray) ? $result['usrID'] : null;
$survey = findSurvey($result['empID'], $usrID);
$survey_guid = buildGUID(array( // function that builds the encrypted URL user will have access to so that they can login to Personal Health Survey
'empSurveyUsername' => $survey['credUsername'],
'empSurveyPassword' => decrypt($survey['credPassIV'], $survey['credPass']),
'usrLang' => '',
'empID' => $survey['empID'],
'hID' => $survey['hID'],
'usrVerified' => 1,
'usrID' => $result['usrID']
));
if ($linkType == 'surveyevents') {
$result['usrID'] = encryptVals($result['usrID']);
$out .= <<<OUT
<script>
$('#header_text').html('Click on the "Yes" button if the name listed below is you.<br>Otherwise, click "No."'); // portion of the Landing Page that will display text depending on how the validation process resolves
$('#verifyUseryes').click(function($event){
//event.preventDefault();
$.post('api_nex.php?p=d11', {'ui':'{$result['usrID']}', 'ei':'{$result['empID']}'}, function(data){ // here's the other portion of the JQuery form dyamic where it's posting back to the same page, only now being routed to the d11 function
$('#window').show();
$('#window').html(data);
});
});
$('#verifyUserno').click(function(){
location.reload();
$('#step_1').show();
$('#window').hide();
});
</script>
OUT;
}
$out = str_replace($replace, $values, $out);
echo $out;
}
else
{
echo "<script>$('#header_text').html('<span style=\"color:red;\">We were unable to match the information you provided.<br>Please try again!</span><br><br>');$('#step_1').show();</script>"; // in case the user doesn't get validated
}
}
// functions to encrypt / decrypt data
function encryptVals($val)
{
list ($iv, $data) = encrypt($val);
$explodediv = str_split($iv, 10);
$explodedata = str_split($data, 10);
$combined = '';
for ($i = 0; $i < count($explodedata); $i ++) {
$combined .= "{$explodediv[$i]}{$explodedata[$i]}";
}
return $combined;
}
function decryptVals($passData)
{
$explodereturn = str_split($passData, 20);
$returnVals = [];
for ($i = 0; $i < count($explodereturn); $i ++) {
if ($i < 3) {
$returnVals[0] .= substr($explodereturn[$i], 0, 10);
$returnVals[1] .= substr($explodereturn[$i], - 10);
} else
if ($i == 3) {
$returnVals[0] .= substr($explodereturn[$i], 0, 2);
$returnVals[1] .= substr($explodereturn[$i], 2, 18);
} else {
$returnVals[1] .= substr($explodereturn[$i], - 20);
}
}
return $returnVals;
}
Click
here to see a screenshot of one of the "insert" screens."
As part of AHA's commitment to security, the implementation of the Singleton Class dynamic was a priority in some cases.
Rather than a "global $conn," the Singleton Class was used in conjuction with PHP's Magic Methods to create a secure database connection that was instantiated once and endured for the entirety of the user's session.
Click
here to see notes on the Singleton Class - the theory behind it and how to implement it. And while I did use this approach in the context of my work with AHA, I also used it with a Shopping Cart that built as a contract developer. You can click
here to see that page. A sample of the Singleton Class is documented below.
// here's the code that instantiates the "insertProduct"
include ("../database.php"); //here's the code that houses the database connection
$name=trim($_POST['name']);
$category_name=$category_heading;
$price=trim($_POST['price']);
$onsale=trim($_POST['onsale']);
$description=trim($_POST['description']);
$category_id=trim($_POST['category']);
$height=trim($_POST['height']);
$width=trim($_POST['width']);
$length=trim($_POST['length']);
$weight=trim($_POST['weight']);
$insert_params=array();
$insert_params=array($name, $category_name, $price, $height, $width, $length, $weight, $onsale, $description, $category_id);
$insert=DB::query("INSERT INTO products (name, categories, price, height, width, length, weight, onsale, description, category_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $insert_params, "INSERT"); //here's what calls the Singleton Class
// here's the actual Class as it's documented on the database.php page...
class DB {
private static $conn; //here's your $conn property
public static function __callStatic($name, $args=NULL){
if(!self::$conn){
// connect here
self::$conn = new PDO("mysql:dbname=forest; host=localhost", "root", '');
self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
return call_user_func_array('self::'.$name, $args);
}
private static function query($statement, $bindParams=NULL, $type=NULL, $assoc=NULL) // since this is a "private" method, PHP will throw an error, unless...you have the Magic Method of "callStatic" in place which will establish the database connection and allow the script to continue
{
private static function query($statement, $bindParams=NULL, $type=NULL, $assoc=NULL)
{
if($type=="SELECT") // this looks to see what kind of "type" of query we're doing. If it's a SELECT, we'll look to see if there are any parameters established and run it based on that criteria. If not, we'll just
run it assuming we're doing a "SELECT *"
{
if(isset($bindParams))
{
$sql=self::$conn->prepare("$statement");
$sql->execute($bindParams);
}
else
{
$sql=self::$conn->prepare("$statement");
$sql->execute();
}
$count=$sql->rowCount(); // looking to see how many elements are being retrieved...
if($count>1) // we've got more than one row / one element that's being retrieved
{
if($assoc==NULL) // the "$assoc" variable defines how the results will be retrieved, either by FETCH_ASSOC or FETCH_OBJ
{
$result=[];
while($row=$sql->fetch(PDO::FETCH_ASSOC))
{
$result[]=$row;
}
return $result;
}
else
{
$row=$sql->fetch(PDO::FETCH_OBJ);
return $row->$assoc;
}
}
if($count>0) // you don't have more than 1 element in your SELECT, nor do you have NULL, so we'll run this script and that will address both the situation where you have one row / result and that
situation where you have NULL as your result
{
if($assoc==NULL)
{
$result=[];
while($row=$sql->fetch(PDO::FETCH_ASSOC))
{
$result[]=$row;
}
return $result;
}
else
{
$row=$sql->fetch(PDO::FETCH_OBJ);
return $row->$assoc;
}
}
else
{
return 0;
}
}
else
{
$sql=self::$conn->prepare("$statement");
if($sql->execute($bindParams))
{
return 1;
}
else
{
return 0;
}
}
}
}
Another highlight was the creation of a unique JQuery / PHP solution that remedied a scenario where several PDFs were created on the fly in response to a user clicking on a "print" button. The code worked just fine right up until the point where the user was having to create hundreds of PDFs, given the fact that each one was 30 pages long. At that point, the system was timing out and throwing an error.
To solve this problem, I used a JQuery command that activated a MySQL SELECT that only grabbed 10 rows of data at a time while simultaneously displaying a popup that gave the user an idea of how many times the system would have to loop through the number of requested rows in order to complete the operation. At the conclusion of every cycle, the code redirected the process to a "blank" window which severed the connection and gave the database an opportunity to "breathe." The code then looped back around, only this time it had the id of the last row that was queried. It re-engaged the inital SELECT beginning with the last id that was retrieved and continued the process until all of the PDFs had been generated.
Click
here to see an example of what it looked like. Below is the code...
//here's the button... You would also normally do a calculation here to see how many rows you've got and divide that by 10 to see how many "laps" you're going to require
Click <a id="boom" data-laps="3" href="#">here</a>
//here's the code that gets things moving..
$('#boom').click(function() {
var laps=$(this).data('laps');
$.get("api.php?id=1&laps="+laps, function(data) {
$('.centered').show();
$('#gear_text').html(data);
});
//here/s the api.php page...
function add($id) { //it's here where you normally have your SELECT and be limiting the number of rows you're retrieving to 10 or so
$new_id=$id+1;
return $new_id;
}
$the_id=$_GET['id'];
$laps=$_GET['laps']-1;
$activate=add($the_id); //add "1" to the existing id
if($laps>0)
{
echo "<script>
$(document).ready(function() {
$.get('api_wait.php?new_id=$activate&laps=$laps', function(data) { //route the process to the "api_wait.php" page where the new id and the number of "laps" is embedded in the URL
$('.centered').show();
$('#gear_text').html(data);
});
});
</script>";
}
else
{
echo "<script>
$('.centered').show();
$('#gear_text').html('You are all set! Click <a id=\"close_print_window\" href=\"#\" style=\"font-size:9pt;\">here</a> to close this window!');
$('#close_print_window').click(function() {
$('.centered').hide();
});
</script>";
}
//...and here's the "api_wait.php" page.
$the_id=$_GET['new_id'];
$laps=$_GET['laps'];
if($laps>1)
{
echo "Stand by! You've only got $laps more laps to go!";
}
else
{
echo "Stand by! You've only got $laps more lap to go!";
}
echo "<script>
$(document).ready(function() {
setTimeout(function() {
$.get('api.php?id=$the_id&laps=$laps', function(data, status) { //send things back to the original api.php page and begin the process all over again
$('#gear_text').html(data);
});
}, 3000);
});
</script>";
Written in Node.js, this is an authentication script that utilizes a JWT (JSON Web Token) and interacts with an API (auth.js) to give a user access to a particular application.
This same code was formatted a little differently in order to accommodate a "Dockersize" dynamic when it was released to PROD. Contact me if you would like to see that code!
this is the app.js file
/*
To test this puppy, just run localhost:5000/authentication/create in tandem with Postman. You'll need the fields you see in the create.js controller. The token you'll need to authenticate will show up in your console after running the code
*/
require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const app = express();
const conn = process.env.CONNECTION;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const authRoutes = require("./routes/auth");
const createRoutes = require("./routes/create");
app.use(authRoutes);
app.use(createRoutes);
mongoose
.connect(conn)
.then(result => {
app.listen(5000);
})
.catch(err => {
console.log(err);
});
this is the auth.js file that confirms the incoming token is valid, looks at the payload and ensures the email address matches one in the database...
const superPassword = process.env.SIGNATURE;
const jwt = require("jsonwebtoken");
const User = require("../models/user");
exports.postAuthenticate = (req, res, next) => {
jwt.verify(req.token, superPassword, (err, authData) => {
res.json({
authData: authData
});
if (err) {
res.sendStatus(403);
} else {
User.findOne({ email: authData.user.email }).then(user => {
if (!user) {
res.json({
message: "nonauthenticated"
});
} else {
res.json({
message: "authenticated",
user_id: user._id
});
}
});
}
});
};
this is the create.js file that adds a user to the database for testing purposes and displays the JWT that can be used in the Postman application for testing purposes
const bcrypt = require("bcryptjs");
const { validationResult } = require("express-validator/check");
const jwt = require("jsonwebtoken");
const superPassword = process.env.SIGNATURE;
const User = require("../models/user");
exports.postCreate = (req, res, next) => {
console.log(req.body);
const first_name = req.body.first_name;
const last_name = req.body.last_name;
const user_name = req.body.user_name;
const dob = req.body.dob;
const email = req.body.email;
const password = req.body.password;
const errors = validationResult(req);
if (!errors.isEmpty()) {
res.json({
message: "form fields are not correct"
});
validationErrors: errors.array(); //send the specific errors in case it's needed
console.log(errors);
} //incoming values are legit, now we see if the incoming user is already in the database
else {
User.findOne({ email: email }) //look to see if user already exists in the database
.then(user => {
if (user) {
res.json({
message: "user already exists in database"
});
} else {
bcrypt
.hash(password, 12)
.then(hashedPassword => {
const user = new User({
first_name: first_name,
last_name: last_name,
username: user_name,
dob: dob,
email: email,
password: hashedPassword
});
return user.save();
})
.then(result => {
//the first line is commented out, although you can include it if you want to specify an expiration time
// jwt.sign({ user: user }, superPassword, { expiresIn: "30s" }, (err, token) => {
const tokenUser = {
//user minus the encrypted password
first_name: first_name,
last_name: last_name,
email: email,
username: user_name
};
jwt.sign({ user: tokenUser }, superPassword, (err, token) => {
res.json({
message: "new user is successfully created",
token: token //this is what you can use in a testing situation to ensure the JWT dynamic is working properly
});
});
})
.catch(err => {
console.log(err);
res.json({
message: "something went wrong with the insert query"
});
});
}
});
}
};
This is the OpenAPI contract that I put together for the first draft of the authentication script you see above.
openapi: 3.0.2
info:
title: Healthkit Authentication
version: "3.0.2"
description: >-
Authenticating user data as it's coming from Healthkit to Applied Health
Analytics
contact:
name: Applied Health Analytics Dev Team
url: 'https://appliedhealthanalytics.net'
email: accounts@appliedhealth.net
servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/brucegust/authentication/3.0.2
- url: 'https://appliedhealth.net/api/{majorVersion}'
description: >-
This is a placeholder for now. The actual URL will be better defined as we
move further into development.
variables:
majorVersion:
default: v1
description: The first crack at an epic API
paths:
/authentication:
description: >-
incoming JWT to be either authenticated, refused or with a response
indicating it was not an authentic JWT
servers:
- url: 'https://appliedhealth.net/'
description: URL of application
get:
responses:
'200':
description: user has been authenticated
'400':
content:
application/json:
schema:
type: string
examples:
bad JWT:
value: MG9LJiEK5_Db8WpF5cWWRebXCtU (contains an _ character)
description: bad JWT
'403':
content:
application/json:
schema:
type: string
examples:
bad username / password:
value:
username: bogus
password: mcgee
description: the username and password don't exist in the database
security:
- bearerAuth: []
summary: get JWT from user
description: >-
after process of authentication has been accomplished, the appropriate
response is made with routing user to the next part of the app or
informed that their credentials are wrong or the JWT was invalid
components:
schemas:
username:
title: Root Type for username
description: username of person attempting to access bIQ from Healthkit
type: object
properties:
username:
description: username of person attempting to access bIQ from Healthkit
type: string
password:
description: password of user attempting to access bIQ from Healthkit
type: string
example:
username: John Doe
password:
description: password of user
type: object
properties:
password:
description: password of user attempting to access bIQ from Healthkit
example:
password: Secret
responses:
'200':
description: OK
'401':
description: Not authenticated
'403':
description: Access token does not have the required scope
securitySchemes:
bearerAuth:
scheme: bearer
type: http
description: 'JWT coming in the header as Authorization bearer: '
security:
- basicAuth: []