Add Auth0, partial support

This commit is contained in:
2024-06-21 19:01:12 -04:00
parent 41853a7645
commit 4aa6e832c7
18 changed files with 2756 additions and 55 deletions

53
src/lib/Auth.php Normal file
View File

@@ -0,0 +1,53 @@
<?php declare(strict_types=1);
namespace MyPrayerJournal;
use Auth0\SDK\Auth0;
use Auth0\SDK\Exception\ConfigurationException;
class Auth
{
private static ?Auth0 $auth0 = null;
public static function client(): Auth0
{
if (is_null(self::$auth0)) {
self::$auth0 = new Auth0([
'domain' => $_ENV['AUTH0_DOMAIN'],
'clientId' => $_ENV['AUTH0_CLIENT_ID'],
'clientSecret' => $_ENV['AUTH0_CLIENT_SECRET'],
'cookieSecret' => $_ENV['AUTH0_COOKIE_SECRET']
]);
}
return self::$auth0;
}
/**
* Initiate a log on with Auth0
*
* @throws ConfigurationException If the Auth0 client is not configured correctly
*/
public static function logOn(): never
{
$params = match (true) {
$_SERVER['PHP_SELF'] <> '/user/log-on.php' => ['redirectUri' => $_SERVER['PHP_SELF']],
default => []
};
self::client()->clear();
header('Location: ' . self::client()->login($_ENV['AUTH0_BASE_URL'] . '/user/log-on/success', $params));
exit;
}
/**
* Log off from this application and Auth0
*
* @throws ConfigurationException If the Auth0 client is not configured correctly
*/
public static function logOff(): never
{
session_destroy();
header('Location: ' . self::client()->logout($_ENV['AUTH0_BASE_URL']));
exit;
}
}

117
src/lib/Layout.php Normal file
View File

@@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace MyPrayerJournal;
class Layout
{
/**
* Create the `DOCTYPE` declaration, `html`, and `head` tags for the page
*
* @param string $title The title of the page
*/
public static function htmlHead(string $title): void
{
if (is_htmx()) {
echo "<!DOCTYPE html><html lang=en><head lang=en><title>$title &#xab; myPrayerJournal</title></head>";
} else {
echo <<<HEAD
<!DOCTYPE html>
<html lang=en>
<head>
<meta name=viewport content="width=device-width, initial-scale=1">
<meta name=description content="Online prayer journal - free w/Google or Microsoft account">
<title>$title &#xab; myPrayerJournal</title>
<link href=https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css rel=stylesheet
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin=anonymous>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel=stylesheet>
<link href=/style/style.css rel=stylesheet>
</head>
HEAD;
}
}
private static function navLink(string $url, string $text): void
{
$classAttr = match (true) {
str_starts_with($_SERVER['PHP_SELF'], $url) => ['class' => 'is-active-route'],
default => []
};
echo '<li class=nav-item>';
page_link($url, $text, $classAttr);
}
/**
* The default navigation bar, which will load the items on page load, and whenever a refresh event occurs
*/
public static function navBar(): void
{ ?>
<nav class="navbar navbar-dark" role="navigation">
<div class=container-fluid><?php
page_link('/', '<span class=m>my</span><span class=p>Prayer</span><span class=j>Journal</span>',
['class' => 'navbar-brand']); ?>
<ul class="navbar-nav me-auto d-flex flex-row"><?php
if (key_exists('user_id', $_SESSION)) {
self::navLink('/journal', 'Journal');
self::navLink('/requests/active', 'Active');
if (key_exists('has_snoozed', $_SESSION)) self::navLink('/requests/snoozed', 'Snoozed');
self::navLink('/requests/answered', 'Answered'); ?>
<li class=nav-item><a href=/user/log-off>Log Off</a><?php
} else { ?>
<li class=nav-item><a href=/user/log-on>Log On</a><?php
}
self::navLink('/docs', 'Docs'); ?>
</ul>
</div>
</nav><?php
}
/**
* Drop .0 or .0.0 from the end of the version to format it for display
*
* @return string The version of the application for user display
*/
private static function displayVersion(): string {
[$major, $minor, $rev] = explode('.', MPJ_VERSION);
$minor = $minor == '0' ? '' : ".$minor";
$rev = match (true) {
$rev == '0' => '',
str_starts_with($rev, '0-') => substr($rev, 1),
default => ".$rev"
};
return "v$major$minor$rev";
}
/**
* Create the footer
*/
public static function htmlFoot(): void
{ ?>
<footer class=container-fluid>
<p class="text-muted text-end">
myPrayerJournal <?=self::displayVersion();?><br>
<em><small><?php
page_link('/legal/privacy-policy', 'Privacy Policy');
echo ' &bull; ';
page_link('/legal/terms-of-service', 'Terms of Service');
echo ' &bull; '; ?>
<a href=https://git.bitbadger.solutions/bit-badger/myPrayerJournal target=_blank
rel=noopener>Developed</a> and hosted by
<a href=https://bitbadger.solutions target=_blank rel=noopener>Bit Badger Solutions</a>
</small></em>
<script src=https://unpkg.com/htmx.org@2.0.0 crossorigin=anonymous
integrity="sha384-wS5l5IKJBvK6sPTKa2WZ1js3d947pvWXbPJ1OmWfEuxLgeHcEbjUUA5i9V5ZkpCw"></script>
<script>if (!htmx) document.write('<script src=\"/script/htmx.min.js\"><\/script>')</script>
<script async src=https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin=anonymous></script>
<script>
setTimeout(function () {
if (!bootstrap) document.write('<script src=\"/script/bootstrap.bundle.min.js\"><\/script>')
}, 2000)
</script>
<script src=/script/mpj.js></script>
</footer><?php
}
}

View File

@@ -2,6 +2,8 @@
namespace MyPrayerJournal;
use BitBadger\PDODocument\DocumentException;
/**
* A note entered on a prayer request
*/
@@ -12,4 +14,17 @@ class Note
* @param string $text The text of the note
*/
public function __construct(public string $asOf, public string $text) { }
// AFU2SCY5X2BNVRXP6W47D369
/**
* Retrieve notes for a given request
*
* @param string $id The ID of the request for which notes should be retrieved
* @return array|Note[] The notes for the request, or an empty array if the request was not found
* @throws DocumentException If any is encountered
*/
public static function byRequestId(string $id): array
{
$req = Request::byId($id);
return $req ? $req->notes : [];
}
}

View File

@@ -2,6 +2,7 @@
namespace MyPrayerJournal;
use BitBadger\PDODocument\{DocumentException, Find};
use Exception;
use Visus\Cuid2\Cuid2;
@@ -30,4 +31,17 @@ class Request
$this->id = (new Cuid2())->toString();
}
}
/**
* Find a request by its ID
*
* @param string $id The ID of the request
* @return Request|false The request if it is found and belongs to the current user, false if not
* @throws DocumentException If any is encountered
*/
public static function byId(string $id): Request|false
{
$req = Find::byId(Table::REQUEST, $id, self::class);
return ($req && $req->userId == $_SESSION['user_id']) ? $req : false;
}
}

12
src/lib/Table.php Normal file
View File

@@ -0,0 +1,12 @@
<?php declare(strict_types=1);
namespace MyPrayerJournal;
/**
* Constants for table names
*/
class Table
{
/** @var string The prayer request table used by myPrayerJournal */
const REQUEST = 'request';
}