#44 password recovery plugged in

This commit is contained in:
Ro 2021-05-06 12:45:39 -07:00
parent 9462b91246
commit 8f1e3a55e7
15 changed files with 430 additions and 151 deletions

View file

@ -60,4 +60,14 @@ class AuthAPI
]; ];
return $result; return $result;
} }
public static function requestSecret($body)
{
$result = Auth::findSecret($body);
return $result;
}
public static function resetPassword($body)
{
$result = Auth::makeNewPassword($body);
return $result;
}
} }

View file

@ -8,7 +8,18 @@ class MailerAPI
public static function handleMail($request, $body, $response) public static function handleMail($request, $body, $response)
{ {
$result = Mailer::sendmail($request, $body, $response); //if testing, verify session is active
if ($body["mail_task"] == "TESTING") {
if (Session::active()) {
$result = Mailer::sendmail($body);
} else {
$result = [
"message" => "You need to be logged in for this, champ.",
"type" => "MAILER_ERROR",
];
}
} else {
}
return $result; return $result;
} }

View file

@ -83,15 +83,15 @@ class APIControl
} }
switch (isset($args["third"]) ? $args["third"] : "none") { switch (isset($args["third"]) ? $args["third"] : "none") {
case "restore": case "restore": //move to 'api/auth'
case "init": case "init": //move to 'api/auth'
$task = $args["third"]; $task = $args["third"];
$result = InitApi::handleInitTasks( $result = InitApi::handleInitTasks(
$task, $task,
$task == "init" ? $body : $request $task == "init" ? $body : $request
); );
break; break;
case "backup": case "backup": //move to 'api/auth'
$token = $request->getHeader("fipamo-access-token"); $token = $request->getHeader("fipamo-access-token");
//Verify token for admin tasks //Verify token for admin tasks
if (Session::verifyToken($token[0])) { if (Session::verifyToken($token[0])) {
@ -103,12 +103,18 @@ class APIControl
]; ];
} }
break; break;
case "login": case "login": //move to 'api/auth'
$result = AuthAPI::login($body); $result = AuthAPI::login($body);
break; break;
case "logout": case "logout": //move to 'api/auth'
$result = AuthAPI::logout($body); $result = AuthAPI::logout($body);
break; break;
case "get-secret": //move to 'api/auth'
$result = AuthAPI::requestSecret($body);
break;
case "reset-password": //move to 'api/auth'
$result = AuthAPI::resetPassword($body);
break;
case "page": case "page":
$token = $request->getHeader("fipamo-access-token"); $token = $request->getHeader("fipamo-access-token");
//Verify token for admin tasks //Verify token for admin tasks

View file

@ -117,9 +117,14 @@ class DashControl
header("Location: /dashboard"); header("Location: /dashboard");
die(); die();
break; break;
case "reset-password":
$template = "dash/reset-password.twig";
$pageOptions = [
"title" => "Reset Password",
];
break;
default: default:
//$secret = ;
$template = "dash/start.twig"; $template = "dash/start.twig";
if (Session::active()) { if (Session::active()) {
$pageOptions = [ $pageOptions = [

View file

@ -5,70 +5,146 @@ use ReallySimpleJWT\Token;
class Auth class Auth
{ {
public function __construct() public function __construct()
{ {
} }
public static function sessionStatus() public static function sessionStatus()
{ {
if (isset($_SESSION["member"])) { if (isset($_SESSION["member"])) {
return true; return true;
} else {
return false;
}
//return $this->secret;
}
public static function status()
{
$result = "";
if (Session::active()) {
$result = true;
} else {
$result = false;
}
return $result;
}
public static function login($who)
{
//grab member list
$folks = (new Settings())->getFolks();
$found = find($folks, ["handle" => $who["handle"]]);
if ($found) {
//name is found, verify password
if (password_verify($who["password"], $found["password"])) {
$member = [
"handle" => $found["handle"],
"email" => $found["email"],
"role" => $found["role"],
"avatar" => $found["avi"],
];
$token = Token::create(
$found["id"],
$found["secret"],
time() + 3600,
"localhost"
); //expires in an hour
Session::start();
Session::set("member", $member);
Session::set("token", $token);
$result = "good_login";
} else {
$result = "bad_pass";
}
} else {
//if name is not found
$result = "no_name";
}
return $result;
}
public static function findSecret($data)
{
$result = [];
$folks = (new Settings())->getFolks();
if (
!empty($data["email"]) &&
filter_var($data["email"], FILTER_VALIDATE_EMAIL)
) {
$found = find($folks, ["email" => $data["email"]]);
if ($found) {
//if email is cool, check mail relay status
//if set up, send secret there, if not just return it
$config = new Settings();
$settings = $config->getSettings();
$email = $settings["email"]["active"];
if ($email != "option-none") {
$data["mail_task"] = "SEND_SECRET";
$data["secret"] = $found["secret"];
$result = Mailer::sendmail($data);
} else { } else {
return false; $result = [
"message" => "Valid email, but no email set up!",
"type" => "secretFound",
"secret" => $found["secret"],
];
} }
//return $this->secret; } else {
$result = [
"message" => "No valid email, no goodies, pleighboi",
"type" => "secretNotFound",
];
}
} else {
$result = [
"message" => "Aye, this address is not right, slick.",
"type" => "secretNotFound",
];
} }
public static function status() return $result;
{ }
$result = "";
if (Session::active()) { public static function makeNewPassword($data)
$result = true; {
} else { //check if passwordsmatch
$result = false; if ($data["newPass"] == $data["newPassConfirm"]) {
} //verify secret
return $result; $folks = (new Settings())->getFolks();
$found = find($folks, ["secret" => $data["secret"]]);
if ($found) {
//create new pass and secret key, then update file
$hash = password_hash($data["newPass"], PASSWORD_DEFAULT);
$freshSecret = StringTools::randomString(12);
Member::updateData("password", $hash, $data["secret"]);
Member::updateData("secret", $freshSecret, $data["secret"]);
$result = [
"message" => "Password Updated. Very nice!",
"type" => "passCreated",
];
} else {
$result = [
"message" => "Secret key is invalid. Try to retrieve it again",
"type" => "passNotCreated",
];
}
} else {
$result = [
"message" => "Passwords don't match. Try it again.",
"type" => "passNotCreated",
];
} }
public static function login($who) return $result;
{ }
//grab member list
$folks = (new Settings())->getFolks();
$found = find($folks, ["handle" => $who["handle"]]);
if ($found) { public static function logout()
//name is found, verify password {
if (password_verify($who["password"], $found["password"])) { Session::kill();
$member = [ }
"handle" => $found["handle"],
"email" => $found["email"],
"role" => $found["role"],
"avatar" => $found["avi"],
];
$token = Token::create(
$found["id"],
$found["secret"],
time() + 3600,
"localhost"
); //expires in an hour
Session::start();
Session::set("member", $member);
Session::set("token", $token);
$result = "good_login";
} else {
$result = "bad_pass";
}
} else {
//if name is not found
$result = "no_name";
}
return $result;
}
public static function logout()
{
Session::kill();
}
} }

View file

@ -7,11 +7,15 @@ class Member
{ {
} }
public static function updateData(string $key, string $data) public static function updateData(string $key, string $data, $secret = null)
{ {
$folks = (new Settings())->getFolks(); $folks = (new Settings())->getFolks();
$member = Session::get("member"); if (isset($secret)) {
$found = find($folks, ["handle" => $member["handle"]]); $found = find($folks, ["secret" => $secret]);
} else {
$member = Session::get("member");
$found = find($folks, ["handle" => $member["handle"]]);
}
$found[$key] = $data; $found[$key] = $data;
//record time updated //record time updated
$updated = new \Moment\Moment(); $updated = new \Moment\Moment();
@ -22,12 +26,14 @@ class Member
DocTools::writeSettings("../config/folks.json", $newFolks); DocTools::writeSettings("../config/folks.json", $newFolks);
//update member data in session //update member data in session
$member = [ if (!isset($secret)) {
"handle" => $found["handle"], $member = [
"email" => $found["email"], "handle" => $found["handle"],
"role" => $found["role"], "email" => $found["email"],
"avatar" => $found["avi"], "role" => $found["role"],
]; "avatar" => $found["avi"],
Session::set("member", $member); ];
Session::set("member", $member);
}
} }
} }

View file

@ -6,26 +6,42 @@ use PHPMailer\PHPMailer\Exception;
class Mailer class Mailer
{ {
public static function sendMail($request, $body, $response) public static function sendMail($body)
{ {
$config = new Settings(); $config = new Settings();
$settings = $config->getSettings(); $settings = $config->getSettings();
$mailConfig = $settings["email"]; $mailConfig = $settings["email"];
if ($body["mail_task"] == "TESTING") {
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>It's just a test</strong><br>" .
$body["content"];
} else {
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>Really cool content and stuff</strong><br>" .
$body["content"];
}
$mail = new PHPMailer(); $mail = new PHPMailer();
switch ($body["mail_task"]) {
case "TESTING":
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>It's just a test</strong><br>" .
$body["content"];
$member = Session::get("member");
$mail->addAddress($member["email"], ""); //pull email address from current user
$mail->Subject = "A test email";
break;
case "SEND_SECRET":
$html =
"<h1>Hi! It's Fipamo!</h1><br>" .
"<strong>This is your secret key.</strong><br><br>" .
"<h3>" .
$body["secret"] .
"</h3>" .
"<br> Use this key to reset your password.";
$mail->addAddress($body["email"], ""); //pull email address from current user
$mail->Subject = "Shhhh! It's a secret!";
break;
default:
return $result = [
"type" => "noMailService",
"message" => "Mail task is undefined. What are you doing??",
];
break;
}
//set values based on current active protocol //set values based on current active protocol
switch ($mailConfig["active"]) { switch ($mailConfig["active"]) {
case "option-smtp": case "option-smtp":
@ -55,8 +71,6 @@ class Mailer
$mail->isSMTP(); $mail->isSMTP();
$mail->SMTPAuth = true; $mail->SMTPAuth = true;
$mail->SMTPSecure = "ssl"; $mail->SMTPSecure = "ssl";
$mail->addAddress("services@playvicio.us", ""); //pull email address from current user
$mail->Subject = "A test email";
$mail->Port = 465; $mail->Port = 465;
// Uncomment for debug info // Uncomment for debug info
@ -67,8 +81,12 @@ class Mailer
$mail->send(); $mail->send();
$result = ["type" => "mailSent", "message" => "Message Away!"]; $result = ["type" => "mailSent", "message" => "Message Away!"];
} catch (Exception $e) { } catch (Exception $e) {
echo $e->errorMessage(); //echo $e->errorMessage();
$result = ["type" => "mailNotSent", "message" => "Message Not Away!"]; $result = [
"type" => "mailNotSent",
"message" => "Message Not Away!",
"error" => $e->errorMessage(),
];
} }
return $result; return $result;

View file

@ -1,11 +1,12 @@
<div id="dash-login"> <div id="dash-login">
<div id="dash-form" class="dash-form"> <div id="dash-form" class="dash-form">
<form id="login" class='login' , name="login" action="/@/dashboard/login" method="POST"> <form id="login" class='login' name="login" action="/@/dashboard/login" method="POST">
<input type="text" name="handle" class="form-control" placeholder="Handle" required "> <input type="text" name="handle" class="form-control" placeholder="Handle" required ">
<input type="password" name="password" class="form-control" placeholder="Password" required"> <input type="password" name="password" class="form-control" placeholder="Password" required">
<button id="login-btn" , class='login-btn' , type='submit'> <button id="login-btn" class='login-btn' type='submit'>
Let's see some ID Let's see some ID
</button> </button><br /><br />
<a href="/dashboard/reset-password"> Forgot Password?</a>
</form> </form>
</div> </div>
</div> </div>

View file

@ -0,0 +1,40 @@
{% extends "dash/_frame.twig" %}
{% block title %}
{{ title }}
{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=dfdfdf">
{% endblock %}
{% block mainContent %}
<div id="dash-index">
<div id="dash-index-wrapper">
<div id="dash-login">
<div id="dash-reset" class="dash-reset">
<form id="reset" class='login' name="reset" action="/@/dashboard/login" method="POST">
<input type="password" id="new_password"name="new_password" class="form-control" placeholder="New Password" required">
<input type="password" id="new_password2" name="new_password2" class="form-control" placeholder="New Password Confirm" required">
<input type="password" id="secret" name="secret" class="form-control" placeholder="Account Secret" required">
<button id="reset-btn" class='login-btn' type='submit'>
Update Password
</button><br /><br />
<p>
Use this to get your secret to verify it's you. If your email is set up, the secret will be sent there. If not, the form will be updated automatically(but please set up your email, once you reset your password).
</p>
<input type="text"id="email" name="email" class="form-control" placeholder="Verify Email" required">
<button id="get-secret-btn" class='login-btn' type='submit'>
Retrieve Secret
</button><br /><br />
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block javascripts %}
<script src="/assets/scripts/dash.min.js" type="text/javascript"></script>
{% endblock %}

View file

@ -2154,18 +2154,19 @@ svg.icons {
max-width: 900px; max-width: 900px;
margin: 0 auto; margin: 0 auto;
} }
#dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form { #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form, #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-reset {
width: 300px; width: 300px;
padding: 0.75em; padding: 0.75em;
background: #374857; background: #374857;
border-radius: 3px; border-radius: 3px;
color: #f2f1ef;
} }
#dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form input { #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form input, #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-reset input {
width: 290px; width: 290px;
margin: 0 0 10px 0; margin: 0 0 10px 0;
height: 30px; height: 30px;
} }
#dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form button { #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-form button, #dash-index-content #dash-index #dash-index-wrapper #dash-login #dash-reset button {
width: 300px; width: 300px;
} }
#dash-index-content #dash-index #dash-index-wrapper #dash-menu { #dash-index-content #dash-index #dash-index-wrapper #dash-menu {

File diff suppressed because one or more lines are too long

View file

@ -40,6 +40,14 @@ export default class Base {
.getElementById("blog-restore") .getElementById("blog-restore")
.addEventListener("click", (e) => this.handleRestore(e)); .addEventListener("click", (e) => this.handleRestore(e));
} }
} else if (document.getElementById("dash-reset")) {
document
.getElementById("get-secret-btn")
.addEventListener("click", (e) => this.handleReset(e));
document
.getElementById("reset-btn")
.addEventListener("click", (e) => this.handleReset(e));
} else { } else {
new DashManager(); new DashManager();
} }
@ -123,7 +131,7 @@ export default class Base {
notify.alert(response.message, true); notify.alert(response.message, true);
setTimeout(() => { setTimeout(() => {
window.location = "/dashboard"; window.location = "/dashboard";
}, 700); }, 800);
} }
}) })
.catch((err) => { .catch((err) => {
@ -131,6 +139,62 @@ export default class Base {
notify.alert(err, false); notify.alert(err, false);
}); });
} }
handleReset(e) {
e.stopPropagation();
e.preventDefault();
let self = this;
let api = new FipamoApi();
if (e.target.id == "get-secret-btn") {
let data = {
email: document.getElementById("email").value,
task: "retrieveSecret",
};
this.processing = true;
api
.getSecret(data)
.then((response) => {
self.processing = false;
if (response.secret) {
document.getElementById("secret").value = response.secret;
notify.alert(response.message, true);
} else {
if (response.type == "mailSent") {
notify.alert(response.message, true);
} else {
notify.alert(response.message, false);
}
}
})
.catch((err) => {
self.processing = false;
notify.alert(err, false);
});
} else {
let data = {
newPass: document.getElementById("new_password").value,
newPassConfirm: document.getElementById("new_password2").value,
secret: document.getElementById("secret").value,
};
api
.setNewPass(data)
.then((response) => {
self.processing = false;
if (response.type == "passNotCreated") {
notify.alert(response.message, false);
} else {
notify.alert(response.message, true);
setTimeout(() => {
window.location = "/dashboard";
}, 1000);
}
})
.catch((err) => {
self.processing = false;
notify.alert(err, false);
});
}
}
handleOptions(e) { handleOptions(e) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();

View file

@ -1,49 +1,51 @@
export const AUTH_STATUS = 'getAuthStatus'; export const AUTH_STATUS = "getAuthStatus";
export const REQUEST_GOOD = 'requestGood'; export const REQUEST_GOOD = "requestGood";
export const REQUEST_LAME = 'requestLame'; export const REQUEST_LAME = "requestLame";
export const API_REQUEST_GOOD = 'apiUseAuthorized'; export const API_REQUEST_GOOD = "apiUseAuthorized";
export const API_REQUEST_LAME = 'apiUseNotAuthorized'; export const API_REQUEST_LAME = "apiUseNotAuthorized";
export const IMG_REQUEST_GOOD = 'imgRequestGood'; export const IMG_REQUEST_GOOD = "imgRequestGood";
export const IMG_REQUEST_LAME = 'imgRequestLame'; export const IMG_REQUEST_LAME = "imgRequestLame";
export const SETTINGS_LOADED = 'settingsLoaded'; export const SETTINGS_LOADED = "settingsLoaded";
export const POST_IMAGE_ADDED = 'postImageAdded'; export const POST_IMAGE_ADDED = "postImageAdded";
export const FEATURE_IMAGE_ADDED = 'featureImageAdded'; export const FEATURE_IMAGE_ADDED = "featureImageAdded";
export const PAGE_ERROR = 'postError'; export const PAGE_ERROR = "postError";
export const PAGE_ADDED = 'postAdded'; export const PAGE_ADDED = "postAdded";
export const PAGE_UPDATED = 'postUpdated'; export const PAGE_UPDATED = "postUpdated";
export const PAGE_DELETED = 'postImageAdded'; export const PAGE_DELETED = "postImageAdded";
export const PAGES_RENDERED = 'pagesRendered'; export const PAGES_RENDERED = "pagesRendered";
export const PAGES_NOT_RENDERED = 'pagesNotRendered'; export const PAGES_NOT_RENDERED = "pagesNotRendered";
export const TAG_PAGES_RENDERED = 'tagPagesRendered'; export const TAG_PAGES_RENDERED = "tagPagesRendered";
export const TAG_PAGES_NOT_RENDERED = 'tagPagesNotRendered'; export const TAG_PAGES_NOT_RENDERED = "tagPagesNotRendered";
export const SETTINGS_UPDATED = 'settingsUpdated'; export const SETTINGS_UPDATED = "settingsUpdated";
export const SETTINGS_NOT_UPDATED = 'settingsNotUpdated'; export const SETTINGS_NOT_UPDATED = "settingsNotUpdated";
export const MENU_ADD_ITEM = 'menuAddItem'; export const MENU_ADD_ITEM = "menuAddItem";
export const MENU_DELETE_ITEM = 'menuDeleteItem'; export const MENU_DELETE_ITEM = "menuDeleteItem";
export const MENU_UPDATED = 'menuUpdated'; export const MENU_UPDATED = "menuUpdated";
export const AVATAR_UPLOADED = 'avatarUploaded'; export const AVATAR_UPLOADED = "avatarUploaded";
export const SITE_BACKGROUND_UPLOADED = 'siteBackgroundUploaded'; export const SITE_BACKGROUND_UPLOADED = "siteBackgroundUploaded";
export const UPLOAD_PROGRESS = 'uploadProgress'; export const UPLOAD_PROGRESS = "uploadProgress";
export const API_PAGE_WRITE = 'writingItDown'; export const API_PAGE_WRITE = "writingItDown";
export const API_PAGE_CREATE = 'writingNewEntry'; export const API_PAGE_CREATE = "writingNewEntry";
export const API_PAGE_DELETE = 'erasingPage'; export const API_PAGE_DELETE = "erasingPage";
export const API_SETTINGS_WRITE = 'savingSettings'; export const API_SETTINGS_WRITE = "savingSettings";
export const API_BACKUP_CREATE = 'createBackup'; export const API_BACKUP_CREATE = "createBackup";
export const API_BACKUP_DOWNLOAD = 'downloadBackup'; export const API_BACKUP_DOWNLOAD = "downloadBackup";
export const API_BACKUP_RESTORE = 'downloadBackup'; export const API_BACKUP_RESTORE = "downloadBackup";
export const API_IMAGES_UPLOAD = 'uploadProfileImages'; export const API_IMAGES_UPLOAD = "uploadProfileImages";
export const API_RENDER_PAGES = 'renderPages'; export const API_RENDER_PAGES = "renderPages";
export const API_REINDEX_PAGES = 'reindexPages'; export const API_REINDEX_PAGES = "reindexPages";
export const API_INIT = 'blogInit'; export const API_INIT = "blogInit";
export const API_INIT_GOOD = 'blogInitGood'; export const API_INIT_GOOD = "blogInitGood";
export const API_INIT_LAME = 'blogInitLame'; export const API_INIT_LAME = "blogInitLame";
export const SEND_MAIL = 'sendMail'; export const API_GET_SECRET = "retrieveSecret";
export const API_RESET_PASS = "resetPassword";
export const SEND_MAIL = "sendMail";
class DataEvent { class DataEvent {
//-------------------------- //--------------------------
// methods // methods
//-------------------------- //--------------------------
//-------------------------- //--------------------------
// event handlers // event handlers
//-------------------------- //--------------------------
} }
export default new DataEvent(); export default new DataEvent();

View file

@ -10,6 +10,8 @@ export const API_RESTORE = "/api/v1/restore";
export const API_LOGIN = "/api/v1/login"; export const API_LOGIN = "/api/v1/login";
export const API_GET_PAGES = "/api/v1/page/published"; export const API_GET_PAGES = "/api/v1/page/published";
export const API_GET_PAGE = "/api/v1/page/single"; export const API_GET_PAGE = "/api/v1/page/single";
export const API_GET_SECRET = "/api/v1/get-secret";
export const API_RESET_PASS = "/api/v1/reset-password";
import * as DataEvent from "../com/events/DataEvent"; import * as DataEvent from "../com/events/DataEvent";
export default class FipamoAPI { export default class FipamoAPI {
//-------------------------- //--------------------------
@ -90,6 +92,42 @@ export default class FipamoAPI {
}); });
} }
getSecret(data) {
return new Promise((resolve, reject) => {
this._request(
API_GET_SECRET,
DataEvent.API_GET_SECRET,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then((result) => {
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}
setNewPass(data) {
return new Promise((resolve, reject) => {
this._request(
API_RESET_PASS,
DataEvent.API_RESET_PASS,
REQUEST_TYPE_POST,
CONTENT_TYPE_JSON,
data
)
.then((result) => {
resolve(result);
})
.catch((err) => {
reject(err);
});
});
}
getPage(id) { getPage(id) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._request( this._request(

View file

@ -49,11 +49,12 @@
max-width: 900px max-width: 900px
margin: 0 auto margin: 0 auto
#dash-form #dash-form, #dash-reset
width: 300px width: 300px
padding: 0.75em padding: 0.75em
background: $primary background: $primary
border-radius: 3px border-radius: 3px
color: $white
input input
width: 290px width: 290px