forked from ChurchCRM/CRM
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add new react app and Slim tempaltes for 2fa enrollemnt
- Loading branch information
1 parent
ef7e8f8
commit 5ceddc7
Showing
7 changed files
with
338 additions
and
1 deletion.
There are no files selected for viewing
267 changes: 267 additions & 0 deletions
267
react/components/UserSecurity/UserTwoFactorEnrollment.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
import * as React from 'react'; | ||
import CRMRoot from '../../window-context-service.jsx'; | ||
|
||
const TwoFAEnrollmentWelcome: React.FunctionComponent<{nextButtonEventHandler: Function}> = ({ nextButtonEventHandler}) => { | ||
return ( | ||
<div> | ||
<div className="col-lg-12"> | ||
<div className="box" id="TwoFAEnrollmentSteps"> | ||
<div className="box-body"> | ||
<p>{window.i18next.t("Enrolling your ChurchCRM user account in Two Factor Authention provides an additional layer of defense against bad actors trying to access your account.")}</p> | ||
<p>{window.i18next.t("ChurchCRM Two factor supports any TOTP authenticator app, so you're free to choose between Microsoft Authenticator, Google Authenticator, Authy, LastPass, and others")}</p> | ||
<hr/> | ||
<div className="col-lg-4"> | ||
<i className="fa fa-id-card-o"></i> | ||
<p>{window.i18next.t("When you sign in to ChurchCRM, you'll still enter your username and password like normal")}</p> | ||
</div> | ||
<div className="col-lg-4"> | ||
<i className="fa fa-key"></i> | ||
<p>{window.i18next.t("However, you'll also need to supply a one-time code from your authenticator device to complete your login")}</p> | ||
</div> | ||
<div className="col-lg-4"> | ||
<i className="fa fa-check-square-o"></i> | ||
<p>{window.i18next.t("After successfully entering both your credentials, and the one-time code, you'll be logged in as normal")}</p> | ||
</div> | ||
<div className="clearfix"></div> | ||
<div className="callout callout-warning"> | ||
<p>{window.i18next.t("To prevent being locked out of your ChurchCRM account, please ensure you're ready to complete two factor enrollment before clicking begin")}</p> | ||
</div> | ||
<ul> | ||
<li>{window.i18next.t("Beginning enrollment will invalidate any previously enrolled 2 factor devices and recovery codes.")}</li> | ||
<li>{window.i18next.t("When you click next, you'll be prompted to scan a QR code to enroll your authenticator app.")}</li> | ||
<li>{window.i18next.t("To confirm enrollment, you'll need to enter the code generated by your authenticator app")}</li> | ||
<li> | ||
{window.i18next.t("After confirming app enrollment, single-use recovery codes will be generated and displayed.")} | ||
<ul> | ||
<li>{window.i18next.t("Recovery codes can be used instead of a code generated from your authenticator app.")}</li> | ||
<li>{window.i18next.t("Store these in a secure location")}</li> | ||
</ul> | ||
</li> | ||
</ul> | ||
|
||
|
||
<div className="clearfix"></div> | ||
<button className="btn btn-success" onClick={() => {nextButtonEventHandler()}}>{window.i18next.t("Begin Two Factor Authentication Enrollment")}</button> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
) | ||
} | ||
|
||
const TwoFAEnrollmentGetQR: React.FunctionComponent<{TwoFAQRCodeDataUri: string, newQRCode:Function, remove2FA:Function, validationCodeChangeHandler:(event: React.ChangeEvent<HTMLInputElement>) => void, currentTwoFAPin?:string, currentTwoFAPinStatus:string }> = ({TwoFAQRCodeDataUri, newQRCode, remove2FA, validationCodeChangeHandler, currentTwoFAPin, currentTwoFAPinStatus}) => { | ||
return ( | ||
<div> | ||
<div className="col-lg-12"> | ||
<div className="box"> | ||
<div className="box-header"> | ||
<h4>{window.i18next.t("2 Factor Authentication Secret")}</h4> | ||
</div> | ||
<div className="box-body"> | ||
<div className="col-lg-6"> | ||
<img src={TwoFAQRCodeDataUri} /> | ||
</div> | ||
<div className="col-lg-6"> | ||
<div className="row"> | ||
<div className="col-lg-6"> | ||
<button className="btn btn-warning" onClick={() => {newQRCode()}}>{window.i18next.t("Regenerate 2 Factor Authentication Secret")}</button> | ||
</div> | ||
<div className="col-lg-6"> | ||
<button className="btn btn-warning" onClick={() => {remove2FA()}}>{window.i18next.t("Remove 2 Factor Authentication Secret")}</button> | ||
</div> | ||
</div> | ||
<div className="row"> | ||
<div className="col-lg-12"> | ||
<label> | ||
{window.i18next.t("Enter TOTP code to confirm enrollment")}: | ||
<input onChange={validationCodeChangeHandler} value={currentTwoFAPin} autoFocus /> | ||
</label> | ||
<p>{currentTwoFAPinStatus}</p> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
} | ||
|
||
const TwoFAEnrollmentSuccess: React.FunctionComponent<{TwoFARecoveryCodes?: string[]}> = ({ TwoFARecoveryCodes}) => { | ||
return ( | ||
<div className="col-lg-12"> | ||
<div className="box"> | ||
<div className="box-header"> | ||
<h4>{window.i18next.t("2 Factor Authentication Enrollment Success")}</h4> | ||
</div> | ||
<div className="box-body"> | ||
<p>{window.i18next.t("Please store these recovery codes in a safe location")}</p> | ||
<p>{window.i18next.t("If you ever lose access to your newly enrolled authenticator app, you'll need to use a recovery code to gain access to your account")}</p> | ||
<ul>{TwoFARecoveryCodes.length ? (TwoFARecoveryCodes.map((code) => <li>{code}</li>)) : <p>waiting</p>}</ul> | ||
</div> | ||
</div> | ||
</div> | ||
) | ||
}; | ||
|
||
|
||
class UserTwoFactorEnrollment extends React.Component<TwoFactorEnrollmentProps, TwoFactorEnrollmentState> { | ||
constructor(props: TwoFactorEnrollmentProps) { | ||
super(props); | ||
|
||
this.state = { | ||
currentView: "intro", | ||
TwoFARecoveryCodes: [] | ||
} | ||
|
||
this.nextButtonEventHandler = this.nextButtonEventHandler.bind(this); | ||
this.requestNew2FABarcode = this.requestNew2FABarcode.bind(this); | ||
this.remove2FAForuser = this.remove2FAForuser.bind(this); | ||
this.validationCodeChangeHandler = this.validationCodeChangeHandler.bind(this); | ||
this.requestNew2FARecoveryCodes = this.requestNew2FARecoveryCodes.bind(this); | ||
} | ||
|
||
nextButtonEventHandler() { | ||
this.requestNew2FABarcode(); | ||
this.setState({ | ||
currentView:"BeginEnroll" | ||
}); | ||
} | ||
|
||
requestNew2FABarcode() { | ||
fetch(CRMRoot + '/api/user/current/refresh2fasecret', { | ||
credentials: "include", | ||
method: "POST", | ||
headers: { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
}, | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
this.setState({ TwoFAQRCodeDataUri: data.TwoFAQRCodeDataUri }) | ||
}); | ||
} | ||
|
||
requestNew2FARecoveryCodes() { | ||
fetch(CRMRoot + '/api/user/current/refresh2farecoverycodes', { | ||
credentials: "include", | ||
method: "POST", | ||
headers: { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
}, | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
this.setState({ TwoFARecoveryCodes: data.TwoFARecoveryCodes }) | ||
}); | ||
} | ||
|
||
remove2FAForuser() { | ||
fetch(CRMRoot + '/api/user/current/remove2fasecret', { | ||
credentials: "include", | ||
method: "POST", | ||
headers: { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
}, | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
this.setState({ | ||
TwoFAQRCodeDataUri: "", | ||
currentView: "intro" | ||
}) | ||
}); | ||
} | ||
|
||
validationCodeChangeHandler(event: React.ChangeEvent<HTMLInputElement> ) { | ||
this.setState({ | ||
currentTwoFAPin: event.currentTarget.value | ||
}); | ||
if(event.currentTarget.value.length == 6) { | ||
console.log("Checking for valid pin"); | ||
fetch(CRMRoot + "/api/user/current/test2FAEnrollmentCode", { | ||
credentials: "include", | ||
method: "POST", | ||
headers: { | ||
Accept: 'application/json', | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({enrollmentCode: event.currentTarget.value}) | ||
}) | ||
.then(response => response.json()) | ||
.then(data => { | ||
if (data.IsEnrollmentCodeValid ) { | ||
this.requestNew2FARecoveryCodes(); | ||
this.setState({ | ||
currentView: "success" | ||
}); | ||
} | ||
else{ | ||
this.setState({ | ||
currentTwoFAPinStatus: "invalid" | ||
}); | ||
} | ||
|
||
}); | ||
this.setState({ | ||
currentTwoFAPinStatus: "pending" | ||
}) | ||
|
||
} | ||
else{ | ||
this.setState({ | ||
currentTwoFAPinStatus: "incomplete" | ||
}) | ||
} | ||
} | ||
|
||
|
||
render() { | ||
if (this.state.currentView === "intro") { | ||
return ( | ||
<div> | ||
<div className="row"> | ||
<TwoFAEnrollmentWelcome nextButtonEventHandler = { this.nextButtonEventHandler} /> | ||
</div> | ||
</div > | ||
); | ||
} | ||
else if(this.state.currentView === "BeginEnroll") { | ||
return ( | ||
<div> | ||
|
||
<div className="row"> | ||
<TwoFAEnrollmentGetQR TwoFAQRCodeDataUri={this.state.TwoFAQRCodeDataUri} newQRCode={this.requestNew2FABarcode} remove2FA={this.remove2FAForuser} validationCodeChangeHandler={this.validationCodeChangeHandler } currentTwoFAPin={this.state.currentTwoFAPin} currentTwoFAPinStatus={this.state.currentTwoFAPinStatus} /> | ||
</div> | ||
</div > | ||
); | ||
} | ||
else if(this.state.currentView === "success") { | ||
return ( | ||
<TwoFAEnrollmentSuccess TwoFARecoveryCodes={this.state.TwoFARecoveryCodes} /> | ||
) | ||
} | ||
else { | ||
return ( | ||
<h4>Uh-oh</h4> | ||
) | ||
} | ||
} | ||
} | ||
|
||
interface TwoFactorEnrollmentProps { | ||
|
||
} | ||
|
||
interface TwoFactorEnrollmentState { | ||
currentView: string, | ||
TwoFAQRCodeDataUri?: string, | ||
currentTwoFAPin?: string, | ||
currentTwoFAPinStatus?: string, | ||
TwoFARecoveryCodes: string[] | ||
} | ||
export default UserTwoFactorEnrollment; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import * as React from 'react'; | ||
import * as ReactDOM from 'react-dom'; | ||
import UserTwoFactorEnrollment from './components/UserSecurity/UserTwoFactorEnrollment'; | ||
|
||
declare global { | ||
interface Window { | ||
// React does have it's own i18next implementation, but for now, lets use the one that's already being loaded | ||
i18next: { | ||
t(string): string | ||
} | ||
} | ||
} | ||
$(document).ready( function() { | ||
ReactDOM.render(<UserTwoFactorEnrollment />, document.getElementById('two-factor-enrollment-react-app')); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#TwoFAEnrollmentSteps div.col-lg-4 { | ||
text-align: center; | ||
} | ||
|
||
#TwoFAEnrollmentSteps div.col-lg-4 i{ | ||
font-size: 100px | ||
} | ||
|
||
#TwoFAEnrollmentSteps button.btn-success { | ||
display: block; | ||
margin-right: auto; | ||
margin-left: auto; | ||
width: 40%; | ||
min-height: 60px; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?php | ||
|
||
|
||
use ChurchCRM\dto\SystemConfig; | ||
use ChurchCRM\dto\SystemURLs; | ||
|
||
//Set the page title | ||
$sPageTitle = $user->getFullName() . gettext("2 Factor Authentication enrollment"); | ||
include SystemURLs::getDocumentRoot() . '/Include/Header.php'; | ||
?> | ||
<div id="two-factor-enrollment-react-app"> </div> | ||
<script src="<?= SystemURLs::getRootPath() ?>/skin/js-react/two-factor-enrollment-app.js"></script> | ||
<?php include SystemURLs::getDocumentRoot() . '/Include/Footer.php'; ?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
<?php | ||
|
||
use ChurchCRM\dto\SystemURLs; | ||
|
||
//Set the page title | ||
$sPageTitle = gettext("Unsupported Two Factor Authentication Configuration"); | ||
require SystemURLs::getDocumentRoot() . '/Include/Header.php'; | ||
?> | ||
|
||
<div class="box"> | ||
<div class="box-body"> | ||
|
||
<div class="box-body"> | ||
<h3><i class="fa fa-warning text-yellow"></i> <?= gettext("Unable To Begin Two Factor Authentication Enrollment") ?></h3> | ||
|
||
<p><?= gettext("Two factor authentication requires ChurchCRM administrators to configure a few parameters").":" ?></p> | ||
<ul> | ||
<li><?= gettext("System configuration ") . " bEnable2FA " . gettext("Must be set to 'true'") ?></li> | ||
<li><?= gettext("Include/Config.php must define an encryption key for storing 2FA secret keys in the database by setting a value for") ?>: $TwoFASecretKey</li> | ||
</ul> | ||
</div> | ||
</div> | ||
|
||
<?php require SystemURLs::getDocumentRoot() . '/Include/Footer.php'; ?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters